From 159ef14fb9e198bb0066ea14e6b980f065de63dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Picca=20Fr=C3=A9d=C3=A9ric-Emmanuel?= Date: Tue, 31 Jul 2018 16:22:25 +0200 Subject: New upstream version 0.8.0+dfsg --- .../.ipynb_checkpoints/Image-checkpoint.ipynb | 1841 +++++++++ doc/source/Tutorials/Image.ipynb | 1873 +++++++++ .../Sift/.ipynb_checkpoints/sift-checkpoint.ipynb | 4248 +++++++++++++++++++ doc/source/Tutorials/Sift/sift.ipynb | 4321 ++++++++++++++++++++ doc/source/Tutorials/convert.rst | 28 +- doc/source/Tutorials/fit.rst | 164 +- doc/source/Tutorials/io.rst | 72 +- doc/source/Tutorials/specfile_to_hdf5.rst | 58 +- doc/source/Tutorials/writing_NXdata.rst | 357 ++ doc/source/developers.rst | 98 + doc/source/ext/snapshotqt_directive.py | 257 ++ doc/source/index.rst | 2 +- doc/source/install.rst | 3 +- doc/source/modules/gui/colors.rst | 9 + doc/source/modules/gui/dialog/colormapdialog.rst | 12 + doc/source/modules/gui/dialog/groupdialog.rst | 8 + .../gui/dialog/img/abstractdatafiledialog.png | Bin 0 -> 116574 bytes doc/source/modules/gui/dialog/img/groupdialog.png | Bin 0 -> 26321 bytes doc/source/modules/gui/dialog/index.rst | 14 +- doc/source/modules/gui/gallery.rst | 24 +- doc/source/modules/gui/hdf5/getting_started.rst | 65 +- doc/source/modules/gui/hdf5/hdf5headerview.rst | 9 + doc/source/modules/gui/hdf5/index.rst | 1 + doc/source/modules/gui/icons.rst | 57 + doc/source/modules/gui/index.rst | 2 + doc/source/modules/gui/plot/colormap.rst | 16 - doc/source/modules/gui/plot/dev.rst | 18 - doc/source/modules/gui/plot/getting_started.rst | 32 +- doc/source/modules/gui/plot/img/ScatterView.png | Bin 0 -> 202300 bytes doc/source/modules/gui/plot/img/netArea.png | Bin 0 -> 18711 bytes doc/source/modules/gui/plot/img/netCounts.png | Bin 18711 -> 11424 bytes doc/source/modules/gui/plot/img/rawArea.png | Bin 0 -> 18437 bytes doc/source/modules/gui/plot/img/rawCounts.png | Bin 18437 -> 9943 bytes doc/source/modules/gui/plot/img/statsWidget.png | Bin 0 -> 11662 bytes doc/source/modules/gui/plot/index.rst | 24 +- doc/source/modules/gui/plot/items.rst | 7 + doc/source/modules/gui/plot/plottools.rst | 36 - doc/source/modules/gui/plot/plotwidget.rst | 1 - doc/source/modules/gui/plot/scatterview.rst | 20 + doc/source/modules/gui/plot/stats/index.rst | 18 + doc/source/modules/gui/plot/stats/stats.rst | 6 + doc/source/modules/gui/plot/stats/statshandler.rst | 7 + doc/source/modules/gui/plot/statswidget.rst | 33 + doc/source/modules/gui/plot/tools.rst | 108 + doc/source/modules/gui/utils.rst | 12 + doc/source/modules/image/index.rst | 3 +- doc/source/modules/image/marchingsquares.rst | 11 + doc/source/modules/io/nxdata.rst | 6 - doc/source/modules/math/colormap.rst | 6 + doc/source/modules/math/combo.rst | 6 +- doc/source/modules/math/index.rst | 1 + doc/source/modules/sx.rst | 6 + doc/source/overview.rst | 6 + .../sample_code/img/plotInteractiveImageROI.png | Bin 0 -> 564397 bytes .../sample_code/img/plotUpdateCurveFromThread.png | Bin 0 -> 62761 bytes .../sample_code/img/plotUpdateFromThread.png | Bin 62761 -> 0 bytes .../sample_code/img/plotUpdateImageFromThread.png | Bin 0 -> 50938 bytes doc/source/sample_code/index.rst | 53 +- doc/source/tutorials.rst | 1 + 59 files changed, 13640 insertions(+), 320 deletions(-) create mode 100644 doc/source/Tutorials/.ipynb_checkpoints/Image-checkpoint.ipynb create mode 100644 doc/source/Tutorials/Image.ipynb create mode 100644 doc/source/Tutorials/Sift/.ipynb_checkpoints/sift-checkpoint.ipynb create mode 100644 doc/source/Tutorials/Sift/sift.ipynb create mode 100644 doc/source/Tutorials/writing_NXdata.rst create mode 100644 doc/source/developers.rst create mode 100644 doc/source/ext/snapshotqt_directive.py create mode 100644 doc/source/modules/gui/colors.rst create mode 100644 doc/source/modules/gui/dialog/colormapdialog.rst create mode 100644 doc/source/modules/gui/dialog/groupdialog.rst create mode 100644 doc/source/modules/gui/dialog/img/abstractdatafiledialog.png create mode 100644 doc/source/modules/gui/dialog/img/groupdialog.png create mode 100644 doc/source/modules/gui/hdf5/hdf5headerview.rst delete mode 100644 doc/source/modules/gui/plot/colormap.rst create mode 100644 doc/source/modules/gui/plot/img/ScatterView.png create mode 100644 doc/source/modules/gui/plot/img/netArea.png create mode 100644 doc/source/modules/gui/plot/img/rawArea.png create mode 100644 doc/source/modules/gui/plot/img/statsWidget.png delete mode 100644 doc/source/modules/gui/plot/plottools.rst create mode 100644 doc/source/modules/gui/plot/scatterview.rst create mode 100644 doc/source/modules/gui/plot/stats/index.rst create mode 100644 doc/source/modules/gui/plot/stats/stats.rst create mode 100644 doc/source/modules/gui/plot/stats/statshandler.rst create mode 100644 doc/source/modules/gui/plot/statswidget.rst create mode 100644 doc/source/modules/gui/plot/tools.rst create mode 100644 doc/source/modules/gui/utils.rst create mode 100644 doc/source/modules/image/marchingsquares.rst create mode 100644 doc/source/modules/math/colormap.rst create mode 100644 doc/source/sample_code/img/plotInteractiveImageROI.png create mode 100644 doc/source/sample_code/img/plotUpdateCurveFromThread.png delete mode 100644 doc/source/sample_code/img/plotUpdateFromThread.png create mode 100644 doc/source/sample_code/img/plotUpdateImageFromThread.png (limited to 'doc') diff --git a/doc/source/Tutorials/.ipynb_checkpoints/Image-checkpoint.ipynb b/doc/source/Tutorials/.ipynb_checkpoints/Image-checkpoint.ipynb new file mode 100644 index 0000000..8042fc7 --- /dev/null +++ b/doc/source/Tutorials/.ipynb_checkpoints/Image-checkpoint.ipynb @@ -0,0 +1,1841 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Image processing performance optimisation\n", + "\n", + "The idea of this tutorial is to give some suggestion how optimise image processing.\n", + "In the following example we decompress a CBF image from a Pilatus 6M, take the log-scale and calculate the histogram of pixel intensities before plotting. \n", + "This very simple operation takes >300ms, what makes it unpractical for live display of images coming from a detector. " + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Populating the interactive namespace from numpy and matplotlib\n" + ] + } + ], + "source": [ + "%pylab nbagg" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Using silx version 0.8.0-dev0\n", + "filename powder_200_2_0001.cbf\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/media/data_unused/tvincent/venvs/py3env/lib/python3.4/importlib/_bootstrap.py:321: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.\n", + " return f(*args, **kwds)\n" + ] + } + ], + "source": [ + "import time, os\n", + "start_time = time.time()\n", + "import silx\n", + "print(\"Using silx version \",silx.version)\n", + "from silx.resources import ExternalResources\n", + "downloader = ExternalResources(\"pyFAI\", \"http://www.silx.org/pub/pyFAI/testimages\", \"PYFAI_DATA\")\n", + "fname = downloader.getfile(\"powder_200_2_0001.cbf\")\n", + "print(\"filename\", os.path.basename(fname))\n", + "import fabio\n", + "nbins = 1000" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 380 ms, sys: 40 ms, total: 420 ms\n", + "Wall time: 420 ms\n" + ] + } + ], + "source": [ + "%%time\n", + "\n", + "#Display an image and the histogram of values (in log scale)\n", + "img = fabio.open(fname).data\n", + "log_img = numpy.arcsinh(img) # arcsinh is well behaved log-like function\n", + "his, edges = numpy.histogram(log_img, nbins)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width);\n", + " canvas.attr('height', height);\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " event.shiftKey = false;\n", + " // Send a \"J\" for go to next cell\n", + " event.which = 74;\n", + " event.keyCode = 74;\n", + " manager.command_mode();\n", + " manager.handle_keydown(event);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fig, ax = subplots(1,2,)\n", + "center = (edges[1:] + edges[:-1])/2.0 # this is the center of the bins \n", + "ax[1].imshow(log_img, cmap=\"inferno\")\n", + "ax[0].plot(center,his)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The results are the same and the processing time is 6x faster. Hence, one can envisage realtime visualisation of images coming from detectors.\n", + "\n", + "## Investigation of the timings" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Profiling info for OpenCL ByteOffset\n", + " copy raw H -> D:\t1.048ms\n", + " memset mask:\t0.374ms\n", + " memset counter:\t0.006ms\n", + " mark exceptions:\t0.688ms\n", + " copy counter D -> H:\t0.003ms\n", + " treat_exceptions:\t0.122ms\n", + " double scan:\t1.672ms\n", + " copy_results:\t1.220ms\n", + "________________________________________________________________________________\n", + " Total execution time:\t5.132ms\n", + "\n", + "Profiling info for OpenCL ImageProcessing\n", + " copy D->D:\t3.041ms\n", + " max_min_stage1:\t0.873ms\n", + " max_min_stage2:\t0.009ms\n", + " histogram:\t0.775ms\n", + "________________________________________________________________________________\n", + " Total execution time:\t4.699ms\n" + ] + } + ], + "source": [ + "ip.set_profiling(True)\n", + "bo.set_profiling(True)\n", + "ip.reset_log()\n", + "bo.reset_log()\n", + "raw = cbf.read(fname, only_raw=True)\n", + "dec = bo(raw, as_float=True)\n", + "ash(dec, res)\n", + "his, edges = ip.histogram(res, nbins, copy=False)\n", + "log_img = res.get()\n", + "import os\n", + "print(os.linesep.join(bo.log_profile()))\n", + "print(os.linesep.join(ip.log_profile()))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Conclusion\n", + "This notebook explains how to optimise some heavy numerical processing up to 10x speed-up for realtime image processing using GPU." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.4.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/doc/source/Tutorials/Image.ipynb b/doc/source/Tutorials/Image.ipynb new file mode 100644 index 0000000..5ddab38 --- /dev/null +++ b/doc/source/Tutorials/Image.ipynb @@ -0,0 +1,1873 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Image processing performance optimisation\n", + "\n", + "The idea of this tutorial is to give some suggestion how optimise image processing.\n", + "In the following example we decompress a CBF image from a Pilatus 6M, take the log-scale and calculate the histogram of pixel intensities before plotting. \n", + "This very simple operation takes >300ms, what makes it unpractical for live display of images coming from a detector. " + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Populating the interactive namespace from numpy and matplotlib\n" + ] + } + ], + "source": [ + "%pylab nbagg" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Using silx version 0.7.0-dev0\n", + "filename powder_200_2_0001.cbf\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/users/kieffer/VirtualEnvs/py3/lib/python3.5/site-packages/h5py/__init__.py:36: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.\n", + " from ._conv import register_converters as _register_converters\n" + ] + } + ], + "source": [ + "import time, os\n", + "start_time = time.time()\n", + "import silx\n", + "print(\"Using silx version \",silx.version)\n", + "from silx.resources import ExternalResources\n", + "downloader = ExternalResources(\"pyFAI\", \"http://www.silx.org/pub/pyFAI/testimages\", \"PYFAI_DATA\")\n", + "fname = downloader.getfile(\"powder_200_2_0001.cbf\")\n", + "print(\"filename\", os.path.basename(fname))\n", + "import fabio\n", + "nbins = 1000" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 252 ms, sys: 32 ms, total: 284 ms\n", + "Wall time: 280 ms\n" + ] + } + ], + "source": [ + "%%time\n", + "\n", + "#Display an image and the histogram of values (in log scale)\n", + "img = fabio.open(fname).data\n", + "log_img = numpy.arcsinh(img) # arcsinh is well behaved log-like function\n", + "his, edges = numpy.histogram(log_img, nbins)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " fig.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " event.shiftKey = false;\n", + " // Send a \"J\" for go to next cell\n", + " event.which = 74;\n", + " event.keyCode = 74;\n", + " manager.command_mode();\n", + " manager.handle_keydown(event);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fig, ax = subplots(1,2,)\n", + "center = (edges[1:] + edges[:-1])/2.0 # this is the center of the bins \n", + "ax[1].imshow(log_img, cmap=\"inferno\")\n", + "ax[0].plot(center,his)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The results are the same and the processing time is 6x faster. Hence, one can envisage realtime visualisation of images coming from detectors.\n", + "\n", + "## Investigation of the timings" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Profiling info for OpenCL ByteOffset\n", + " copy raw H -> D:\t0.972ms\n", + " memset mask:\t0.091ms\n", + " memset counter:\t0.005ms\n", + " mark exceptions:\t0.092ms\n", + " copy counter D -> H:\t0.002ms\n", + " treat_exceptions:\t0.027ms\n", + " double scan:\t0.200ms\n", + " copy_results:\t0.141ms\n", + "________________________________________________________________________________\n", + " Total execution time:\t1.530ms\n", + "\n", + "Profiling info for OpenCL ImageProcessing\n", + " copy D->D :\t0.104ms\n", + " max_min_stage1:\t0.069ms\n", + " max_min_stage2:\t0.009ms\n", + " histogram:\t0.098ms\n", + "________________________________________________________________________________\n", + " Total execution time:\t0.281ms\n" + ] + } + ], + "source": [ + "ip.set_profiling(True)\n", + "bo.set_profiling(True)\n", + "ip.reset_log()\n", + "bo.reset_log()\n", + "raw = cbf.read(fname, only_raw=True)\n", + "dec = bo(raw, as_float=True)\n", + "ash(dec, res)\n", + "his, edges = ip.histogram(res, nbins, copy=False)\n", + "log_img = res.get()\n", + "import os\n", + "print(os.linesep.join(bo.log_profile()))\n", + "print(os.linesep.join(ip.log_profile()))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Conclusion\n", + "This notebook explains how to optimise some heavy numerical processing up to 10x speed-up for realtime image processing using GPU." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.4.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/doc/source/Tutorials/Sift/.ipynb_checkpoints/sift-checkpoint.ipynb b/doc/source/Tutorials/Sift/.ipynb_checkpoints/sift-checkpoint.ipynb new file mode 100644 index 0000000..f6544c1 --- /dev/null +++ b/doc/source/Tutorials/Sift/.ipynb_checkpoints/sift-checkpoint.ipynb @@ -0,0 +1,4248 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# SIFT image alignment tutorial\n", + "\n", + "SIFT (Scale-Invariant Feature Transform) is an algorithm developped by David Lowe in 1999. \n", + "It is a worldwide reference for image alignment and object recognition. \n", + "The robustness of this method enables to detect features at different scales, angles and illumination of a scene. \n", + "\n", + "Silx provides an implementation of SIFT in OpenCL, meaning that it can run on Graphics Processing Units and Central Processing Units as well. \n", + "Interest points are detected in the image, then data structures called *descriptors* are built to be characteristic of the scene, so that two different images of the same scene have similar descriptors. \n", + "They are robust to transformations like translation, rotation, rescaling and illumination change, which make SIFT interesting for image stitching. \n", + "\n", + "In the fist stage, descriptors are computed from the input images. \n", + "Then, they are compared to determine the geometric transformation to apply in order to align the images. \n", + "This implementation can run on most graphic cards and CPU, making it usable on many setups. \n", + "OpenCL processes are handled from Python with PyOpenCL, a module to access OpenCL parallel computation API.\n", + "\n", + "This tutuorial explains the three subsequent steps:\n", + "\n", + "* keypoint extraction\n", + "* Keypoint matching\n", + "* image alignment\n", + "\n", + "All the tutorial has been made using the Jupyter notebook." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Populating the interactive namespace from numpy and matplotlib\n" + ] + } + ], + "source": [ + "import time\n", + "start_time = time.time()\n", + "%pylab nbagg" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Silx version 0.8.0-dev0\n" + ] + }, + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width);\n", + " canvas.attr('height', height);\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " event.shiftKey = false;\n", + " // Send a \"J\" for go to next cell\n", + " event.which = 74;\n", + " event.keyCode = 74;\n", + " manager.command_mode();\n", + " manager.handle_keydown(event);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#Overlay keypoints on the image:\n", + "fig, ax = subplots()\n", + "ax.imshow(image)\n", + "ax.plot(keypoints[:].x, keypoints[:].y,\".g\")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width);\n", + " canvas.attr('height', height);\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " event.shiftKey = false;\n", + " // Send a \"J\" for go to next cell\n", + " event.which = 74;\n", + " event.keyCode = 74;\n", + " manager.command_mode();\n", + " manager.handle_keydown(event);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#One can see 3 groups of keypoints, boundaries at: 8 and 20. Let's display them using colors.\n", + "S = 8\n", + "L = 20\n", + "tiny = keypoints[keypoints[:].scale=S)]\n", + "bigger = keypoints[keypoints[:].scale>=L]\n", + "\n", + "fig, ax = subplots()\n", + "ax.imshow(image, cmap=\"gray\")\n", + "ax.plot(tiny[:].x, tiny[:].y,\",g\", label=\"tiny\")\n", + "ax.plot(small[:].x, small[:].y,\".b\", label=\"small\")\n", + "ax.plot(bigger[:].x, bigger[:].y,\"or\", label=\"large\")\n", + "ax.legend()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Image matching and alignment\n", + "\n", + "Matching can also be performed on the device (GPU) as every single keypoint from an image needs to be compared with all\n", + "keypoints from the second image.\n", + "\n", + "In this simple example we will simple offset the first image by a few pixels" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "shifted = numpy.zeros_like(image)\n", + "shifted[5:,8:] = image[:-5, :-8]\n", + "shifted_points = sift_ocl(shifted)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 8 ms, sys: 0 ns, total: 8 ms\n", + "Wall time: 4.49 ms\n", + "CPU times: user 4 ms, sys: 0 ns, total: 4 ms\n", + "Wall time: 6.34 ms\n", + "Number of Keypoints with for image 1 : 411, For image 2 : 399, Matching keypoints: 353\n", + "Measured offsets dx: 8.000, dy: 5.000\n" + ] + } + ], + "source": [ + "%time mp = sift.MatchPlan()\n", + "%time match = mp(keypoints, shifted_points)\n", + "print(\"Number of Keypoints with for image 1 : %i, For image 2 : %i, Matching keypoints: %i\" % (keypoints.size, shifted_points.size, match.shape[0]))\n", + "from numpy import median\n", + "print(\"Measured offsets dx: %.3f, dy: %.3f\"%(median(match[:,1].x-match[:,0].x),median(match[:,1].y-match[:,0].y)))" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width);\n", + " canvas.attr('height', height);\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " event.shiftKey = false;\n", + " // Send a \"J\" for go to next cell\n", + " event.which = 74;\n", + " event.keyCode = 74;\n", + " manager.command_mode();\n", + " manager.handle_keydown(event);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# display test image\n", + "import silx\n", + "print(\"Silx version %s\"%silx.version)\n", + "\n", + "from PIL import Image\n", + "from silx.test.utils import utilstest\n", + "path = utilstest.getfile(\"lena.png\")\n", + "image = numpy.asarray(Image.open(path))\n", + "fig, ax = subplots()\n", + "ax.imshow(image)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 680 ms, sys: 236 ms, total: 916 ms\n", + "Wall time: 919 ms\n", + "Device used for calculation: TITAN V\n" + ] + } + ], + "source": [ + "#Initialization of the sift object is time consuming: it compiles all the code.\n", + "import os \n", + "#set to 1 to see the compilation going on\n", + "os.environ[\"PYOPENCL_COMPILER_OUTPUT\"] = \"0\" \n", + "#switch to \"GPU\" to \"CPU\" to enable fail-save version.\n", + "devicetype=\"GPU\"\n", + "from silx.image import sift\n", + "\n", + "%time sift_ocl = sift.SiftPlan(template=image, devicetype=devicetype) \n", + "\n", + "print(\"Device used for calculation: \", sift_ocl.ctx.devices[0].name)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Time for calculating the keypoints on one image of size 512x512\n", + "CPU times: user 20 ms, sys: 4 ms, total: 24 ms\n", + "Wall time: 23.6 ms\n", + "Number of keypoints: 411\n", + "Keypoint content:\n", + "(numpy.record, [('x', '');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " fig.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " event.shiftKey = false;\n", + " // Send a \"J\" for go to next cell\n", + " event.which = 74;\n", + " event.keyCode = 74;\n", + " manager.command_mode();\n", + " manager.handle_keydown(event);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "Text(0.5,0,'scale')" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "#Diplaying keypoints by scale:\n", + "fig, ax = subplots()\n", + "ax.hist(keypoints[:].scale, 100)\n", + "ax.set_xlabel(\"scale\")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " fig.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " event.shiftKey = false;\n", + " // Send a \"J\" for go to next cell\n", + " event.which = 74;\n", + " event.keyCode = 74;\n", + " manager.command_mode();\n", + " manager.handle_keydown(event);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "Text(0.5,1,'aligned')" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Example of usage of the automatic alignment:\n", + "import scipy.ndimage\n", + "rotated = scipy.ndimage.rotate(image, 20, reshape=False)\n", + "sa = sift.LinearAlign(image, devicetype=devicetype)\n", + "fig,ax = subplots(1, 3, figsize=(9,3))\n", + "ax[0].imshow(image)\n", + "ax[0].set_title(\"original\")\n", + "ax[1].imshow(rotated)\n", + "ax[1].set_title(\"rotated\")\n", + "ax[2].imshow(sa.align(rotated))\n", + "ax[2].set_title(\"aligned\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### References\n", + "\n", + "- David G. Lowe, Distinctive image features from scale-invariant keypoints, International Journal of Computer Vision, vol. 60, no 2, 2004, p. 91–110 - \"http://www.cs.ubc.ca/~lowe/papers/ijcv04.pdf\"\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Total execution time: 1.972s\n" + ] + } + ], + "source": [ + "print(\"Total execution time: %.3fs\" % (time.time() - start_time))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.5.3" + } + }, + "nbformat": 4, + "nbformat_minor": 1 +} diff --git a/doc/source/Tutorials/convert.rst b/doc/source/Tutorials/convert.rst index c7e67a6..125bbe9 100644 --- a/doc/source/Tutorials/convert.rst +++ b/doc/source/Tutorials/convert.rst @@ -5,9 +5,9 @@ Converting various data files to HDF5 This document explains how to convert SPEC files, EDF files and various other data formats into HDF5 files. -An understanding of the way these data formats are exposed by the :meth:`silx.io.open` +Understanding the way these data formats are exposed by the :meth:`silx.io.open` function is a prerequisite for this tutorial. You can learn more about this subject by -reading :doc:`io`. +reading ":doc:`io`". Using the convert module ++++++++++++++++++++++++ @@ -25,23 +25,23 @@ HDF5 file with the same structure as the one exposed by the :mod:`spech5` or :mo You can then read the file with any HDF5 reader. -The function :func:`silx.io.convert.convert` is a simplified version of a +The function :func:`silx.io.convert.convert` is a simplified version of the more flexible function :func:`silx.io.convert.write_to_h5`. -The latter allows you to write scans into a specific HDF5 group in the output directory. -You can also decide whether you want to overwrite an existing file, or append data to it. +The latter allows you writing scans into a specific HDF5 group in the output directory. +You can also decide whether you want to overwrite an existing file or append data to it. You can specify whether existing data with the same name as input data should be overwritten or ignored. -This allows you to repeatedly transfer new content of a SPEC file to an existing -HDF5 file, in between two scans. +This allows you to repeatedly transfer the new content of a SPEC file to an existing +HDF5 file between two scans. The following script is an example of a command line interface to :func:`write_to_h5`. .. literalinclude:: ../../../examples/writetoh5.py :lines: 44- -But the functionality implemented in this script (and much more) is already implemented +Notice that the functionality and muche more implemented in this script is already implemented in the *silx convert* application. @@ -51,7 +51,7 @@ Using the convert application .. versionadded:: 0.6 -*silx* also provides a ``silx convert`` command line application, which allows you to +*silx* also provides a ``silx convert`` command line application, by means of which you can perform standard conversions without having to write your own program. Type ``silx convert --help`` in a terminal to see all available options. @@ -70,10 +70,10 @@ The simplest command to convert a single SPEC file to an HDF5 file would be: silx convert myspecfile.dat -As no output name is supplied, the output file name will be a time-stamp with a +As no output name is supplied, the output file name will be a timestamp with a *.h5* suffix (e.g. *20180110-114930.h5*). -The following example allows you to append the content of a SPEC file to an +In the following example it is shown how to append the content of a SPEC file to an existing HDF5 file:: silx convert myspecfile.dat -m a -o myhdf5file.h5 @@ -81,7 +81,7 @@ existing HDF5 file:: The ``-m a`` argument stands for *append mode*. The ``-o myhdf5file.h5`` argument is used to specify the output file name. -You could write the file into a specific group of the HDF5 file by providing +You could write the file into a specific group of the HDF5 file by writing the complete URL in the format ``file_path::group_path``. For instance:: silx convert myspecfile.dat -m a -o archive.h5::/2017-09-20/SPEC @@ -91,7 +91,7 @@ Merging a stack of images ************************* *silx convert* can merge a stack of image files. -It support series of single frame files, and is based on +It supports series of single frame files, and is based on `fabio.file_series `_. All frames must have the same shape. @@ -101,7 +101,7 @@ The following command merges all files matching a pattern:: The data in the output file is presented as a 3D array. -It is possible to provide multiple indices in the file name pattern, and specify a +It is possible to provide multiple indices in the file name pattern and specify a range for each index:: silx convert --file-pattern ch09__mca_0005_%04d_%04d.edf --begin 0,1 --end 0,54 diff --git a/doc/source/Tutorials/fit.rst b/doc/source/Tutorials/fit.rst index 9889274..b1b28e5 100644 --- a/doc/source/Tutorials/fit.rst +++ b/doc/source/Tutorials/fit.rst @@ -1,8 +1,8 @@ .. _fit-tutorial: -Fit tools ---------- +Best fit tools +-------------- .. contents:: :local: @@ -13,15 +13,15 @@ Using :func:`leastsq` .. currentmodule:: silx.math.fit -Running an iterative fit with :func:`leastsq` involves the following steps: +Running an iterative best fit process with :func:`leastsq` involves the following steps: - - designing a fit model function that has the signature ``f(x, ...)``, + - designing a fit model function having the signature ``f(x, ...)``, where ``x`` is an array of values of the independent variable and all remaining parameters are the parameters to be fitted - defining the sequence of initial values for all parameters to be fitted. - You can usually start with ``[1., 1., ...]`` if you don't know a better + You can usually start with ``[1., 1., ...]`` if you don't have a better estimate. The algorithm is robust enough to converge to a solution most - of the time. + of the times. - setting constraints (optional) Data required to perform a fit is: @@ -29,14 +29,14 @@ Data required to perform a fit is: - an array of ``x`` values (abscissa, independent variable) - an array of ``y`` data points - the ``sigma`` array of uncertainties associated to each data point. - This is optional, by default each data point gets assigned a weight of 1. + This is optional, by default each data point is assigned a weight of 1. Default (unweighted) fit ************************ Let's demonstrate this process in a short example, using synthetic data. We generate an array of synthetic data using a polynomial function of degree 4, -and try to use :func:`leastsq` to find back the functions parameters. +and try to use :func:`leastsq` to find back the function parameters. .. code-block:: python @@ -74,7 +74,7 @@ and try to use :func:`leastsq` to find back the functions parameters. print("Optimal parameters for y2 fitting:\n\t" + "a=%f, b=%f, c=%f, d=%f, e=%f" % (a, b, c, d, e)) -The output of this program is:: +The output of the above program is:: Fit took 35 iterations Reduced chi-square: 682592.670690 @@ -87,18 +87,18 @@ The output of this program is:: The exact results may vary depending on your Python version. -We can see that this fit result is poor. In particular, parameters ``d`` and ``e`` +We can see that the fitting result is poor. In particular, parameters ``d`` and ``e`` are very poorly fitted. This is due to the fact that data points with large values have a stronger influence -in the fit process. In our examples, as ``x`` increases, ``y`` increases fast. -The influence of the weighting, and how to solve this issue is explained in more details +in the fitting process. In our examples, as ``x`` increases, ``y`` increases fast. +The influence of the weighting and how to solve this issue is explained in more details in the next section. -In the meantime, if you simply limit the ``x`` range, to deal with +In the meantime, if you simply limit the ``x`` range to deal with smaller ``y`` values, you can notice that the fit result becomes perfect. -In our example, replacing ``x`` with:: +In our example, replacing ``x`` by:: x = numpy.arange(100) @@ -116,18 +116,16 @@ Weighted fit ************ Since the fitting algorithm minimizes the sum of squared differences between input -and evaluated data, points with higher y value had a greater weight in the fitting process. -A solution to this problem, if we want to improve our fit, is to define uncertainties -for the data. -The larger the uncertainty on a data sample, the smaller its weight will be -in the least-square problem. +and calculated data, points with higher y values have a greater weight in the fitting process. +A way to improve the fit, is to attach uncertainties to the data. +The larger the uncertainty on a data sample, the smaller its weight in the least-square problem. -It is important to set the uncertainties correctly, or you risk favoring either -the lower values or the higher values in your data. +It is important to set the uncertainties correctly, or you risk to introduce a bias either +toward the lower values or toward the higher values in your data. -The common approach in counting experiments is to use the square-root of the data -values as the uncertainty value (assuming a Poissonian law). -Let's apply it to our previous example: +The common approach in event counting experiments is to use the squareroot of the data +values as the uncertainty values (assuming a Poissonian law). +Let's apply it to the previous example: .. code-block:: python @@ -151,40 +149,38 @@ This results in a great improvement:: a=2.400000, b=-10.000000, c=15.200000, d=-24.600000, e=150.000000 The resulting fit is perfect. The fit converged even faster than when -we limited ``x`` range to 0 -- 100. +we limited the ``x`` range to 0 -- 100. To use a real world example, when fitting x-ray fluorescence spectroscopy data, -this common approach means that we consider the variance of each channel to be +this common approach means that we take the variance of each channel to be the number of counts in that channel. That corresponds to assuming a normal distribution. The true distribution being a Poisson distribution, the Gaussian distribution is a good approximation for channels with high number of counts, -but the approximation is not valid when the number of counts in a channel is small. +but the approximation is not valid when the number of counts in a channel is low. Therefore, in spectra where the overall statistics is very low, a weighted fit can lead the fitting process to fit the background -considering the peaks as outliers, because the fit will consider a -channel with 1 count 100 times more relevant than a channel with 100 -counts. +considering the peaks as outliers, because the fit procedure will consider a +channel with 1 count has a 100 times higher weight than a channel with 100 counts. Constrained fit *************** -But let's revert back to our unweighted fit, to experiment -with different approaches to improving the fit. +Let's revert to our unweighted fit and try out with different approaches for improving the fit. -The :func:`leastsq` functions provides -a way to set constraints on parameters. You can for instance assert that a given -parameter must remain equal to it's initial value, or define an acceptable range +The :func:`leastsq` function provides +a way to set constraints on parameters. You can for instance enforce that a given +parameter must remain equal to its initial value, or define an acceptable range for it to vary, or decide that a parameter must be equal to another parameter -multiplied by a certain factor. This is very useful in cases in which you have +multiplied by a certain factor. This is very useful for cases in which you have enough knowledge to make reasonable assumptions on some parameters. -In our case, we will set constraints on ``d`` and ``e``. We will quote ``d`` to +In our case, we will set constraints on ``d`` and ``e``. We will require ``d`` to stay in the range between -25 and -24, and fix ``e`` to 150. -Replace the call to :func:`leastsq` by following lines: +Replace the call to :func:`leastsq` by the following lines: .. code-block:: python @@ -209,7 +205,7 @@ The output of this program is:: Optimal parameters for y fitting: a=2.400000, b=-9.999999, c=15.199648, d=-24.533014, e=150.000000 -The chi-square value is much improved and the results are much better, at the +The chi-square value is substantially improved and the results are much better, at the cost of more iterations. .. _fitmanager-tutorial: @@ -230,7 +226,7 @@ Weighted polynomial fit *********************** The following program accomplishes the same weighted fit of a polynomial as in -the previous tutorial (`Weighted fit`_) +the previous tutorial (See `Weighted fit`_) .. code-block:: python @@ -275,7 +271,7 @@ the previous tutorial (`Weighted fit`_) "a=%f, b=%f, c=%f, d=%f, e=%f" % (a, b, c, d, e)) -The result is the same as in our weighted :func:`leastsq` example, +The result is the same as in the weighted :func:`leastsq` example, as expected:: Fit took 6 iterations @@ -285,19 +281,19 @@ as expected:: Optimal parameters for y2 fitting: a=2.400000, b=-10.000000, c=15.200000, d=-24.600000, e=150.000000 -Fitting gaussians +Fitting Gaussians ***************** The :class:`FitManager` object is especially useful for fitting multi-peak -gaussian-shaped spectra. The *silx* module :mod:`silx.math.fit.fittheories` -provides fit functions and their associated estimation functions that are +Gaussian-shaped spectra. The *silx* module :mod:`silx.math.fit.fittheories` +contains fit functions and their associated estimation functions that are specifically designed for this purpose. -These fit functions can handle variable number of parameters defining a +These fit functions can handle a varying number of parameters defining a variable number of peaks, and the estimation functions use a peak detection algorithm to determine how many initial parameters must be returned. -For the sake of the example, let's test the multi-peak fitting on synthetic +For the sake of example, let's test the multi-peak fitting on synthetic data, generated using another *silx* module: :mod:`silx.math.fit.functions`. .. code-block:: python @@ -333,7 +329,7 @@ data, generated using another *silx* module: :mod:`silx.math.fit.functions`. dummy_list.append(param['fitresult']) print("chisq = ", fit.chisq) -And the result of this program is:: +The result of this program is:: Searched parameters = [1000, 100.0, 250, 255, 690.0, 45, 1500, 800.5, 95] Obtained parameters : @@ -348,8 +344,8 @@ And the result of this program is:: ('FWHM3', ' = ', 95.000000000000014) ('chisq = ', 0.0) -In addition to gaussians, we could have fitted several other similar type of -functions: asymetric gaussian functions, lorentzian functions, +In addition to Gaussians, we could have fitted several other similar types of +function: asymmetric Gaussian functions, Lorentzian functions, pseudo-voigt functions or hypermet tailing functions. The :meth:`loadtheories` method can also be used to load user defined @@ -358,13 +354,13 @@ as a parameter. This source file must adhere to certain conventions, as explaine in the documentation of :mod:`silx.math.fit.fittheories` and :mod:`silx.math.fit.fittheory.FitTheory`. -Subtracting a background -************************ +Background subtraction +********************** :class:`FitManager` provides a few standard background theories, for cases when a background signal is superimposed on the multi-peak spectrum. -For example, let's add a linear background to our synthetic data, and see how +For instance, let's add a linear background to our synthetic data, and see how :class:`FitManager` handles the fitting. In our previous example, redefine ``y`` as follows: @@ -410,10 +406,11 @@ signal is significantly smoother than the actual signal, it can be easily computed. The main parameters required by the strip function are the strip width *w* -and the number of iterations. At each iteration, if the contents of channel *i*, -``y(i)``, is above the average of the contents of the channels at *w* channels of -distance, ``y(i-w)`` and ``y(i+w)``, ``y(i)`` is replaced by the average. -At the end of the process we are left with something that resembles a spectrum +and the number of iterations. At each iteration, if the content of channel *i*, +``y(i)`` is above the average of the contents of the channels at a distance *w* +(measured in channel units) i.e., ``y(i-w)`` and ``y(i+w)``, +``y(i)`` is replaced by the average value of the neighbouring channels. +At the end of the process one is left with something that resembles a spectrum in which the peaks have been "stripped". The following example illustrates the strip background removal process: @@ -470,11 +467,11 @@ The following example illustrates the strip background removal process: - Data with background in black (``y``), actual background in red, computed strip background in green * - |imgStrip2| - - Data with background in blue, data after subtracting strip background in black + - Data with background in blue, data after subtracting the strip background in black The strip also removes the statistical noise, so the computed strip background will be slightly lower than the actual background. This can be solved by -performing a smoothing prior to the strip computation. +smoothing the data prior to the strip computation. See the `PyMca documentation `_ for more information on the strip background. @@ -482,15 +479,15 @@ for more information on the strip background. To configure the strip background model of :class:`FitManager`, use its :meth:`configure` method to modify the following parameters: - - *StripWidth*: strip width parameter *w*, mentionned earlier + - *StripWidth*: strip width parameter *w*, mentioned earlier - *StripNIterations*: number of iterations - *StripThresholdFactor*: if this parameter is left to its default value 1, the algorithm behaves as explained earlier: ``y(i)`` is compared to the average of ``y(i-1)`` and ``y(i+1)``. - If this factor is set to another value *f*, ``y(i)`` is compared to the - average multiplied by ``f``. - - *SmoothStrip*: if this parameter is set to ``True``, a smoothing is applied - prior to the strip. + If this factor is set to another value, *f*, ``y(i)`` is compared to the + average multiplied by *f*. + - *SmoothStrip*: if this parameter is set to ``True``, smoothing is performed + prior to stripping. These parameters can be modified like this: @@ -520,7 +517,7 @@ Using :class:`FitWidget` Simple usage ************ -The :class:`FitWidget` is a graphical interface for :class:`FitManager`. +:class:`FitWidget` is a graphical interface to :class:`FitManager`. .. code-block:: python @@ -570,9 +567,9 @@ Executing this code opens the following widget. |imgFitWidget1| -The functions you can choose from are the standard gaussian-shaped functions +The functions you can choose from the widget are the standard Gaussian-shaped functions from :mod:`silx.math.fit.fittheories`. At the top of the list, you will find -the *Add Function(s)* option, that allows you to load your user defined fit +the *Add Function(s)* option, that allows loading the user defined fit theories from a *.py* source file. After selecting the *Constant* background model and clicking the *Estimate* @@ -580,41 +577,40 @@ button, the widget displays this: |imgFitWidget2| -The 7 peaks have been detected, and their parameters estimated. -Also, the estimation function defined some constraints (positive height and -positive full-width at half-maximum). +7 peaks have been detected, and their parameters estimated. +Also, the estimation function defines some constraints (positive height and full-width at half-maximum). You can modify the values in the estimation column of the table, to use different -initial parameters for the fit. +initial fit parameters. The individual constraints can be modified prior to fitting. It is also possible to -modify the constraints globally by clicking the *Configure* button' to open a +modify the constraints globally by clicking the *Configure* button to open a configuration dialog. To get help on the meaning of the various parameters, -hover the mouse on the corresponding check box or entry widget, to display a +hover the mouse cursor on the corresponding check box or entry widget to display a tooltip help message. |imgFitWidget3| The other configuration tabs can be modified to change the peak search parameters -and the strip background parameters prior to the estimation. +and the strip background parameters prior to estimating them. After closing the configuration dialog, you must re-run the estimation by clicking the *Estimate* button. -After all configuration parameters and all constrants are set according to your +After all configuration parameters and all constraints are set according to your preferences, you can click the *Start Fit* button. This runs the fit and displays the results in the *Fit Value* column of the table. |imgFitWidget4| -Customizing the functions +Customising the functions ************************* .. |imgFitWidget5| image:: img/fitwidget5.png :width: 300px :align: middle -The :class:`FitWidget` can be initialized with a non-standard -:class:`FitManager`, to customize the available functions. +The :class:`FitWidget` can be initialised with a non-standard +:class:`FitManager` to customise the available functions. .. code-block:: python @@ -644,16 +640,16 @@ The :class:`FitWidget` can be initialized with a non-standard a.exec_() -In our previous example, we didn't load a custom :class:`FitManager`. -Therefore, the fit widget automatically initialized a fit manager and -loaded the custom gaussian functions. +In our previous example, we didn't load a customised :class:`FitManager`, +therefore, the fit widget automatically initialised the default fit manager and +loaded the default custom Gaussian functions. -This time, we initialized our own :class:`FitManager` and loaded our +This time, we initialised our own :class:`FitManager` and loaded our own function, so only this function is presented as an option in the GUI. Our custom function does not provide an associated estimation function, so -the default estimation function of :class:`FitManager` was used. This -default estimation function returns an array of ones the same length as the -list of *parameter* names, and set all constraints to *FREE*. +the default estimation function of the :class:`FitManager` is used. This +default estimation function returns an array of ones with the same length as the +list of *parameter* names, and sets all constraints to *FREE*. |imgFitWidget5| diff --git a/doc/source/Tutorials/io.rst b/doc/source/Tutorials/io.rst index 369e5ad..8b70e83 100644 --- a/doc/source/Tutorials/io.rst +++ b/doc/source/Tutorials/io.rst @@ -22,19 +22,16 @@ Background ---------- In the past, it was necessary to learn how to use multiple libraries to read multiple -data formats. The library *FabIO* was designed to read images in many formats, but not to read +data formats. The library *FabIO* was designed to read in images in many formats, but not to read in more heterogeneous formats, such as *HDF5* or *SPEC*. To read *SPEC* data files in Python, a common solution was to use the *PyMca* module :mod:`PyMca5.PyMcaIO.specfilewrapper`. -Regarding HDF5 files, the de-facto standard for reading them in Python is to -use the *h5py* library. +Regarding HDF5 files, the de-facto standard for reading them in Python is the *h5py* library. -*silx* tries to address this situation by providing a unified way to read all -data formats supported at the ESRF. -Today, HDF5 is the preffered format to store -data for many scientific institutions, including most synchrotrons. -So it was decided to provide tools for reading data that mimic the *h5py* library's API. +*silx* tries to provide a unified way to read all data formats supported at the ESRF. +Today, HDF5 is the preferred format to store data for many scientific institutions, including most synchrotrons. +Hence, it was decided to provide tools for reading data that mimic the *h5py* library's API. Definitions @@ -44,9 +41,9 @@ HDF5 ++++ The *HDF5* format is a *hierarchical data format*, designed to store and -organize large amounts of data. +organise large amounts of data. -A HDF5 file contains a number of *datasets*, which are multidimensional arrays +An HDF5 file contains a number of *datasets*, which are multidimensional arrays of a homogeneous type. These datasets are stored in container structures @@ -54,10 +51,11 @@ called *groups*. Groups can also be stored in other groups, allowing to define a hierarchical tree structure. Both datasets and groups may have *attributes* attached to them. Attributes are -used to document the object. They are similar to datasets in several ways -(data container of homogeneous type), but they are typically much smaller. +used to document an object. Attributes are similar to datasets in several respects +(data containers of homogeneous type), but there sizes are typically much smaller +than the object data themselves. -It is a common analogy to compare a HDF5 file to a filesystem. +It is quite common to compare an HDF5 file to a filesystem. Groups are analogous to directories, while datasets are analogous to files, and attributes are analogous to file metadata (creation date, last modification...). @@ -68,16 +66,16 @@ and attributes are analogous to file metadata (creation date, last modification. h5py ++++ -The *h5py* library is a Pythonic interface to the `HDF5`_ binary data format. +The *h5py* library is a pythonic interface to the `HDF5`_ binary data format. It exposes an HDF5 group as a python object that resembles a python -dictionary, and an HDF5 dataset or attribute as an object that resembles a +dictionary and an HDF5 dataset or attribute as an object that resembles a numpy array. API description --------------- -All main objects, File, Group and Dataset, share the following attributes: +All main objects, i.e., File, Group and Dataset, share the following attributes: - :attr:`attrs`: Attributes, as a dictionary of metadata for the group or dataset. - :attr:`basename`: String giving the basename of this group or dataset. @@ -94,12 +92,12 @@ The API of the file objects returned by the :meth:`silx.io.open` function tries to be as close as possible to the API of the :class:`h5py.File` objects used to read HDF5 data. -A h5py file is a group with just a few extra attributes and methods. +An h5py file is a group with just a few extra attributes and methods. The objects defined in `silx.io` implement a subset of these attributes and methods: - :attr:`filename`: Name of the file on disk. - - :attr:`mode`: String indicating if the file is open in read mode ("r") + - :attr:`mode`: String indicating whether the file is open in read mode ("r") or write mode ("w"). :meth:`silx.io.open` always returns objects in read mode. - :meth:`close`: Close this file. All child objects, groups and datasets, will become invalid. @@ -110,7 +108,7 @@ Group object Group objects behave like python dictionaries. -You can iterate over a group's :meth:`keys`, which are the names of the objects +One can iterate over group's :meth:`keys`, that are the names of the objects encapsulated by the group (datasets and sub-groups). The :meth:`values` method returns an iterator over the encapsulated objects. The :meth:`items` method returns an iterator over `(name, value)` pairs. @@ -131,7 +129,7 @@ Example Accessing data ++++++++++++++ -In this first example, we open a Spec data file and we print some of its information. +In this first example below, we open a Spec data file and print some pieces of its information. .. code-block:: python @@ -144,9 +142,9 @@ In this first example, we open a Spec data file and we print some of its informa -We just opened a file, keeping a reference to the file object as ``sf``. -We then printed all items contained in this root group. We can see that all -these items are groups. Lets looks at what is inside these groups, and find +We opened a file, keeping a reference to the file object as ``sf``. +We then printed all items contained in the root group. We can see that all +these items are groups. Let us look at what is inside these groups, and find datasets: @@ -176,8 +174,8 @@ datasets: Found item sample sample is a group. -We could have replaced the first three lines with this single line, -by iterating over the iterator returned by the group method :meth:`items`: +We could have replaced the first three lines by the following single line, +using the iterator returned by the group method :meth:`items`: .. code-block:: python @@ -196,7 +194,7 @@ Let's look at a dataset: As you can see, printing a dataset does not print the data itself, it only print a -representation of the dataset object. The information printed tells us that the +representation of the dataset object. The printed information tells that the object is similar to a numpy array, with a *shape* and a *type*. In this case, we are dealing with a scalar dataset, so we can use the same syntax as @@ -207,7 +205,7 @@ in numpy to access the scalar value, ``result = dset[()]``: >>> print(sf["2.1/title"][()]) 2 ascan phi 0.61 1.61 20 1 -Similarly, you need to use numpy slicing to access values in numeric array: +Similarly, you need to use numpy slicing to access values in a numeric array: .. code-block:: python @@ -219,7 +217,7 @@ Similarly, you need to use numpy slicing to access values in numeric array: >>> entire_phi_array = sf["2.1/measurement/Phi"][:] Here we could read the entire array by slicing it with ``[:]``, because we know -it is a 1D array. For a 2D array, the slicing argument would have been ``[:, :]``. +it is a 1D array. For a 2D array, the slicing argument would be ``[:, :]``. For a dataset of unknown dimensionality (including scalar datasets), the ``Ellipsis`` object (represented by ``...``) can be used to slice the object. @@ -235,12 +233,12 @@ For a dataset of unknown dimensionality (including scalar datasets), the 1.50999999 1.55999994 1.61000001] To read more about the usage of ``Ellipsis`` to slice arrays, see -`Indexing numpy arrays `_ +`"Indexing numpy arrays" `_ in the scipy documentation. -Note that slicing a scalar dataset with ``[()]`` is not strictly equivalent to -slicing with ``[...]``. The former gives you the actual scalar value in -the dataset, while the latter always gives you an array object, which happens to +Note that slicing a scalar dataset via ``[()]`` is not strictly equivalent to +slicing via ``[...]``. The former returns the actual scalar value in +the dataset, while the latter always returns an array object, which happens to be 0D in the case of a scalar. >>> sf["2.1/instrument/positioners/Delta"][()] @@ -251,7 +249,7 @@ be 0D in the case of a scalar. Closing the file ++++++++++++++++ -You should always make sure to close the files that you opened. The simple way of +You should always make sure to close the files that you opened. The simplest way of closing a file is to call its :meth:`close` method. .. code-block:: python @@ -264,12 +262,12 @@ closing a file is to call its :meth:`close` method. sf.close() -The drawback of this method is that, if an error is raised while processing +The drawback of this method is that, if an error is arising while processing the file, the program might never reach the ``sf.close()`` line. -Leaving files open can cause various issues for the rest of your program, +Leaving files open can cause various issues to the rest of your program, such as consuming memory, not being able to reopen the file when you need it... -The best way to ensure the file is always properly closed is to use the file +The best way to ensure that the file is always properly closed is to use the file inside its context manager: .. code-block:: python @@ -286,5 +284,5 @@ Additional resources - `h5py documentation `_ - `Formats supported by FabIO `_ -- `Spec file h5py-like structure `_ +- `Spec file with h5py-like structure `_ - `HDF5 format documentation `_ diff --git a/doc/source/Tutorials/specfile_to_hdf5.rst b/doc/source/Tutorials/specfile_to_hdf5.rst index 5a5f0a5..db2253f 100644 --- a/doc/source/Tutorials/specfile_to_hdf5.rst +++ b/doc/source/Tutorials/specfile_to_hdf5.rst @@ -6,7 +6,7 @@ Introduction to SPEC data files ------------------------------- SPEC data files are ASCII files. -They contain two general types of block of lines: +They contain two general types of line blocks: - header lines starting with a ``#`` immediately followed by one or more characters identifying the information that follows @@ -17,9 +17,9 @@ Header lines There are two types of headers. The first type is the *file header*. File headers always start with a ``#F`` line. -The metadata stored in a file header applies to all the content of the data file, until a +The metadata stored in a file header refers to all the content of the data file, until a new file header is encountered. There can be more than one file header, but a file with -multiple headers can be treated as multiple SPEC files concatenated into a single one. +multiple headers can be treated as a set of multiple SPEC files concatenated into a single one. File headers are sometimes missing. A file header contains general information: @@ -33,15 +33,15 @@ A file header contains general information: The second type of header is the *scan header*. A scan header must start with a ``#S`` line and must be preceded by an empty line. This also applies to files without file headers: in such a case, the file must start with an empty line. -The metadata stored in scan headers applies to a single block of data lines. +The metadata stored in scan headers refers to a single block of data lines. -A scan header contains following information: +A scan header contains the following information: - ``#S`` - Mandatory first line showing the scan number and the command that was used to record the scan - ``#D`` - scan time and date - ``#Q`` - *H, K, L* values - - ``#P`` - Motor positions (corresponding motor names are in file header ``#O``) + - ``#P`` - Motor positions (the corresponding motor names are in the file header ``#O``) - ``#N`` - Number of data columns in the following data block - ``#L`` - Column labels (``#N`` labels separated by two blank spaces) @@ -57,20 +57,20 @@ Data blocks are structured as 2D arrays. Each line contains ``#N`` values, each corresponding to the label with the same position in the ``#L`` scan header line. This implies that each column corresponds to one series of measurements. -A column typically contains motor positions for a given positioner, a timestamp or the measurement +A column typically includes motor positions for a given positioner, a timestamp or the measurement of a sensor. MCA data ++++++++ -Newer SPEC files can also contain multi-channel analyser data, in between each *normal* data line. +More recent SPEC files can also comprise multi-channel analyser data, between *normal* data lines. A multichannel analyser records multiple values per single measurement. -This is typically a histogram of number of counts against channels (*MCA spectrum*), to analyze energy distribution +This is typically a histogram of number of counts against channels (*MCA spectrum*), to analyse the energy distribution of a process. SPEC data files containing MCA data have additional scan header lines: - - ``#@MCA %16C`` - a spectrum will usually extend for more than one line. + - ``#@MCA %16C`` - a spectrum will usually extend over more than one line. This indicates a number of 16 values per line. - ``#@CHANN`` - contains 4 values: @@ -79,14 +79,14 @@ SPEC data files containing MCA data have additional scan header lines: - the last channel number - the increment between two channel numbers (usually 1) - ``#@CALIB`` - 3 polynomial calibration values a, b, c. ( i.e. energy = a + b * channel + c * channel ^ 2) - - ``#@CTIME`` - 3 values: preset time, live time, elapsed time + - ``#@CTIME`` - 3 values: preset time, life time, elapsed time The actual MCA data for a single spectrum usually spans over multiple lines. -A spectrum starts on a new line with a ``@A``, and when it span over multiple lines, all -lines except the last one end with a continuation character ``\``. +A spectrum starts on a new line with ``@A``, and when it spans over multiple lines, all +lines except the last one end by a continuation character ``\``. -Example of SPEC file -++++++++++++++++++++ +Example of SPEC files ++++++++++++++++++++++ Example of file header:: @@ -126,7 +126,7 @@ Example of scan and data block, without MCA:: 29.366004 45256 0.000343 734 419 248 229 236 343 178082 664 0.00372862 0.00765939 0.0041217 0.00235285 0.00185308 0.00139262 0.00128592 0.00132523 0.00192608 1364 330 29.36998 45258 0.00036 847 448 254 229 248 360 178342 668 0.00374561 0.00857342 0.0047493 0.00251203 0.00194009 0.00142423 0.00128405 0.00139059 0.00201859 1529 346 -Synthetic example of file with 3 scans. The last scan includes MCA data. +Synthetic example of a file with 3 scans. The last scan includes MCA data. :: @@ -230,30 +230,30 @@ The structure exposed is as follows:: … Scans appear as *Groups* at the root level. The name of a scan group is -made of two numbers, the first one being the *scan number* from the ``#S`` +composed of two numbers, the first one being the *scan number* from the ``#S`` header line, and the second one being the *scan order*. -If a scan number appears multiple times in a SPEC file, the scan order is incremented. -For examples, the scan *3.2* designates the second occurence of scan number 3 in a given file. +If a scan number appears multiple times in a SPEC file, the scan order is incremented by one. +For example, the scan *3.2* designates the second occurence of scan number 3 in a given file. Data is stored in the ``measurement`` subgroup, one dataset per column. The dataset name -is the column label as it appears on the ``#L`` header line. +is the column label as it appears in the ``#L`` header line. The ``instrument`` subgroup contains following subgroups: - ``specfile`` - contains two datasets, ``file_header`` and ``scan_header``, containing all header lines as a long string. Lines are delimited by the ``\n`` character. - - ``positioners`` - contains one dataset per motor (positioner), containing - either the single motor position from the ``#P`` header line, or a complete 1D array - of positions if the motor names corresponds to a data column (i.e. if the motor name - from the ``#O`` header line is identical to a label on the ``#L`` header line) + - ``positioners`` - contains one dataset per motor (positioner), including + either the single motor position from the ``#P`` header line or a complete 1D array + of positions if the motor names correspond to a data column (i.e. if the motor name + from the ``#O`` header line is identical to a label in the ``#L`` header line) - one subgroup per MCA analyser/device containing a 2D ``data`` array with all spectra recorded by this analyser, as well as datasets for the various MCA metadata (``#@`` header lines). The first dimension of the ``data`` array corresponds to the number of points and the second one to the spectrum length. -In addition the the data columns, this group contains one subgroup per MCA analyser/device -with links to the data already contained in ``instrument/mca_...`` +In addition to the data columns, this group contains one subgroup per MCA analyser/device +with links to the data already comprised in ``instrument/mca_...`` spech5 examples +++++++++++++++ @@ -282,7 +282,7 @@ Accessing groups and datasets: mca_0_spectra = measurement_group["mca_0/data"] -Files and groups can be treated as iterators, which allows looping through them. +Files and groups can be treated as iterators, allowing looping through them. .. code-block:: python @@ -296,8 +296,8 @@ Files and groups can be treated as iterators, which allows looping through them. .. note:: - A :class:`SpecH5` object is also returned when you open a SPEC file - with :meth:`silx.io.open`. See :doc:`io` for additional information. + A :class:`SpecH5` object is also returned when one open a SPEC file + through :meth:`silx.io.open`. See ":doc:`io`" for additional information. Converting SPEC data to HDF5 ++++++++++++++++++++++++++++ diff --git a/doc/source/Tutorials/writing_NXdata.rst b/doc/source/Tutorials/writing_NXdata.rst new file mode 100644 index 0000000..1c65199 --- /dev/null +++ b/doc/source/Tutorials/writing_NXdata.rst @@ -0,0 +1,357 @@ + +Writing NXdata +============== + +This tutorial explains how to write a *NXdata* group into a HDF5 file. + +A basic knowledge of the HDF5 file format, including understanding +the concepts of *group*, *dataset* and *attribute*, +is a prerequisite for this tutorial. You should also be able to read +a python script using the *h5py* library to write HDF5 data. +You can find some information on these topics at the beginning of the +:doc:`io` tutorial. + +Definitions +----------- + +NeXus Data Format ++++++++++++++++++ + +NeXus is a common data format for neutron, x-ray, and muon science. +It is being developed as an international standard by scientists and programmers +representing major scientific facilities in order to facilitate greater +cooperation in the analysis and visualization of neutron, x-ray, and muon data. + +It uses the HDF5 format, adding additional rules and structure to help +people and software understand how to read a data file. + +The name of a group in a NeXus data file can be any string of characters, +but it must have a `NX_class` attribute defining a +`*class type* `_. + +Examples of such classes are: + + - *NXroot*: root group of the file (may be implicit, if the `NX_class` attribute is omitted) + - *NXentry*: describes a measurement; it is mandatory that there is at least one + group of this type in the NeXus file + - *NXsample*: contains information pertaining to the sample, such as its chemical composition, + mass, and environment variables (temperature, pressure, magnetic field, etc.) + - *NXinstrument*: encapsulates all the instrumental information that might be relevant to a measurement + - *NXdata*: describes the plottable data and related dimension scales + +You can find all the specifications about the NeXus format on the +`nexusformat.org website `_. The rest of this tutorial will +focus exclusively on *NXdata*. + +NXdata groups ++++++++++++++ + +NXdata describes the plottable data and related dimension scales. + +It is mandatory that there is at least one NXdata group in each NXentry group. +Note that the variable and data can be defined with different names. +The `signal` and `axes` attributes of the group define which items +are plottable data and which are dimension scales, respectively. + +In the case of a curve, for instance, you would have a 1D signal +dataset (*y* values) and optionally another 1D signal of identical +size as axis (*x* values). In the case of an image, you would have +a 2D dataset as signal and optionally two 1D datasets to scale +the X and Y axes. + +A NXdata group should define all the information needed to +provide a sensible plot, including axis labels and a plot title. +It can also include additional metadata such as standard deviations +of data values, or errors an axes. + +.. note:: + + + The NXdata specification evolved slightly over the course of time. + The `complete documentation for the *NXdata* class + `_ mentions + older rules that you will probably have to take into account + if you intend to write a program that reads NeXus files. + + If you only need to write such files and only need to read back files + you have yourself written, you should adhere to the most recent rules. + We will only mention these most recent specifications in this tutorial. + +Main elements in a NXdata group +------------------------------- + +Signal +++++++ + +The `@signal` attribute of the NXdata group provides the name of a dataset +containing the plottable data. The name of this dataset can be freely chosen +by the writer. + +This signal dataset may have a `@long_name` attribute, that can be used as +an axis label (e.g. for the Y axis of a curve) or a plot title (e.g. for an image). + +Axes +++++ + +The `@axes` attributes of the NXdata group provides a list of names of datasets +to be used as *dimension scales*. The number of axes in this list +should match the number of dimensions of the signal data, in the general case. +But in some specific cases, such as scatter plots or stack of images or curves, +the number of axes may differ from the number of signal dimensions. + +An axis should be a 1D dataset, whose length matches the size of the corresponding +signal dimension. + +Silx supports also an axis being a dataset with 2 values :math:`(a, b)`. +In such a case, it is interpreted as an affine scaling of the indices +(:math:`i \mapsto a + i * b`). + +An axis dataset may have a `@long_name` attribute, that can be used as +an axis label. + +An axis dataset may also define a `@first_good` and `@last_good` attribute. +These can be used to define a range of indices to be considered valid values +in the axis. + +The name of the dataset can be freely chosen by the writer. + +An axis may be omitted for one or more dimensions of the signal. In this +case, a `"."` should be written in place of the dataset name in the +list of axes names. + + +Signal errors ++++++++++++++ + +A dataset named `errors` can be present in a NXdata group. It provides +the standard deviation of data values. This dataset must have the same +shape as the signal dataset. + +Axes errors ++++++++++++ + +An axis may have associated errors (uncertainties). These axis errors +must be provided in a dataset whose name is the axis name with `_errors` +appended to it. + +For instance, an axis whose dataset name is `pressure` may provide errors +in an another dataset whose name is `pressure_errors`. + +This dataset must have the same size as the corresponding axis. + +Interpretation +++++++++++++++ + +Silx supports an attribute `@interpretation` attached to the signal dataset. +The supported values for this attribute are `scalar`, `spectrum` or `image`. + +This attribute must be provided when the number of axes is lower than the +number of signal dimensions. For instance, a 3D signal with +`@interpretation="image"` is interpreted as a stack of images. +The axes always apply to the last dimensions of the signal, so in this example +of a 3D stack of images, the first dimension is not scaled and is interpreted as +a *frame number*. + +.. note:: + + This additional attribute is not mentionned in the official NXdata + specification. + + +Writing NXdata with h5py +------------------------ + +The following examples explain how to write NXdata directly using +the *h5py* library. + +.. note:: + + All following examples should be preceded by: + + .. code-block:: python + + import h5py + import numpy + import sys + + # this is needed for writing arrays of utf-8 strings with h5py + if sys.version_info < (3,): + text_dtype = h5py.special_dtype(vlen=unicode) + else: + text_dtype = h5py.special_dtype(vlen=str) + + filename = "./myfile.h5" + h5f = h5py.File(filename, "w") + entry = h5f.create_group("my_entry") + entry.attrs["NX_class"] = "NXentry" + +A simple curve +++++++++++++++ + +The simplest NXdata example would be a 1D signal to be plotted as a curve. + + +.. code-block:: python + + nxdata = entry.create_group("my_curve") + nxdata.attrs["NX_class"] = "NXdata" + nxdata.attrs["signal"] = numpy.array("y", dtype=text_dtype) + ds = nxdata.create_dataset("y", + data=numpy.array([0.1, 0.2, 0.15, 0.44])) + ds.attrs["long_name"] = numpy.array("ordinate", dtype=text_dtype) + +To add an axis: + +.. code-block:: python + + nxdata.attrs["axes"] = numpy.array(["x"], + dtype=text_dtype) + ds = nxdata.create_dataset("x", + data=numpy.array([101.1, 101.2, 101.3, 101.4])) + ds.attrs["long_name"] = numpy.array("abscissa", dtype=text_dtype) + + +A scatter plot +++++++++++++++ + +A scatter plot is the only case for which we can have more axes than +there are signal dimensions. The signal is 1D, and there can be any +number of axes with the same number of values as the signal. + +But the most common case is a 2D scatter plot, with a signal and +two axes. + + +.. code-block:: python + + nxdata = entry.create_group("my_scatter") + nxdata.attrs["NX_class"] = "NXdata" + nxdata.attrs["signal"] = numpy.array("values", + dtype=text_dtype) + nxdata.attrs["axes"] = numpy.array(["x", "y"], + dtype=text_dtype) + nxdata.create_dataset("values", + data=numpy.array([0.1, 0.2, 0.15, 0.44])) + nxdata.create_dataset("x", + data=numpy.array([101.1, 101.2, 101.3, 101.4])) + nxdata.create_dataset("y", + data=numpy.array([2, 4, 6, 8])) + +A stack of images ++++++++++++++++++ + +The following examples illustrates how to use the `@interpretation` +attribute to define only two axes for a 3D signal. The first +dimension of the signal is considered a frame index and is not scaled. + + +.. code-block:: python + + nxdata = entry.create_group("images") + nxdata.attrs["NX_class"] = "NXdata" + nxdata.attrs["signal"] = numpy.array("frames", + dtype=text_dtype) + nxdata.attrs["axes"] = numpy.array(["y", "x"], + dtype=text_dtype) + # 2 frames of size 3 rows x 4 columns + signal = nxdata.create_dataset( + "frames", + data=numpy.array([[[1., 1.1, 1.2, 1.3], + [1.4, 1.5, 1.6, 1.7], + [1.8, 1.9, 2.0, 2.1]], + [[8., 8.1, 8.2, 8.3], + [8.4, 8.5, 8.6, 8.7], + [8.8, 8.9, 9.0, 9.1]]])) + signal.attrs["interpretation"] = "image" + nxdata.create_dataset("x", + data=numpy.array([0.1, 0.2, 0.3, 0.4])) + nxdata.create_dataset("y", + data=numpy.array([2, 4, 6])) + + +Writing NXdata with silx +------------------------ + +*silx* provides a convenience function to write NXdata groups: +:func:`silx.io.nxdata.save_NXdata` + +The following examples show how to reproduce the previous examples +using this function. + + +A simple curve +++++++++++++++ + +To get exactly the same output as previously, you can specify all attributes +like this: + +.. code-block:: python + + import numpy + from silx.io.nxdata import save_NXdata + + save_NXdata(filename="./myfile.h5", + signal=numpy.array([0.1, 0.2, 0.15, 0.44]), + signal_name="y", + signal_long_name="ordinate", + axes=[numpy.array([101.1, 101.2, 101.3, 101.4])], + axes_names=["x"], + axes_long_names=["abscissa"], + nxentry_name="my_entry", + nxdata_name="my_curve") + +Most of these parameters are optional, only *filename* and *signal* +are mandatory parameters. Omitted parameters have default values. + +If you do not care about the names of the entry, NXdata and of all the +datasets, you can simply write: + +.. code-block:: python + + import numpy + from silx.io.nxdata import save_NXdata + + save_NXdata(filename="./myfile.h5", + signal=numpy.array([0.1, 0.2, 0.15, 0.44]), + axes=[numpy.array([101.1, 101.2, 101.3, 101.4])]) + +A scatter plot +++++++++++++++ + +.. code-block:: python + + import numpy + from silx.io.nxdata import save_NXdata + + save_NXdata(filename="./myfile.h5", + signal=numpy.array([0.1, 0.2, 0.15, 0.44]), + signal_name="values", + axes=[numpy.array([2, 4, 6, 8]), + numpy.array([101.1, 101.2, 101.3, 101.4])], + axes_names=["x", "y"], + nxentry_name="my_entry", + nxdata_name="my_scatter") + + +A stack of images ++++++++++++++++++ + +.. code-block:: python + + import numpy + from silx.io.nxdata import save_NXdata + + save_NXdata(filename="./myfile.h5", + signal=numpy.array([[[1., 1.1, 1.2, 1.3], + [1.4, 1.5, 1.6, 1.7], + [1.8, 1.9, 2.0, 2.1]], + [[8., 8.1, 8.2, 8.3], + [8.4, 8.5, 8.6, 8.7], + [8.8, 8.9, 9.0, 9.1]]]), + signal_name="frames", + interpretation="image", + axes=[numpy.array([2, 4, 6]), + numpy.array([0.1, 0.2, 0.3, 0.4])], + axes_names=["y", "x"], + nxentry_name="my_entry", + nxdata_name="images") diff --git a/doc/source/developers.rst b/doc/source/developers.rst new file mode 100644 index 0000000..e1122b6 --- /dev/null +++ b/doc/source/developers.rst @@ -0,0 +1,98 @@ +Developers documentation +======================== + +Development process +------------------- + +silx follows a peer-review development process based on the `github flow`_ +(See `scikit-image contribution documentation`_). + +General guidelines +------------------ + +See `scikit-image guidelines`_. + +Coding convention +----------------- + +silx follows `PEP 8`_ keeping in mind its recommendation: + +.. container:: + + A style guide is about consistency. + Consistency with this style guide is important. + Consistency within a project is more important. + Consistency within one module or function is the most important. + + However, know when to be inconsistent -- sometimes style guide recommendations just aren't applicable. + When in doubt, use your best judgment. + Look at other examples and decide what looks best. + And don't hesitate to ask! + + In particular: do not break backwards compatibility just to comply with this PEP! + +Therefore in GUI with inheritance from Qt, camelCase will be the preferred coding style. +External modules integrated in the library will follow the coding style of the external module. + +- Whenever possible refer to array as row, column not x, y + (See `scikit-image coordinate conventions`_) + This might not be possible in the GUI part where coordinate convention is x, y. + +``silx.gui`` coding convention +------------------------------ + +This package mostly contains objects inherited from Qt, thus it follows Qt style. +But the way to describe a ``QObject`` in Python (with getter, setter, properties) does not allow to respect it. +Here are some recommendations: + +- Follow the Qt style as much as possible + + - That is particularly important for the public API + - CamelCase for all the package + - For widget constructor + + - ``parent=None`` as first argument, and no other mandatory arguments + (it is needed to promote widget with Qt designer). + - Avoid as much as possible other arguments + + - Avoid Python properties or attributes, use ``value()`` instead of ``value`` + - Prefer prefixing getter and setter with ``get``, ``is``, ``set``... + (it avoid hiding getter when we also define a Qt property). + - Lower case or camel case but no ``_`` for arguments + (lower case is inherited from matplotlib conventions) + - Signals can be prefixed by ``sig`` + (``sig`` allows to identify and list signals easily) + - Manipulate as much as possible Qt style objects + +- Do not use right-click for interaction purpose, keep it for context menu. + + +How-to add icons +================ + +silx icons are stored in ``silx/resources/gui/icons/``. +Icons should be provided as both a SVG file and a 32x32 PNG file. + +Animated icons should be provided as both a MNG file and a folder with the same name containing a serie of PNG files with number as filename: 00.png, 01.png, ... + +For maximum compatibility, here are a few recommendation to produce SVG icons: + +- It should be SVG 1.1. +- It should not contain raster images (even embedded). + This makes files smaller and does not rely on a decompression library (it occured that libjpeg was not available) +- The text should use a free font. +- The text should be converted to paths. + As its font might not be available everywhere. + +Steps to create an icon: + +- Create an icon with `inkscape `_ (or another authoring tool), save it as SVG and export it as a 32x32 PNG file. +- Then, optimize the SVG file with `scour `_:: + + scour -i input.svg -o output.svg --enable-viewboxing --enable-id-stripping --enable-comment-stripping --shorten-ids --indent=none --remove-metadata + + .. warning:: By default all icons are copyrighted ESRF and available under the MIT license. + If an icon has a different copyright/license, then provide the copyright/license in the SVG file and optimize it without the ``--remove-metadata`` option. + Also update the ``copyright`` file at the root of the project. +- Update the icons gallery in the documentation by running the ``doc/source/modules/gui/update_icons_rst.py`` script. + diff --git a/doc/source/ext/snapshotqt_directive.py b/doc/source/ext/snapshotqt_directive.py new file mode 100644 index 0000000..1cd8dbe --- /dev/null +++ b/doc/source/ext/snapshotqt_directive.py @@ -0,0 +1,257 @@ +"""RST directive to include snapshot of a Qt application in Sphinx doc. + +Configuration variable in conf.py: + +- snapshotqt_image_type: image file extension (default 'png'). +- snapshotqt_script_dir: relative path of the root directory for scripts from + the documentation source directory (i.e., the directory of conf.py) + (default: '..'). +""" + +from __future__ import absolute_import + +import logging +import os +import subprocess +import sys + +from docutils.parsers.rst.directives.images import Figure + + +logging.basicConfig() +_logger = logging.getLogger(__name__) + +# TODO: +# - Create image in build directory +# - Check if it is needed to patch block_text? + +# RST directive ############################################################### + +class SnapshotQtDirective(Figure): + """Figure of a Qt application snapshot. + + Directive Type: "snapshotqt" + Doctree Elements: As for figure + Directive Arguments: One or more, required (script URI + script arguments). + Directive Options: Possible. + Directive Content: Interpreted as the figure caption and optional legend. + + A "snapshotqt" is a rst `figure + `_ + that is generated from a Python script that uses Qt. + + The path of the script to take a snapshot is relative to + the path given in conf.py 'snapshotqt_script_dir' value. + + :: + + .. snapshotqt: ../examples/demo.py + :align: center + :height: 5cm + + Caption of the figure. + """ + + # TODO this should be configured in conf.py + IMAGE_RELATIVE_DIR = os.path.join('images') + """The path where to store images relative to doc directory.""" + + def run(self): + # Run script stored in arguments and replace by snapshot filename + + env = self.state.document.settings.env + + # Create an image filename from arguments + image_ext = env.config.snapshotqt_image_type.lower() + image_name = '_'.join(self.arguments) + '.' + image_ext + image_name = image_name.replace('./\\', '_') + image_name = ''.join([c for c in image_name + if c.isalnum() or c in '_-.']) + image_name = os.path.join(self.IMAGE_RELATIVE_DIR, image_name) + + # Change path to absolute path to run the script + script_dir = os.path.join(env.srcdir, env.config.snapshotqt_script_dir) + script_cmd = self.arguments[:] + script_cmd[0] = os.path.join(script_dir, script_cmd[0]) + + # Run snapshot + snapshot_tool = os.path.abspath(__file__) + _logger.info('Running script: %s', script_cmd) + _logger.info('Saving snapshot to: %s', image_name) + abs_image_name = os.path.join(env.srcdir, image_name) + cmd = [sys.executable, snapshot_tool, '--output', abs_image_name] + cmd += script_cmd + subprocess.check_call(cmd) + + # Use created image as in Figure + self.arguments = [image_name] + + return super(SnapshotQtDirective, self).run() + + +def setup(app): + app.add_config_value('snapshotqt_image_type', 'png', 'env') + app.add_config_value('snapshotqt_script_dir', '..', 'env') + + app.add_directive('snapshotqt', SnapshotQtDirective) + + return {'version': '0.1'} + + +# Qt monkey-patch ############################################################# + +def monkeyPatchQApplication(filename, qt=None): + """Monkey-patch QApplication to take a snapshot and close the application. + + :param str filename: The image filename where to save the snapshot. + :param str qt: The Qt binding to patch. + This MUST be the same as the one used by the script + for which to take a snapshot. + In: 'PyQt4', 'PyQt5', 'PySide' or None (the default). + If None, it will try to use PyQt4, then PySide and + finally PyQt4. + """ + + # Probe Qt binding + if qt is None: + try: + import PyQt4.QtCore # noqa + qt = 'PyQt4' + except ImportError: + try: + import PySide.QtCore # noqa + qt = 'PySide' + except ImportError: + try: + import PyQt5.QtCore # noqa + qt = 'PyQt5' + except ImportError: + raise RuntimeError('Cannot find any supported Qt binding.') + + if qt == 'PyQt4': + from PyQt4.QtGui import QApplication, QPixmap + from PyQt4.QtCore import QTimer + import PyQt4.QtGui as _QApplicationPackage + + def grabWindow(winID): + return QPixmap.grabWindow(winID) + + elif qt == 'PyQt5': + from PyQt5.QtGui import QPixmap + from PyQt5.QtWidgets import QApplication + from PyQt5.QtCore import QTimer + import PyQt5.QtWidgets as _QApplicationPackage + + def grabWindow(winID): + screen = QApplication.primaryScreen() + return screen.grabWindow(winID) + + elif qt == 'PySide': + from PySide.QtGui import QApplication, QPixmap + from PySide.QtCore import QTimer + import PySide.QtGui as _QApplicationPackage + + def grabWindow(winID): + return QPixmap.grabWindow(winID) + + else: + raise ValueError('Unsupported Qt binding: %s' % qt) + + _logger.info('Using Qt bindings: %s', qt) + + class _QApplication(QApplication): + + _TIMEOUT = 1000. + _FILENAME = filename + + def _grabActiveWindowAndClose(self): + activeWindow = QApplication.activeWindow() + if activeWindow is not None: + if activeWindow.isVisible(): + pixmap = grabWindow(activeWindow.winId()) + saveOK = pixmap.save(self._FILENAME) + if not saveOK: + _logger.error( + 'Cannot save snapshot to %s', self._FILENAME) + else: + _logger.error('activeWindow is not visible.') + self.quit() + else: + self._count -= 1 + if self._count > 0: + # Only restart a timer if everything is OK + QTimer.singleShot(self._TIMEOUT, + self._grabActiveWindowAndClose) + else: + raise RuntimeError( + 'Aborted: It took too long to have an active window.') + + def exec_(self): + self._count = 10 + QTimer.singleShot(self._TIMEOUT, self._grabActiveWindowAndClose) + + return super(_QApplication, self).exec_() + + _QApplicationPackage.QApplication = _QApplication + + +# main ######################################################################## + +if __name__ == '__main__': + import argparse + import runpy + + # Parse argv + + parser = argparse.ArgumentParser( + description=__doc__, + epilog="""Arguments provided after the script or module name are passed + to the script or module.""") + parser.add_argument( + '-o', '--output', nargs=1, type=str, + default='snapshot.png', + help='Image filename of the snapshot (default: snapshot.png).') + parser.add_argument( + '--bindings', nargs='?', + choices=('PySide', 'PyQt4', 'PyQt5', 'auto'), + default='auto', + help="""Qt bindings used by the script/module. + If 'auto' (the default), it is probed from available python modules. + """) + parser.add_argument( + '-m', '--module', action='store_true', + help='Run the corresponding module as a script.') + parser.add_argument( + 'script_or_module', nargs=1, type=str, + help='Python script to run for the snapshot.') + args, unknown = parser.parse_known_args() + + script_or_module = args.script_or_module[0] + + # arguments provided after the script or module + extra_args = sys.argv[sys.argv.index(script_or_module) + 1:] + + if unknown != extra_args: + parser.print_usage() + _logger.error( + '%s: incorrect arguments', os.path.basename(sys.argv[0])) + sys.exit(1) + + # Monkey-patch Qt + monkeyPatchQApplication(args.output[0], + args.bindings if args.bindings != 'auto' else None) + + # Update sys.argv and sys.path + sys.argv = [script_or_module] + extra_args + sys.path.insert(0, os.path.abspath(os.path.dirname(script_or_module))) + + if args.module: + _logger.info('Running module: %s', ' '.join(sys.argv)) + runpy.run_module(script_or_module, run_name='__main__') + + else: + with open(script_or_module) as f: + code = f.read() + + _logger.info('Running script: %s', ' '.join(sys.argv)) + exec(code) diff --git a/doc/source/index.rst b/doc/source/index.rst index 0a44784..83629c2 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -9,7 +9,7 @@ reduction routines and a set of Qt widgets to browse and visualise data. Silx can be cited by its DOIs referenced on `Zenodo `_. -The current version (v0.7) caters for: +The current version (v\ |version|) caters for: * Supporting `HDF5 `_, `SPEC `_ and diff --git a/doc/source/install.rst b/doc/source/install.rst index e7bbe1c..93b9df0 100644 --- a/doc/source/install.rst +++ b/doc/source/install.rst @@ -36,8 +36,9 @@ The GUI widgets depend on the following extra packages: `PySide `_, or `PySide2 `_ * `matplotlib `_ * `PyOpenGL `_ -* `IPython `_ and `qt_console `_ +* `qt_console `_ for the ``silx.gui.console`` widget. +* `dateutil `_ Tools for reading and writing files depend on the following packages: diff --git a/doc/source/modules/gui/colors.rst b/doc/source/modules/gui/colors.rst new file mode 100644 index 0000000..3cba7c6 --- /dev/null +++ b/doc/source/modules/gui/colors.rst @@ -0,0 +1,9 @@ +.. currentmodule:: silx.gui + +:mod:`colors`: Colors and colormap +---------------------------------- + +.. currentmodule:: silx.gui.colors + +.. automodule:: silx.gui.colors + :members: diff --git a/doc/source/modules/gui/dialog/colormapdialog.rst b/doc/source/modules/gui/dialog/colormapdialog.rst new file mode 100644 index 0000000..0904431 --- /dev/null +++ b/doc/source/modules/gui/dialog/colormapdialog.rst @@ -0,0 +1,12 @@ + +.. currentmodule:: silx.gui.dialog + +:mod:`ColormapDialog` +===================== + +.. automodule:: silx.gui.dialog.ColormapDialog + +.. currentmodule:: silx.gui.dialog.ColormapDialog + +.. autoclass:: ColormapDialog + :members: diff --git a/doc/source/modules/gui/dialog/groupdialog.rst b/doc/source/modules/gui/dialog/groupdialog.rst new file mode 100644 index 0000000..85e85ad --- /dev/null +++ b/doc/source/modules/gui/dialog/groupdialog.rst @@ -0,0 +1,8 @@ + +.. currentmodule:: silx.gui.dialog + +:mod:`GroupDialog` +====================== + +.. automodule:: silx.gui.dialog.GroupDialog + diff --git a/doc/source/modules/gui/dialog/img/abstractdatafiledialog.png b/doc/source/modules/gui/dialog/img/abstractdatafiledialog.png new file mode 100644 index 0000000..7e4eeeb Binary files /dev/null and b/doc/source/modules/gui/dialog/img/abstractdatafiledialog.png differ diff --git a/doc/source/modules/gui/dialog/img/groupdialog.png b/doc/source/modules/gui/dialog/img/groupdialog.png new file mode 100644 index 0000000..ee8c469 Binary files /dev/null and b/doc/source/modules/gui/dialog/img/groupdialog.png differ diff --git a/doc/source/modules/gui/dialog/index.rst b/doc/source/modules/gui/dialog/index.rst index a13ad31..7e2f687 100644 --- a/doc/source/modules/gui/dialog/index.rst +++ b/doc/source/modules/gui/dialog/index.rst @@ -17,10 +17,14 @@ Snapshot of the widgets: :height: 150px :align: middle -======================== ========================== -|imgImageFileDialog| |imgDataFileDialog| -:class:`ImageFileDialog` :class:`DataFileDialog` -======================== ========================== +.. |imgGroupDialog| image:: ./img/datafiledialog.png + :height: 150px + :align: middle + +======================== ========================== ========================== +|imgImageFileDialog| |imgDataFileDialog| |imgGroupDialog| +:class:`ImageFileDialog` :class:`DataFileDialog` :class:`GroupDialog` +======================== ========================== ========================== Public modules: @@ -30,4 +34,6 @@ Public modules: imagefiledialog.rst datafiledialog.rst abstractdatafiledialog.rst + groupdialog.rst + colormapdialog.rst diff --git a/doc/source/modules/gui/gallery.rst b/doc/source/modules/gui/gallery.rst index 40d8648..c71a87a 100644 --- a/doc/source/modules/gui/gallery.rst +++ b/doc/source/modules/gui/gallery.rst @@ -75,13 +75,20 @@ Widgets gallery * - .. image:: dialog/img/datafiledialog.png :height: 150px :align: center - - :class:`DataFileDialog` is a dialog that allow users to select - any datasets or groups from an HDF5-like file. + - :class:`DataFileDialog` is a dialog that allows users to select + any datasets or groups from any HDF5-like file. It features a file + browser that can also browse the content of HDF5 file as if they were + directories. * - .. image:: dialog/img/imagefiledialog_h5.png :height: 150px :align: center - - :class:`ImageFileDialog` is a dialog that allow users to select - an image from an HDF5-like file. + - :class:`ImageFileDialog` is a dialog that allows users to select + an image from any HDF5-like file. + * - .. image:: dialog/img/groupdialog.png + :height: 150px + :align: center + - :class:`GroupDialog` is a dialog that allows users to select + a group from one or several specified HDF5-like files. :mod:`silx.gui.fit` Widgets @@ -179,7 +186,10 @@ Plotting widgets: 2D dataset of complex data. It allows to switch between viewing amplitude, phase, real, imaginary, colored phase with amplitude or log10(amplitude) as brightness. - + * - .. image:: plot/img/ScatterView.png + :height: 150px + :align: center + - :class:`ScatterView` is a widget dedicated to visualize a scatter plot. Additional widgets: @@ -204,6 +214,10 @@ Additional widgets: :align: center - :class:`.ColorBar.ColorBarWidget` display colormap gradient and can be linked with a plot to display the colormap + * - .. image:: plot/img/statsWidget.png + :height: 150px + :align: center + - :class:`.statsWidget.StatsWidget` display statistics on plot's items (curve, images...) .. _plot3d-gallery: diff --git a/doc/source/modules/gui/hdf5/getting_started.rst b/doc/source/modules/gui/hdf5/getting_started.rst index e843cac..1a81a0a 100644 --- a/doc/source/modules/gui/hdf5/getting_started.rst +++ b/doc/source/modules/gui/hdf5/getting_started.rst @@ -4,10 +4,9 @@ Getting started with HDF5 widgets ================================= Silx provides an implementation of a tree model and a tree view for HDF5 files. -The aim of this tree is to provide a convenient read-only widget for a big -amount of data and supporting file formats often used in synchrotrons. +The aim of this tree is to provide a convenient read-only widget supporting file formats often used in synchrotrons for big amounts of data. -This page provides some source code to show how to use this widget. +This page displays some source code to explain how to use this widget. Commented source code --------------------- @@ -34,15 +33,15 @@ HDF5 widgets are all exposed by the package `silx.gui.hdf5`. Custom your tree view +++++++++++++++++++++ -The tree view can be customized to be sorted by default. +The tree view can be customised to be sorted by default. .. testcode:: # Sort content of files by time or name treeview.setSortingEnabled(True) -The model can be customized to support mouse interaction. -A convenient method :meth:`Hdf5TreeView.findHdf5TreeModel` returns the main +The model can be customised to support mouse interaction. +A convenient method, i.e., :meth:`Hdf5TreeView.findHdf5TreeModel`, returns the main HDF5 model used through proxy models. .. testcode:: @@ -55,8 +54,7 @@ HDF5 model used through proxy models. # Allow the user to reorder files with drag-and-drop model.setFileMoveEnabled(True) -The tree view is also provided with a custom header which help to choose -visible columns. +The tree view is also provided with a custom header that helps to choose visible columns. .. testcode:: @@ -72,18 +70,18 @@ visible columns. Add a file by name ++++++++++++++++++ -The model can be used to add HDF5. It is internally using +The model can be used to add an HDF5 file. It is internally using :func:`silx.io.open`. .. code-block:: python model.insertFile("test.h5") -Add a file with h5py -++++++++++++++++++++ +Add a file using h5py ++++++++++++++++++++++ -The model internally uses :mod:`h5py` object API. We can use h5py file, group -and dataset as it is. +The model internally uses the :mod:`h5py` object API. +We can use directly h5py Files, Groups and Datasets. .. code-block:: python @@ -97,10 +95,10 @@ and dataset as it is. model.insertH5pyObject(h5["group1"]) model.insertH5pyObject(h5["group1/dataset50"]) -Add a file with silx -++++++++++++++++++++ +Add a file using silx ++++++++++++++++++++++ -Silx also provides an input API. It supports HDF5 files through :mod:`h5py`. +Silx also provides an input API and supports HDF5 files through :mod:`h5py`. .. code-block:: python @@ -119,7 +117,7 @@ Custom context menu The :class:`Hdf5TreeView` provides a callback API to populate the context menu. The callback receives a :class:`Hdf5ContextMenuEvent` every time the user requests the context menu. The event contains :class:`H5Node` objects which wrap -h5py objects with extra information. +h5py objects adding extra information. .. testcode:: @@ -147,27 +145,27 @@ The :class:`Hdf5TreeView` widget provides default Qt signals inherited from - `activated`: This signal is emitted when the item specified by index is - activated by the user. How to activate items depends on the platform; + activated by the user. How to activate items depends on the platform, e.g., by single- or double-clicking the item, or by pressing the - Return or Enter key when the item is current. + Return or Enter key when the item is the current one. - `clicked`: - This signal is emitted when a mouse button is clicked. The item the mouse - was clicked on is specified by index. The signal is only emitted when the - index is valid. + This signal is emitted when a mouse button is clicked. The item selected by + mouse click is specified by index. The signal is only emitted when the + index is a valid one. - `doubleClicked`: - This signal is emitted when a mouse button is double-clicked. The item - the mouse was double-clicked on is specified by index. The signal is - only emitted when the index is valid. + This signal is emitted when a mouse button is double-clicked. + The selected item is specified by index. + The signal is only emitted when the index is a valid one. - `entered`: This signal is emitted when the mouse cursor enters the item specified by index. Mouse tracking needs to be enabled for this feature to work. - `pressed`: - This signal is emitted when a mouse button is pressed. The item the mouse - was pressed on is specified by index. The signal is only emitted when the - index is valid. + This signal is emitted when a mouse button is pressed. + The selected item is specified by index. + The signal is only emitted when the index is a valid one. The method :meth:`Hdf5TreeView.selectedH5Nodes` returns an iterator of :class:`H5Node` -objects which wrap h5py objects with extra information. +objects which wrap h5py objects adding extra information. .. testcode:: @@ -204,7 +202,7 @@ Example examples_hdf5widget.rst The :doc:`examples_hdf5widget` sample code provides an example of properties of -the view, the model and the header. +the view, model and header. .. image:: img/Hdf5Example.png :height: 200px @@ -214,13 +212,12 @@ the view, the model and the header. Source code: :doc:`examples_hdf5widget`. -After installing `silx` and downloading the script, you can start it from the -command prompt: +After installing `silx` and downloading the script, you can run it from the +command line prompt: .. code-block:: bash python hdf5widget.py This example loads files added to the command line, or files dropped from the -file system. It also provides a GUI to display test files created -programmatically. +file system. It also provides a GUI to display dummy test files. diff --git a/doc/source/modules/gui/hdf5/hdf5headerview.rst b/doc/source/modules/gui/hdf5/hdf5headerview.rst new file mode 100644 index 0000000..b3b3a98 --- /dev/null +++ b/doc/source/modules/gui/hdf5/hdf5headerview.rst @@ -0,0 +1,9 @@ + +.. currentmodule:: silx.gui.hdf5 + +:class:`Hdf5HeaderView` class +----------------------------- + +.. autoclass:: Hdf5HeaderView + :show-inheritance: + :members: diff --git a/doc/source/modules/gui/hdf5/index.rst b/doc/source/modules/gui/hdf5/index.rst index cd2c4eb..9ae416f 100644 --- a/doc/source/modules/gui/hdf5/index.rst +++ b/doc/source/modules/gui/hdf5/index.rst @@ -43,4 +43,5 @@ Public modules hdf5treemodel.rst hdf5contextmenuevent.rst h5node.rst + hdf5headerview.rst nexussortfilterproxymodel.rst diff --git a/doc/source/modules/gui/icons.rst b/doc/source/modules/gui/icons.rst index 48fe95c..3105006 100644 --- a/doc/source/modules/gui/icons.rst +++ b/doc/source/modules/gui/icons.rst @@ -29,6 +29,20 @@ Available icons - 3d-plane-pan * - |3d-plane| - 3d-plane + * - |add-shape-arc| + - add-shape-arc + * - |add-shape-diagonal| + - add-shape-diagonal + * - |add-shape-horizontal| + - add-shape-horizontal + * - |add-shape-point| + - add-shape-point + * - |add-shape-polygon| + - add-shape-polygon + * - |add-shape-rectangle| + - add-shape-rectangle + * - |add-shape-vertical| + - add-shape-vertical * - |arrow-keys| - arrow-keys * - |axis| @@ -117,6 +131,8 @@ Available icons - item-object * - |last| - last + * - |layer-nx| + - layer-nx * - |math-amplitude| - math-amplitude * - |math-average| @@ -163,6 +179,14 @@ Available icons - next * - |normal| - normal + * - |nxdata-axis-add| + - nxdata-axis-add + * - |nxdata-axis-remove| + - nxdata-axis-remove + * - |nxdata-create| + - nxdata-create + * - |nxdata-remove| + - nxdata-remove * - |pan| - pan * - |pixel-intensities| @@ -179,6 +203,8 @@ Available icons - plot-roi-reset * - |plot-roi| - plot-roi + * - |plot-symbols| + - plot-symbols * - |plot-toggle-points| - plot-toggle-points * - |plot-widget| @@ -243,6 +269,18 @@ Available icons - sliders-on * - |spec| - spec + * - |stats-active-items| + - stats-active-items + * - |stats-visible-data| + - stats-visible-data + * - |stats-whole-data| + - stats-whole-data + * - |stats-whole-items| + - stats-whole-items + * - |tree-collapse-all| + - tree-collapse-all + * - |tree-expand-all| + - tree-expand-all * - |view-1d| - view-1d * - |view-2d-stack| @@ -283,6 +321,13 @@ Available icons .. |3d-plane-normal-z| image:: ../../../../silx/resources/gui/icons/3d-plane-normal-z.png .. |3d-plane-pan| image:: ../../../../silx/resources/gui/icons/3d-plane-pan.png .. |3d-plane| image:: ../../../../silx/resources/gui/icons/3d-plane.png +.. |add-shape-arc| image:: ../../../../silx/resources/gui/icons/add-shape-arc.png +.. |add-shape-diagonal| image:: ../../../../silx/resources/gui/icons/add-shape-diagonal.png +.. |add-shape-horizontal| image:: ../../../../silx/resources/gui/icons/add-shape-horizontal.png +.. |add-shape-point| image:: ../../../../silx/resources/gui/icons/add-shape-point.png +.. |add-shape-polygon| image:: ../../../../silx/resources/gui/icons/add-shape-polygon.png +.. |add-shape-rectangle| image:: ../../../../silx/resources/gui/icons/add-shape-rectangle.png +.. |add-shape-vertical| image:: ../../../../silx/resources/gui/icons/add-shape-vertical.png .. |arrow-keys| image:: ../../../../silx/resources/gui/icons/arrow-keys.png .. |axis| image:: ../../../../silx/resources/gui/icons/axis.png .. |camera| image:: ../../../../silx/resources/gui/icons/camera.png @@ -327,6 +372,7 @@ Available icons .. |item-none| image:: ../../../../silx/resources/gui/icons/item-none.png .. |item-object| image:: ../../../../silx/resources/gui/icons/item-object.png .. |last| image:: ../../../../silx/resources/gui/icons/last.png +.. |layer-nx| image:: ../../../../silx/resources/gui/icons/layer-nx.png .. |math-amplitude| image:: ../../../../silx/resources/gui/icons/math-amplitude.png .. |math-average| image:: ../../../../silx/resources/gui/icons/math-average.png .. |math-derive| image:: ../../../../silx/resources/gui/icons/math-derive.png @@ -350,6 +396,10 @@ Available icons .. |median-filter| image:: ../../../../silx/resources/gui/icons/median-filter.png .. |next| image:: ../../../../silx/resources/gui/icons/next.png .. |normal| image:: ../../../../silx/resources/gui/icons/normal.png +.. |nxdata-axis-add| image:: ../../../../silx/resources/gui/icons/nxdata-axis-add.png +.. |nxdata-axis-remove| image:: ../../../../silx/resources/gui/icons/nxdata-axis-remove.png +.. |nxdata-create| image:: ../../../../silx/resources/gui/icons/nxdata-create.png +.. |nxdata-remove| image:: ../../../../silx/resources/gui/icons/nxdata-remove.png .. |pan| image:: ../../../../silx/resources/gui/icons/pan.png .. |pixel-intensities| image:: ../../../../silx/resources/gui/icons/pixel-intensities.png .. |plot-grid| image:: ../../../../silx/resources/gui/icons/plot-grid.png @@ -358,6 +408,7 @@ Available icons .. |plot-roi-between| image:: ../../../../silx/resources/gui/icons/plot-roi-between.png .. |plot-roi-reset| image:: ../../../../silx/resources/gui/icons/plot-roi-reset.png .. |plot-roi| image:: ../../../../silx/resources/gui/icons/plot-roi.png +.. |plot-symbols| image:: ../../../../silx/resources/gui/icons/plot-symbols.png .. |plot-toggle-points| image:: ../../../../silx/resources/gui/icons/plot-toggle-points.png .. |plot-widget| image:: ../../../../silx/resources/gui/icons/plot-widget.png .. |plot-window-image| image:: ../../../../silx/resources/gui/icons/plot-window-image.png @@ -390,6 +441,12 @@ Available icons .. |sliders-off| image:: ../../../../silx/resources/gui/icons/sliders-off.png .. |sliders-on| image:: ../../../../silx/resources/gui/icons/sliders-on.png .. |spec| image:: ../../../../silx/resources/gui/icons/spec.png +.. |stats-active-items| image:: ../../../../silx/resources/gui/icons/stats-active-items.png +.. |stats-visible-data| image:: ../../../../silx/resources/gui/icons/stats-visible-data.png +.. |stats-whole-data| image:: ../../../../silx/resources/gui/icons/stats-whole-data.png +.. |stats-whole-items| image:: ../../../../silx/resources/gui/icons/stats-whole-items.png +.. |tree-collapse-all| image:: ../../../../silx/resources/gui/icons/tree-collapse-all.png +.. |tree-expand-all| image:: ../../../../silx/resources/gui/icons/tree-expand-all.png .. |view-1d| image:: ../../../../silx/resources/gui/icons/view-1d.png .. |view-2d-stack| image:: ../../../../silx/resources/gui/icons/view-2d-stack.png .. |view-2d| image:: ../../../../silx/resources/gui/icons/view-2d.png diff --git a/doc/source/modules/gui/index.rst b/doc/source/modules/gui/index.rst index 1f7ee4a..0afc49a 100644 --- a/doc/source/modules/gui/index.rst +++ b/doc/source/modules/gui/index.rst @@ -16,9 +16,11 @@ The ``silx.gui`` package provides a set of PyQt widgets. dialog/index.rst hdf5/index.rst icons.rst + colors.rst plot/index.rst plot3d/index.rst qt.rst + utils.rst widgets/index.rst diff --git a/doc/source/modules/gui/plot/colormap.rst b/doc/source/modules/gui/plot/colormap.rst deleted file mode 100644 index a492605..0000000 --- a/doc/source/modules/gui/plot/colormap.rst +++ /dev/null @@ -1,16 +0,0 @@ - -.. currentmodule:: silx.gui.plot.Colormap - -:mod:`Colormap`: Colormap API -============================= - -.. automodule:: silx.gui.plot.Colormap - -.. currentmodule:: silx.gui.plot.Colormap - -:class:`Colormap` class ------------------------ - -.. autoclass:: Colormap - :show-inheritance: - :members: diff --git a/doc/source/modules/gui/plot/dev.rst b/doc/source/modules/gui/plot/dev.rst index b793b01..d7d4581 100644 --- a/doc/source/modules/gui/plot/dev.rst +++ b/doc/source/modules/gui/plot/dev.rst @@ -20,7 +20,6 @@ The different events emitted by :class:`Plot` and by the interaction modes are c The :class:`PlotWindow` uses additional widgets: -- :mod:`.ColormapDialog` to change colormap settings. - :mod:`.CurvesROIWidget` to create regions of interest for curves - :mod:`.LegendSelector` to display a list of curves legends which provides some control on the curves (e.g., select, delete). - :mod:`.MaskToolsWidget` to provide tools to draw a mask on an image. @@ -39,7 +38,6 @@ The :class:`PlotWindow` uses additional widgets: The widgets also use the following miscellaneous modules: -- :mod:`.Colors` to convert colors from name to RGB(A) - :mod:`._utils`: utility functions The :mod:`backends` package provide the implementation of the rendering used by the :class:`Plot`. @@ -88,22 +86,6 @@ The following modules are the modules used internally by the plot package. .. automodule:: silx.gui.plot.backends.BackendMatplotlib :members: -:mod:`ColormapDialog` -+++++++++++++++++++++ - -.. currentmodule:: silx.gui.plot.ColormapDialog - -.. automodule:: silx.gui.plot.ColormapDialog - :members: - -:mod:`Colors` -+++++++++++++ - -.. currentmodule:: silx.gui.plot.Colors - -.. automodule:: silx.gui.plot.Colors - :members: rgba - :mod:`CurvesROIWidget` ++++++++++++++++++++++ diff --git a/doc/source/modules/gui/plot/getting_started.rst b/doc/source/modules/gui/plot/getting_started.rst index bfea8f2..412cc11 100644 --- a/doc/source/modules/gui/plot/getting_started.rst +++ b/doc/source/modules/gui/plot/getting_started.rst @@ -20,7 +20,9 @@ For a complete description of the API, see :mod:`silx.gui.plot`. Use :mod:`silx.gui.plot` from (I)Python console ----------------------------------------------- -The simplest way is to import the :mod:`silx.sx` module: +We recommend to use (I)Python 3.x and PyQt5. + +From a Python or IPython interpreter, the simplest way is to import the :mod:`silx.sx` module: >>> from silx import sx @@ -28,19 +30,27 @@ The :mod:`silx.sx` module initialises Qt and provides access to :mod:`silx.gui.p .. note:: The :mod:`silx.sx` module does NOT initialise Qt and does NOT expose silx widget in a notebook. +An alternative to run :mod:`silx.gui` widgets from `IPython `_, +is to set IPython to use Qt(5), e.g., with the `--gui` option:: + + ipython --gui=qt5 + + Compatibility with IPython ++++++++++++++++++++++++++ -To run :mod:`silx.gui` widgets from `IPython `_, -IPython must be set to use Qt (and in case of using PyQt4 and Python 2.7, -PyQt must be set to use API version 2, see note below for explanation). - -As *silx* is configuring the Qt binding and `matplotlib `_, the safest way to use *silx* from IPython is to import :mod:`silx.gui.plot` first and then run either `%gui `_ qt or `%pylab `_ qt:: +silx widgets require Qt to be initialized. +If Qt is not yet loaded, silx tries to load PyQt5 first before trying other supported bindings. - In [1]: from silx.gui.plot import * - In [2]: %pylab qt +With versions of IPython lower than 3.0 (e.g., on Debian 8), there is an incompatibility between +the way silx loads Qt and the way IPython is doing it through the ``--gui`` option, +`%gui `_ or +`%pylab `_ magics. +In this case, IPython magics that initialize Qt might not work after importing modules from silx.gui. -Alternatively, when using Python 2.7 and PyQt4, you can start IPython with the ``QT_API`` environment variable set to ``pyqt``. +When using Python2.7 and PyQt4, there is another incompatibility to deal with as +silx requires PyQt4 API version 2 (See note below for explanation). +In this case, start IPython with the ``QT_API`` environment variable set to ``pyqt``. On Linux and MacOS X, run from the command line:: @@ -289,7 +299,7 @@ A ``colormap`` is described with a :class:`.Colormap` class as follows: .. code-block:: python - from silx.gui.plot.Colormap import Colormap + from silx.gui.colors import Colormap colormap = Colormap(name='gray', # Name of the colormap normalization='linear', # Either 'linear' or 'log' @@ -317,7 +327,7 @@ It is possible to change the default colormap of the plot widget by :meth:`.Plot .. code-block:: python - from silx.gui.plot.Colormap import Colormap + from silx.gui.colors import Colormap colormap = Colormap(name='viridis', normalization='linear', diff --git a/doc/source/modules/gui/plot/img/ScatterView.png b/doc/source/modules/gui/plot/img/ScatterView.png new file mode 100644 index 0000000..a8116b9 Binary files /dev/null and b/doc/source/modules/gui/plot/img/ScatterView.png differ diff --git a/doc/source/modules/gui/plot/img/netArea.png b/doc/source/modules/gui/plot/img/netArea.png new file mode 100644 index 0000000..f987d1f Binary files /dev/null and b/doc/source/modules/gui/plot/img/netArea.png differ diff --git a/doc/source/modules/gui/plot/img/netCounts.png b/doc/source/modules/gui/plot/img/netCounts.png index f987d1f..9f7d2a0 100644 Binary files a/doc/source/modules/gui/plot/img/netCounts.png and b/doc/source/modules/gui/plot/img/netCounts.png differ diff --git a/doc/source/modules/gui/plot/img/rawArea.png b/doc/source/modules/gui/plot/img/rawArea.png new file mode 100644 index 0000000..fa36b8e Binary files /dev/null and b/doc/source/modules/gui/plot/img/rawArea.png differ diff --git a/doc/source/modules/gui/plot/img/rawCounts.png b/doc/source/modules/gui/plot/img/rawCounts.png index fa36b8e..6382438 100644 Binary files a/doc/source/modules/gui/plot/img/rawCounts.png and b/doc/source/modules/gui/plot/img/rawCounts.png differ diff --git a/doc/source/modules/gui/plot/img/statsWidget.png b/doc/source/modules/gui/plot/img/statsWidget.png new file mode 100644 index 0000000..7e18ec4 Binary files /dev/null and b/doc/source/modules/gui/plot/img/statsWidget.png differ diff --git a/doc/source/modules/gui/plot/index.rst b/doc/source/modules/gui/plot/index.rst index ab1c7df..5cda6d8 100644 --- a/doc/source/modules/gui/plot/index.rst +++ b/doc/source/modules/gui/plot/index.rst @@ -25,22 +25,40 @@ See :ref:`plot-gallery` gallery. Public modules -------------- +Main plot widgets: + .. toctree:: :maxdepth: 2 plotwidget.rst plotwindow.rst + compleximageview.rst imageview.rst stackview.rst - compleximageview.rst - colormap.rst + scatterview.rst + +Classes describing plot content: + +- :class:`~silx.gui.colors.Colormap` + +.. toctree:: + :maxdepth: 2 + items.rst + +Additionnal plot tool widgets: + +.. toctree:: + :maxdepth: 2 + actions/index.rst plottoolbuttons.rst - plottools.rst + tools.rst profile.rst roi.rst printpreviewtoolbutton.rst + statswidget.rst + stats/index.rst Internals --------- diff --git a/doc/source/modules/gui/plot/items.rst b/doc/source/modules/gui/plot/items.rst index 3cdcf17..eb3ca1a 100644 --- a/doc/source/modules/gui/plot/items.rst +++ b/doc/source/modules/gui/plot/items.rst @@ -108,3 +108,10 @@ Axis .. autoclass:: Axis :members: + + +:mod:`~silx.gui.plot.items.roi`: Regions of Interest +---------------------------------------------------- + +.. automodule:: silx.gui.plot.items.roi + :members: diff --git a/doc/source/modules/gui/plot/plottools.rst b/doc/source/modules/gui/plot/plottools.rst deleted file mode 100644 index 4cf8f88..0000000 --- a/doc/source/modules/gui/plot/plottools.rst +++ /dev/null @@ -1,36 +0,0 @@ -:mod:`PlotTools`: Tool widgets for PlotWidget -============================================= - -.. currentmodule:: silx.gui.plot.PlotTools - -.. automodule:: silx.gui.plot.PlotTools - -:class:`PositionInfo` class ---------------------------- - -.. autoclass:: PositionInfo - :show-inheritance: - :members: - -:class:`LimitsToolBar` class ----------------------------- - -.. autoclass:: LimitsToolBar - :show-inheritance: - :members: - - -.. currentmodule:: silx.gui.plot - -:mod:`ColorBar`: ColorBar Widget -================================ - -.. currentmodule:: silx.gui.plot.ColorBar - -.. automodule:: silx.gui.plot.ColorBar - -:class:`ColorBarWidget` class ------------------------------ - -.. autoclass:: ColorBarWidget - :members: diff --git a/doc/source/modules/gui/plot/plotwidget.rst b/doc/source/modules/gui/plot/plotwidget.rst index d045f92..4ce69f7 100644 --- a/doc/source/modules/gui/plot/plotwidget.rst +++ b/doc/source/modules/gui/plot/plotwidget.rst @@ -148,7 +148,6 @@ Misc. .. automethod:: PlotWidget.getWidgetHandle .. automethod:: PlotWidget.saveGraph -.. automethod:: PlotWidget.setDefaultBackend Signals ....... diff --git a/doc/source/modules/gui/plot/scatterview.rst b/doc/source/modules/gui/plot/scatterview.rst new file mode 100644 index 0000000..6a0cf93 --- /dev/null +++ b/doc/source/modules/gui/plot/scatterview.rst @@ -0,0 +1,20 @@ + +.. currentmodule:: silx.gui.plot + +:mod:`ScatterView`: Plot 2D scatter data +======================================== + +.. automodule:: silx.gui.plot.ScatterView + +.. currentmodule:: silx.gui.plot.ScatterView + +.. image:: img/ScatterView.png + :height: 400px + :align: center + +:class:`ScatterView` class +-------------------------- + +.. autoclass:: ScatterView + :show-inheritance: + :members: \ No newline at end of file diff --git a/doc/source/modules/gui/plot/stats/index.rst b/doc/source/modules/gui/plot/stats/index.rst new file mode 100644 index 0000000..ccfb3f9 --- /dev/null +++ b/doc/source/modules/gui/plot/stats/index.rst @@ -0,0 +1,18 @@ +.. currentmodule:: silx.gui.plot.stats + +:mod:`stats`: Stats for PlotWidget +================================================= + +.. automodule:: silx.gui.plot.stats + +stats API +--------- + +Actions are divided into the following sub-modules: + + +.. toctree:: + :maxdepth: 1 + + stats.rst + statshandler.rst \ No newline at end of file diff --git a/doc/source/modules/gui/plot/stats/stats.rst b/doc/source/modules/gui/plot/stats/stats.rst new file mode 100644 index 0000000..717f6eb --- /dev/null +++ b/doc/source/modules/gui/plot/stats/stats.rst @@ -0,0 +1,6 @@ +:mod:`silx.gui.plot.stats.stats` +```````````````````````````````` +.. currentmodule:: silx.gui.plot.stats.stats + +.. automodule:: silx.gui.plot.stats.stats + :members: diff --git a/doc/source/modules/gui/plot/stats/statshandler.rst b/doc/source/modules/gui/plot/stats/statshandler.rst new file mode 100644 index 0000000..01d56f3 --- /dev/null +++ b/doc/source/modules/gui/plot/stats/statshandler.rst @@ -0,0 +1,7 @@ +:mod:`silx.gui.plot.stats.statshandler` +``````````````````````````````````````` + +.. currentmodule:: silx.gui.plot.stats.statshandler + +.. automodule:: silx.gui.plot.stats.statshandler + :members: diff --git a/doc/source/modules/gui/plot/statswidget.rst b/doc/source/modules/gui/plot/statswidget.rst new file mode 100644 index 0000000..f534921 --- /dev/null +++ b/doc/source/modules/gui/plot/statswidget.rst @@ -0,0 +1,33 @@ + +.. currentmodule:: silx.gui.plot + +:mod:`StatsWidget`: Display a set of statictics for plot items +============================================================== + +.. automodule:: silx.gui.plot.StatsWidget + +.. currentmodule:: silx.gui.plot.StatsWidget + +:class:`StatsWidget` class +-------------------------- + +.. autoclass:: StatsWidget + :show-inheritance: + :members: + + +:class:`BasicStatsWidget` class +------------------------------- + +.. autoclass:: BasicStatsWidget + :show-inheritance: + :members: + + +:class:`StatsTable` class +------------------------- + +.. autoclass:: StatsTable + :show-inheritance: + :members: + diff --git a/doc/source/modules/gui/plot/tools.rst b/doc/source/modules/gui/plot/tools.rst new file mode 100644 index 0000000..766c8f1 --- /dev/null +++ b/doc/source/modules/gui/plot/tools.rst @@ -0,0 +1,108 @@ +:mod:`~silx.gui.plot.tools`: Tool widgets for PlotWidget +======================================================== + +.. currentmodule:: silx.gui.plot.tools + +.. automodule:: silx.gui.plot.tools + +:class:`PositionInfo` class +--------------------------- + +.. autoclass:: PositionInfo + :show-inheritance: + :members: + +:class:`LimitsToolBar` class +---------------------------- + +.. autoclass:: LimitsToolBar + :show-inheritance: + :members: + +:class:`InteractiveModeToolBar` class +------------------------------------- + +.. autoclass:: InteractiveModeToolBar + :show-inheritance: + :members: + +:class:`OutputToolBar` class +---------------------------- + +.. autoclass:: OutputToolBar + :show-inheritance: + :members: + +:class:`ImageToolBar` class +---------------------------- + +.. autoclass:: ImageToolBar + :show-inheritance: + :members: + +:class:`CurveToolBar` class +---------------------------- + +.. autoclass:: CurveToolBar + :show-inheritance: + :members: + +:class:`ScatterToolBar` class +----------------------------- + +.. autoclass:: ScatterToolBar + :show-inheritance: + :members: + +:mod:`~silx.gui.plot.tools.roi`: Region of interest +--------------------------------------------------- + +.. automodule:: silx.gui.plot.tools.roi + +.. currentmodule:: silx.gui.plot.tools.roi + +:class:`RegionOfInterestManager` class +++++++++++++++++++++++++++++++++++++++ + +.. autoclass:: RegionOfInterestManager + :members: + +:class:`InteractiveRegionOfInterestManager` class ++++++++++++++++++++++++++++++++++++++++++++++++++ + +.. autoclass:: InteractiveRegionOfInterestManager + :members: + +:class:`RegionOfInterestTableWidget` class +++++++++++++++++++++++++++++++++++++++++++ + +.. autoclass:: RegionOfInterestTableWidget + :members: + +:mod:`~silx.gui.plot.tools.profile`: Profile Tools +-------------------------------------------------- + +.. automodule:: silx.gui.plot.tools.profile + +.. currentmodule:: silx.gui.plot.tools.profile + +:class:`ScatterProfileToolBar` +++++++++++++++++++++++++++++++ + +.. autoclass:: ScatterProfileToolBar + :members: sigProfileChanged, getProfilePoints, getProfileValues, getProfileTitle, getPlotWidget, isDefaultProfileWindowEnabled, setDefaultProfileWindowEnabled, getDefaultProfileWindow, getColor, setColor, clearProfile, getNPoints, setNPoints + +.. currentmodule:: silx.gui.plot + +:mod:`ColorBar`: ColorBar Widget +================================ + +.. currentmodule:: silx.gui.plot.ColorBar + +.. automodule:: silx.gui.plot.ColorBar + +:class:`ColorBarWidget` class +----------------------------- + +.. autoclass:: ColorBarWidget + :members: diff --git a/doc/source/modules/gui/utils.rst b/doc/source/modules/gui/utils.rst new file mode 100644 index 0000000..be0f0ae --- /dev/null +++ b/doc/source/modules/gui/utils.rst @@ -0,0 +1,12 @@ +.. currentmodule:: silx.gui.utils + +:mod:`~silx.gui.utils`: Qt helpers +================================== + +.. automodule:: silx.gui.utils + +:mod:`~silx.gui.utils.concurrent` +--------------------------------- + +.. automodule:: silx.gui.utils.concurrent + :members: diff --git a/doc/source/modules/image/index.rst b/doc/source/modules/image/index.rst index 89c5642..477cc9f 100644 --- a/doc/source/modules/image/index.rst +++ b/doc/source/modules/image/index.rst @@ -6,9 +6,10 @@ .. toctree:: :maxdepth: 1 - + bilinear.rst medianfilter.rst + marchingsquares.rst shapes.rst sift.rst backprojection.rst diff --git a/doc/source/modules/image/marchingsquares.rst b/doc/source/modules/image/marchingsquares.rst new file mode 100644 index 0000000..b44f66d --- /dev/null +++ b/doc/source/modules/image/marchingsquares.rst @@ -0,0 +1,11 @@ + +.. currentmodule:: silx.image + +:mod:`marchingsquares`: Marching squares +---------------------------------------- + +.. automodule:: silx.image.marchingsquares + :members: + +.. autoclass:: silx.image.marchingsquares.MarchingSquaresMergeImpl + :members: diff --git a/doc/source/modules/io/nxdata.rst b/doc/source/modules/io/nxdata.rst index 7940270..1ec66fb 100644 --- a/doc/source/modules/io/nxdata.rst +++ b/doc/source/modules/io/nxdata.rst @@ -1,8 +1,2 @@ -.. currentmodule:: silx.io - -:mod:`nxdata`: NXdata group parsing ------------------------------------- - .. automodule:: silx.io.nxdata - :members: diff --git a/doc/source/modules/math/colormap.rst b/doc/source/modules/math/colormap.rst new file mode 100644 index 0000000..f184b66 --- /dev/null +++ b/doc/source/modules/math/colormap.rst @@ -0,0 +1,6 @@ +:mod:`~silx.math.colormap`: Apply colormap to array +--------------------------------------------------- + +.. automodule:: silx.math.colormap + +.. autofunction:: cmap diff --git a/doc/source/modules/math/combo.rst b/doc/source/modules/math/combo.rst index 4d1b5c6..511aacf 100644 --- a/doc/source/modules/math/combo.rst +++ b/doc/source/modules/math/combo.rst @@ -1,7 +1,5 @@ -.. currentmodule:: silx.math - -:mod:`silx.math.combo`: Statistics combo functions --------------------------------------------------- +:mod:`~silx.math.combo`: Statistics combo functions +--------------------------------------------------- .. automodule:: silx.math.combo diff --git a/doc/source/modules/math/index.rst b/doc/source/modules/math/index.rst index ece49dd..fbecc02 100644 --- a/doc/source/modules/math/index.rst +++ b/doc/source/modules/math/index.rst @@ -11,3 +11,4 @@ histogram.rst medianfilter.rst combo.rst + colormap.rst diff --git a/doc/source/modules/sx.rst b/doc/source/modules/sx.rst index d5e0288..f08dd98 100644 --- a/doc/source/modules/sx.rst +++ b/doc/source/modules/sx.rst @@ -13,6 +13,7 @@ The following functions plot curves and images with silx widgets: - :func:`plot` for curves - :func:`imshow` for images +- :func:`scatter` for scatter plot The :func:`ginput` function handles user selection on those widgets. @@ -29,6 +30,11 @@ The :func:`ginput` function handles user selection on those widgets. .. autofunction:: imshow +:func:`scatter` ++++++++++++++++ + +.. autofunction:: scatter + :func:`ginput` ++++++++++++++ diff --git a/doc/source/overview.rst b/doc/source/overview.rst index 50a5ffd..3f3724a 100644 --- a/doc/source/overview.rst +++ b/doc/source/overview.rst @@ -39,3 +39,9 @@ Project - Linux and MacOS X: `Travis `_ - Windows: `AppVeyor `_ +Additional Material +------------------- + +- Code Camp Talks. *silx* follows a quarterly release cycle. Prior to each release, a code camp takes place in which the novelties are presented and interested users make use of the development version in order to spot bugs or missing features. The PDFs of the talks are available for download (`v0.7.0 `_, `v0.6.0 `_, `v0.5.0 `_, `v0.4.0 `_, `v0.3.0 `_, `v0.2.0 `_) + + diff --git a/doc/source/sample_code/img/plotInteractiveImageROI.png b/doc/source/sample_code/img/plotInteractiveImageROI.png new file mode 100644 index 0000000..de62dc3 Binary files /dev/null and b/doc/source/sample_code/img/plotInteractiveImageROI.png differ diff --git a/doc/source/sample_code/img/plotUpdateCurveFromThread.png b/doc/source/sample_code/img/plotUpdateCurveFromThread.png new file mode 100644 index 0000000..ac97ccd Binary files /dev/null and b/doc/source/sample_code/img/plotUpdateCurveFromThread.png differ diff --git a/doc/source/sample_code/img/plotUpdateFromThread.png b/doc/source/sample_code/img/plotUpdateFromThread.png deleted file mode 100644 index ac97ccd..0000000 Binary files a/doc/source/sample_code/img/plotUpdateFromThread.png and /dev/null differ diff --git a/doc/source/sample_code/img/plotUpdateImageFromThread.png b/doc/source/sample_code/img/plotUpdateImageFromThread.png new file mode 100644 index 0000000..c0caec3 Binary files /dev/null and b/doc/source/sample_code/img/plotUpdateImageFromThread.png differ diff --git a/doc/source/sample_code/index.rst b/doc/source/sample_code/index.rst index 933d162..a5cbf11 100644 --- a/doc/source/sample_code/index.rst +++ b/doc/source/sample_code/index.rst @@ -142,7 +142,7 @@ Widgets - .. image:: img/colormapDialog.png :height: 150px :align: center - - This script shows the features of a :mod:`~silx.gui.plot.ColormapDialog`. + - This script shows the features of a :mod:`~silx.gui.dialog.ColormapDialog`. :class:`silx.gui.plot.actions.PlotAction` ......................................... @@ -208,14 +208,15 @@ Sample code that adds specific tools or functions to plot widgets. - .. image:: img/plotWidget.png :height: 150px :align: center - - This script shows how to subclass :class:`~silx.gui.plot.PlotWidget` to tune its tools. + - This script shows how to create a custom window around a PlotWidget. - It subclasses a :class:`~silx.gui.plot.PlotWidget` and adds toolbars and - a colorbar by using pluggable widgets: + It subclasses :class:`QMainWindow`, uses a :class:`~silx.gui.plot.PlotWidget` + as its central widget and adds toolbars and a colorbar by using pluggable widgets: + - :class:`~silx.gui.plot.PlotWidget` from :mod:`silx.gui.plot` + - QToolBar from :mod:`silx.gui.plot.tools` - QAction from :mod:`silx.gui.plot.actions` - QToolButton from :mod:`silx.gui.plot.PlotToolButtons` - - QToolBar from :mod:`silx.gui.plot.PlotTools` - :class:`silx.gui.plot.ColorBar.ColorBarWidget` * - :download:`plotContextMenu.py <../../../examples/plotContextMenu.py>` - .. image:: img/plotContextMenu.png @@ -246,21 +247,43 @@ Sample code that adds specific tools or functions to plot widgets. :align: center - This script is an example to illustrate how to use axis synchronization tool. - * - :download:`plotUpdateFromThread.py <../../../examples/plotUpdateFromThread.py>` - - .. image:: img/plotUpdateFromThread.png + * - :download:`plotUpdateCurveFromThread.py <../../../examples/plotUpdateCurveFromThread.py>` + - .. image:: img/plotUpdateCurveFromThread.png :height: 150px :align: center - This script illustrates the update of a :mod:`silx.gui.plot` widget from a thread. The problem is that plot and GUI methods should be called from the main thread. - To safely update the plot from another thread, one need to make the update - asynchronously from the main thread. - In this example, this is achieved through a Qt signal. - - In this example we create a subclass of :class:`~silx.gui.plot.PlotWindow.Plot1D` - that adds a thread-safe method to add curves: - :meth:`ThreadSafePlot1D.addCurveThreadSafe`. - This thread-safe method is then called from a thread to update the plot.. + To safely update the plot from another thread, one need to execute the update + asynchronously in the main thread. + In this example, this is achieved with + :func:`~silx.gui.utils.concurrent.submitToQtMainThread`. + + In this example a thread calls submitToQtMainThread to update the curve + of a plot. + * - :download:`plotUpdateImageFromThread.py <../../../examples/plotUpdateImageFromThread.py>` + - .. image:: img/plotUpdateImageFromThread.png + :height: 150px + :align: center + - This script illustrates the update of a :mod:`silx.gui.plot` widget from a thread. + + The problem is that plot and GUI methods should be called from the main thread. + To safely update the plot from another thread, one need to execute the update + asynchronously in the main thread. + In this example, this is achieved with + :func:`~silx.gui.utils.concurrent.submitToQtMainThread`. + + In this example a thread calls submitToQtMainThread to update the curve + of a plot. + * - :download:`plotInteractiveImageROI.py <../../../examples/plotInteractiveImageROI.py>` + - .. image:: img/plotInteractiveImageROI.png + :height: 150px + :align: center + - This script illustrates image ROI selection in a :class:`~silx.gui.plot.PlotWidget` + + It uses :class:`~silx.gui.plot.tools.roi.RegionOfInterestManager` and + :class:`~silx.gui.plot.tools.roi.RegionOfInterestTableWidget` to handle the + interactive selection and to display the list of selected ROIs. * - :download:`printPreview.py <../../../examples/printPreview.py>` - .. image:: img/printPreview.png :height: 150px diff --git a/doc/source/tutorials.rst b/doc/source/tutorials.rst index c4d7daa..cda7855 100644 --- a/doc/source/tutorials.rst +++ b/doc/source/tutorials.rst @@ -14,6 +14,7 @@ Tutorials and cookbooks: Tutorials/io.rst Tutorials/convert.rst Tutorials/specfile_to_hdf5.rst + Tutorials/writing_NXdata.rst modules/gui/hdf5/getting_started.rst Tutorials/fit.rst Tutorials/fitconfig.rst -- cgit v1.2.3