{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Dynamic Range Analysis" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from __future__ import division, unicode_literals\n", "\n", "import numpy as np\n", "from bokeh.io import push_notebook, show, output_notebook\n", "from bokeh.layouts import row\n", "from bokeh.models import ColumnDataSource, Label\n", "from bokeh.plotting import figure\n", "from bokeh.models.widgets import DataTable, DateFormatter, TableColumn, PreText\n", "from ipywidgets import interact\n", "from scipy.optimize import fsolve\n", "\n", "import colour\n", "import colour_hdri" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "\n", "
\n", " \n", " Loading BokehJS ...\n", "
" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/javascript": [ "\n", "(function(global) {\n", " function now() {\n", " return new Date();\n", " }\n", "\n", " var force = \"1\";\n", "\n", " if (typeof (window._bokeh_onload_callbacks) === \"undefined\" || force !== \"\") {\n", " window._bokeh_onload_callbacks = [];\n", " window._bokeh_is_loading = undefined;\n", " }\n", "\n", "\n", " \n", " if (typeof (window._bokeh_timeout) === \"undefined\" || force !== \"\") {\n", " window._bokeh_timeout = Date.now() + 5000;\n", " window._bokeh_failed_load = false;\n", " }\n", "\n", " var NB_LOAD_WARNING = {'data': {'text/html':\n", " \"
\\n\"+\n", " \"

\\n\"+\n", " \"BokehJS does not appear to have successfully loaded. If loading BokehJS from CDN, this \\n\"+\n", " \"may be due to a slow or bad network connection. Possible fixes:\\n\"+\n", " \"

\\n\"+\n", " \"\\n\"+\n", " \"\\n\"+\n", " \"from bokeh.resources import INLINE\\n\"+\n", " \"output_notebook(resources=INLINE)\\n\"+\n", " \"\\n\"+\n", " \"
\"}};\n", "\n", " function display_loaded() {\n", " if (window.Bokeh !== undefined) {\n", " Bokeh.$(\"#e5b0d91f-82f7-4fc8-b85d-ea7eab312262\").text(\"BokehJS successfully loaded.\");\n", " } else if (Date.now() < window._bokeh_timeout) {\n", " setTimeout(display_loaded, 100)\n", " }\n", " }\n", "\n", " function run_callbacks() {\n", " window._bokeh_onload_callbacks.forEach(function(callback) { callback() });\n", " delete window._bokeh_onload_callbacks\n", " console.info(\"Bokeh: all callbacks have finished\");\n", " }\n", "\n", " function load_libs(js_urls, callback) {\n", " window._bokeh_onload_callbacks.push(callback);\n", " if (window._bokeh_is_loading > 0) {\n", " console.log(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n", " return null;\n", " }\n", " if (js_urls == null || js_urls.length === 0) {\n", " run_callbacks();\n", " return null;\n", " }\n", " console.log(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n", " window._bokeh_is_loading = js_urls.length;\n", " for (var i = 0; i < js_urls.length; i++) {\n", " var url = js_urls[i];\n", " var s = document.createElement('script');\n", " s.src = url;\n", " s.async = false;\n", " s.onreadystatechange = s.onload = function() {\n", " window._bokeh_is_loading--;\n", " if (window._bokeh_is_loading === 0) {\n", " console.log(\"Bokeh: all BokehJS libraries loaded\");\n", " run_callbacks()\n", " }\n", " };\n", " s.onerror = function() {\n", " console.warn(\"failed to load library \" + url);\n", " };\n", " console.log(\"Bokeh: injecting script tag for BokehJS library: \", url);\n", " document.getElementsByTagName(\"head\")[0].appendChild(s);\n", " }\n", " };var element = document.getElementById(\"e5b0d91f-82f7-4fc8-b85d-ea7eab312262\");\n", " if (element == null) {\n", " console.log(\"Bokeh: ERROR: autoload.js configured with elementid 'e5b0d91f-82f7-4fc8-b85d-ea7eab312262' but no matching script tag was found. \")\n", " return false;\n", " }\n", "\n", " var js_urls = ['https://cdn.pydata.org/bokeh/release/bokeh-0.12.2.min.js', 'https://cdn.pydata.org/bokeh/release/bokeh-widgets-0.12.2.min.js', 'https://cdn.pydata.org/bokeh/release/bokeh-compiler-0.12.2.min.js'];\n", "\n", " var inline_js = [\n", " function(Bokeh) {\n", " Bokeh.set_log_level(\"info\");\n", " },\n", " \n", " function(Bokeh) {\n", " \n", " Bokeh.$(\"#e5b0d91f-82f7-4fc8-b85d-ea7eab312262\").text(\"BokehJS is loading...\");\n", " },\n", " function(Bokeh) {\n", " console.log(\"Bokeh: injecting CSS: https://cdn.pydata.org/bokeh/release/bokeh-0.12.2.min.css\");\n", " Bokeh.embed.inject_css(\"https://cdn.pydata.org/bokeh/release/bokeh-0.12.2.min.css\");\n", " console.log(\"Bokeh: injecting CSS: https://cdn.pydata.org/bokeh/release/bokeh-widgets-0.12.2.min.css\");\n", " Bokeh.embed.inject_css(\"https://cdn.pydata.org/bokeh/release/bokeh-widgets-0.12.2.min.css\");\n", " }\n", " ];\n", "\n", " function run_inline_js() {\n", " \n", " if ((window.Bokeh !== undefined) || (force === \"1\")) {\n", " for (var i = 0; i < inline_js.length; i++) {\n", " inline_js[i](window.Bokeh);\n", " }if (force === \"1\") {\n", " display_loaded();\n", " }} else if (Date.now() < window._bokeh_timeout) {\n", " setTimeout(run_inline_js, 100);\n", " } else if (!window._bokeh_failed_load) {\n", " console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n", " window._bokeh_failed_load = true;\n", " } else if (!force) {\n", " var cell = $(\"#e5b0d91f-82f7-4fc8-b85d-ea7eab312262\").parents('.cell').data().cell;\n", " cell.output_area.append_execute_result(NB_LOAD_WARNING)\n", " }\n", "\n", " }\n", "\n", " if (window._bokeh_is_loading === 0) {\n", " console.log(\"Bokeh: BokehJS loaded, going straight to plotting\");\n", " run_inline_js();\n", " } else {\n", " load_libs(js_urls, function() {\n", " console.log(\"Bokeh: BokehJS plotting callback run at\", now());\n", " run_inline_js();\n", " });\n", " }\n", "}(this));" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "output_notebook()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Helpers & Resources" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false }, "outputs": [], "source": [ "BOKEH_TOOLS = 'pan,wheel_zoom,box_zoom,reset,resize'\n", "\n", "BIT_DEPTH = 12\n", "MINIMUM_REPRESENTABLE_VALUE = 1 / 2 ** BIT_DEPTH\n", "MAXIMUM_REPRESENTABLE_VALUE = 1\n", "\n", "\n", "def solve_EV_range(function,\n", " minimum_representable_value=MINIMUM_REPRESENTABLE_VALUE, \n", " maximum_representable_value=MAXIMUM_REPRESENTABLE_VALUE, \n", " **kwargs):\n", " \n", " EV_min = fsolve(lambda x: function(2 ** x, **kwargs) - minimum_representable_value, 0)\n", " EV_max = fsolve(lambda x: function(2 ** x, **kwargs) - maximum_representable_value, 0)\n", " EV_neutral_gray = fsolve(lambda x: function(2 ** x, **kwargs) - 0.18, 0)\n", " EV_domain = np.arange(np.floor(EV_min), np.ceil(EV_max) + 1)\n", " \n", " EV_range = function(2 ** EV_domain, **kwargs)\n", " \n", " return colour.Structure(\n", " **{'EV_domain': EV_domain.astype(np.int_), \n", " 'EV_range': EV_range,\n", " 'EV_min': EV_min,\n", " 'EV_max': EV_max,\n", " 'EV_neutral_gray': EV_neutral_gray})\n", "\n", "\n", "def dynamic_range_plot(function, \n", " minimum_representable_value=MINIMUM_REPRESENTABLE_VALUE, \n", " maximum_representable_value=MAXIMUM_REPRESENTABLE_VALUE, \n", " **kwargs):\n", "\n", " solve_r = solve_EV_range(\n", " function, \n", " minimum_representable_value, \n", " maximum_representable_value,\n", " **colour.filter_kwargs(function, **kwargs))\n", "\n", " settings = colour.Structure(\n", " **{'title': None,\n", " 'width': 720,\n", " 'height': 405,\n", " 'x_range': None,\n", " 'y_range': None,\n", " 'y_axis_location' : 'right',\n", " 'toolbar_location': 'left',\n", " 'toolbar_sticky': False,\n", " 'tools': BOKEH_TOOLS})\n", " settings.update({(key, value) for key, value in kwargs.items() \n", " if key in settings})\n", "\n", " plot = figure(**settings)\n", "\n", " plot.xaxis.axis_label = 'Dynamic Range (EV)'\n", " plot.xaxis.major_tick_line_width = 8\n", " \n", " EV_rectangle = plot.rect(\n", " solve_r.EV_domain, solve_r.EV_range, width=0.5, height=0.01) \n", " \n", " neutral_gray_line = plot.line(\n", " (-64, 64), np.ones(2) * 0.18, line_width=2, line_dash=[8, 2]) \n", "\n", " data = {'Metric': ('Minimum EV',\n", " 'Maximum EV',\n", " 'EV < 18%',\n", " 'EV > 18%'),\n", " 'Value':(solve_r.EV_min, \n", " solve_r.EV_max,\n", " solve_r.EV_min - solve_r.EV_neutral_gray,\n", " solve_r.EV_max - solve_r.EV_neutral_gray)}\n", " source = ColumnDataSource(data)\n", " columns = [TableColumn(field='Metric', title='Metric'),\n", " TableColumn(field='Value', title='Value')]\n", " table = DataTable(source=source, \n", " columns=columns, \n", " row_headers=False, \n", " width=int(settings.width / 3))\n", " \n", " handle = show(row(plot, table), notebook_handle=True)\n", "\n", " return colour.Structure(\n", " **{'EV_rectangle': EV_rectangle,\n", " 'table': table,\n", " 'handle': handle})\n", "\n", "\n", "def update_dynamic_range_plot(structure, function, **kwargs):\n", " solve_r = solve_EV_range(function, **kwargs)\n", " \n", " structure.EV_rectangle.data_source.data['x'] = solve_r.EV_domain \n", " structure.EV_rectangle.data_source.data['y'] = solve_r.EV_range\n", "\n", " structure.table.source.data = {\n", " 'Metric': ('Minimum EV',\n", " 'Maximum EV',\n", " 'EV < 18%',\n", " 'EV > 18%'),\n", " 'Value':(solve_r.EV_min, \n", " solve_r.EV_max,\n", " solve_r.EV_min - solve_r.EV_neutral_gray,\n", " solve_r.EV_max - solve_r.EV_neutral_gray)}\n", "\n", " push_notebook(handle=structure.handle) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Gamma" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "\n", "\n", "
\n", "
\n", "
\n", "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "GAMMA_FUNCTION_S = dynamic_range_plot(\n", " colour.gamma_function, \n", " x_range=(-BIT_DEPTH, 0), \n", " y_range=(0, 1), \n", " exponent=1)\n", "\n", "\n", "def update_gamma_function(exponent=1.0):\n", " update_dynamic_range_plot(\n", " GAMMA_FUNCTION_S, colour.gamma_function, exponent=exponent)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "interact(update_gamma_function, exponent=(0.1, 4, 0.01))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exposure" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "\n", "\n", "
\n", "
\n", "
\n", "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "EXPOSURE_FUNCTION_S = dynamic_range_plot(\n", " colour_hdri.adjust_exposure, \n", " x_range=(-BIT_DEPTH, 0), \n", " y_range=(0, 1), \n", " EV=0)\n", "\n", "\n", "def update_exposure_function(EV_a=0):\n", " update_dynamic_range_plot(\n", " EXPOSURE_FUNCTION_S, colour_hdri.adjust_exposure, EV=EV_a)" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "interact(update_exposure_function, EV_a=(-4, 4, 0.1))" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "\n", "\n", "
\n", "
\n", "
\n", "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "_ = dynamic_range_plot(\n", " colour_hdri.tonemapping_operator_simple, \n", " x_range=(-BIT_DEPTH, 4), \n", " y_range=(0, 1))" ] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.12" } }, "nbformat": 4, "nbformat_minor": 0 }