From 96b781b56e03536bc972ff2d48184f2e3753183c Mon Sep 17 00:00:00 2001 From: Almar Klein Date: Tue, 8 Dec 2020 15:37:12 +0100 Subject: [PATCH 1/8] docs! --- README.md | 244 +++++++++++++++++++++++++++++++----------- dash_slicer/docs.py | 86 +++++++++++++++ dash_slicer/slicer.py | 144 +++++++++++++++---------- tests/test_docs.py | 26 +++++ 4 files changed, 383 insertions(+), 117 deletions(-) create mode 100644 dash_slicer/docs.py create mode 100644 tests/test_docs.py diff --git a/README.md b/README.md index d4f27db..8c01393 100644 --- a/README.md +++ b/README.md @@ -1,60 +1,184 @@ -![CI](https://github.com/pygfx/wgpu-py/workflows/CI/badge.svg) - -# dash_slicer - -A volume slicer for Dash - - -## Status - -This work is marked as alpha - some essential features are still in -development, and some parts of the API may change in future releases. - - -## Installation - -``` -$ pip install dash-slicer -``` - -Dash-slicer depends on Python 3.6+ plus some [dependencies](requirements.txt). - - -## Usage example - -```py -import dash -import dash_html_components as html -from dash_slicer import VolumeSlicer -import imageio - -app = dash.Dash(__name__) - -vol = imageio.volread("imageio:stent.npz") -slicer = VolumeSlicer(app, vol) -app.layout = html.Div([slicer.graph, slicer.slider, *slicer.stores]) - -if __name__ == "__main__": - app.run_server() -``` - - -## License - -This code is distributed under MIT license. - - -## Developers - - -* Make sure that you have Python with the appropriate dependencies installed, e.g. via `venv`. -* Run `pip install -e .` to do an in-place install of the package. -* Run the examples using e.g. `python examples/slicer_with_1_view.py` - -* Use `black .` to autoformat. -* Use `flake8 .` to lint. -* Use `pytest .` to run the tests. - -On every PR, an app with the same name as your branch is deployed to the Dash -playground instance so that you can change whether your changes did not break -the package. +![CI](https://github.com/pygfx/wgpu-py/workflows/CI/badge.svg) + +# dash_slicer + +A volume slicer for Dash + + +## Status + +This work is marked as alpha - some essential features are still in +development, and some parts of the API may change in future releases. + + +## Installation + +``` +$ pip install dash-slicer +``` + +Dash-slicer depends on Python 3.6+ plus some [dependencies](requirements.txt). + + +## Usage example + +```py +import dash +import dash_html_components as html +from dash_slicer import VolumeSlicer +import imageio + +app = dash.Dash(__name__) + +vol = imageio.volread("imageio:stent.npz") +slicer = VolumeSlicer(app, vol) +app.layout = html.Div([slicer.graph, slicer.slider, *slicer.stores]) + +if __name__ == "__main__": + app.run_server() +``` + + +## License + +This code is distributed under MIT license. + + +## Developers + + +* Make sure that you have Python with the appropriate dependencies installed, e.g. via `venv`. +* Run `pip install -e .` to do an in-place install of the package. +* Run the examples using e.g. `python examples/slicer_with_1_view.py` + +* Use `black .` to autoformat. +* Use `flake8 .` to lint. +* Use `pytest .` to run the tests. + +On every PR, an app with the same name as your branch is deployed to the Dash +playground instance so that you can change whether your changes did not break +the package. + + +## Reference + +### The VolumeSlicer class + +**class `VolumeSlicer(app, volume, *, spacing=None, origin=None, axis=0, reverse_y=True, scene_id=None, color=None)`** + +A slicer object to show 3D image data in Dash. Upon +instantiation one can provide the following parameters: + +* `app` (`dash.Dash`): the Dash application instance. +* `volume` (`ndarray`): the 3D numpy array to slice through. The dimensions + are assumed to be in zyx order. If this is not the case, you can + use ``np.swapaxes`` to make it so. +* `spacing` (tuple of `float`): The distance between voxels for each + dimension (zyx).The spacing and origin are applied to make the slice + drawn in "scene space" rather than "voxel space". +* `origin` (tuple of `float`): The offset for each dimension (zyx). +* `axis` (`int`): the dimension to slice in. Default 0. +* `reverse_y` (`bool`): Whether to reverse the y-axis, so that the origin of + the slice is in the top-left, rather than bottom-left. Default True. + Note: setting this to False affects performance, see #12. +* `scene_id` (`str`): the scene that this slicer is part of. Slicers + that have the same scene-id show each-other's positions with + line indicators. By default this is derived from ``id(volume)``. +* `color` (`str`): the color for this slicer. By default the color is + red, green, or blue, depending on the axis. Set to empty string + for "no color". + +Note that this is not a Dash component. The components that make +up the slicer (and which must be present in the layout) are: +`slicer.graph`, `slicer.slider`, and `slicer.stores`. + + +**method `VolumeSlicer.create_overlay_data(mask, color=None)`** + +Given a 3D mask array and an index, create an object that +can be used as output for ``slicer.overlay_data``. The color +can be a hex color or an rgb/rgba tuple. Alternatively, color +can be a list of such colors, defining a colormap. + + +**property `VolumeSlicer.axis`**: The axis at which the slicer is slicing. + +**property `VolumeSlicer.graph`**: The dcc.Graph for this slicer. Use ``graph.figure`` to access the +Plotly Figure object. + + +**property `VolumeSlicer.nslices`**: The number of slices for this slicer. + +**property `VolumeSlicer.overlay_data`**: A dcc.Store containing the overlay data. The form of this +data is considered an implementation detail; users are expected to use +``create_overlay_data`` to create it. + + +**property `VolumeSlicer.scene_id`** str: The id of the "virtual scene" for this slicer. Slicers that have +the same scene_id show each-other's positions. + + +**property `VolumeSlicer.slider`**: The `dcc.Slider` to change the index for this slicer. If you +don't want to use the slider, wrap it in a div with style +``display: none``. + + +**property `VolumeSlicer.state`**: A dcc.Store representing the current state of the slicer (present +in slicer.stores). Its data is a dict with the fields: index (int), +index_changed (bool), xrange (2 floats), yrange (2 floats), +zpos (float), axis (int), color (str). + +Its id is a dictionary so it can be used in a pattern matching Input. +Fields: context, scene, name. Where scene is the scene_id and name is "state". + + +**property `VolumeSlicer.stores`**: A list of dcc.Store objects that the slicer needs to work. +These must be added to the app layout. + + + + +### Reacting to slicer state + +It is possible to get notified of updates to slicer position and +view ranges. To get this for all slicers with a specific scene_id, create +a pattern matching input like this: +```py +Input({"scene": scene_id, "context": ALL, "name": "state"}) +``` + +These state values are objects with fields: + +* "index": the integer slice index. +* "index_changed": a bool indicating whether the index changed since last time. +* "xrange": the view range (2 floats) in the x-dimension (2D). +* "yrange": the view range (2 floats) in the y-dimension (2D). +* "zpos": the float position aling the axis, in scene coordinates. +* "axis": the axis (int) for this slicer. +* "color": the color (str) for this slicer. + + +### Setting slicer positions + +To programatically set the position of the slicer, create a `dcc.Store` with +a dictionary-id that has the following fields: + +* 'context': a unique name for this store. +* 'scene': the scene_id of the slicer objects to set the position for. +* 'name': 'setpos' + +The value in the store must be an 3-element tuple (x, y, z) in scene coordinates. +To apply the position for one dimension only, use e.g ``(None, None, x)``. + + +### Performance tips + +* Most importantly, when running the server in debug mode, consider setting + `dev_tools_props_check=False`. +* Also consider creating the `Dash` application with `update_title=None`. +* Setting `reverse_y` to False negatively affects performance. This will be + fixed in a future version of Plotly/Dash. +* For a smooth experience, avoid triggering unnecessary figure updates. +* When adding a callback that uses the slicer position, use the (rate limited) + `state` store rather than the slider value. + diff --git a/dash_slicer/docs.py b/dash_slicer/docs.py new file mode 100644 index 0000000..9ab0f61 --- /dev/null +++ b/dash_slicer/docs.py @@ -0,0 +1,86 @@ +import os +import inspect + +import dash_slicer + +HERE = os.path.dirname(os.path.abspath(__file__)) + + +def dedent(text): + """Dedent a docstring, removing leading whitespace.""" + lines = text.lstrip().splitlines() + min_indent = 9999 + for i in range(1, len(lines)): + line1 = lines[i] + line2 = line1.lstrip() + if line2: + indent = len(lines[i]) - len(lines[i].lstrip()) + min_indent = min(min_indent, indent) + if min_indent > 16: + min_indent = 0 + for i in range(1, len(lines)): + lines[i] = lines[i][min_indent:] + return "\n".join(lines) + + +def get_reference_docs(): + """Create reference documentation from the source code. + A bit like Sphinx autodoc, but using Markdown, and more basic. + Returns a str in Markdown format. + + Note that this function is used to build the Dash Slicer chapter + in the Dash docs. + """ + + methods = [] + props = [] + + sig = str(inspect.signature(dash_slicer.VolumeSlicer.__init__)).replace("self, ", "") + doc = f"**class `VolumeSlicer{sig}`**" + doc += "\n\n" + dedent(dash_slicer.VolumeSlicer.__doc__) + methods.append(doc) + + for name in dir(dash_slicer.VolumeSlicer): + val = getattr(dash_slicer.VolumeSlicer, name) + + if name.startswith("_") or not hasattr(val, "__doc__"): + pass + elif callable(val): + # Method + sig = str(inspect.signature(val)).replace("self, ", "") + doc = f"**method `VolumeSlicer.{name}{sig}`**" + doc += "\n\n" + dedent(val.__doc__) + methods.append(doc) + else: + # Property + doc = f"**property `VolumeSlicer.{name}`**" + try: + doc += " " + val.fget.__annotations__["return"].__name__ + except (AttributeError, KeyError): + pass + doc += ": " + dedent(val.__doc__) + props.append(doc) + + parts = [] + parts.append("### The VolumeSlicer class") + parts += methods + parts += props + parts.append(dash_slicer.slicer.__doc__) + return "\n\n".join(parts) + + +def write_reference_docs(): + """Write the reference docs to the README.""" + header = "## Reference" + filename = os.path.join(os.path.dirname(HERE), "README.md") + assert os.path.isfile(filename), "README.md not found" + with open(filename, "rt", encoding="utf-8") as f: + text = f.read() + text, _, _ = text.partition(header) + text = text.rstrip() + "\n\n\n" + header + "\n\n" + get_reference_docs() + with open(filename, "wt", encoding="utf-8") as f: + f.write(text) + + +if __name__ == "__main__": + write_reference_docs() diff --git a/dash_slicer/slicer.py b/dash_slicer/slicer.py index 2002242..a494caf 100644 --- a/dash_slicer/slicer.py +++ b/dash_slicer/slicer.py @@ -1,3 +1,53 @@ +# The docstring below is used as part of the reference docs. + +""" + +### Reacting to slicer state + +It is possible to get notified of updates to slicer position and +view ranges. To get this for all slicers with a specific scene_id, create +a pattern matching input like this: +```py +Input({"scene": scene_id, "context": ALL, "name": "state"}) +``` + +These state values are objects with fields: + +* "index": the integer slice index. +* "index_changed": a bool indicating whether the index changed since last time. +* "xrange": the view range (2 floats) in the x-dimension (2D). +* "yrange": the view range (2 floats) in the y-dimension (2D). +* "zpos": the float position aling the axis, in scene coordinates. +* "axis": the axis (int) for this slicer. +* "color": the color (str) for this slicer. + + +### Setting slicer positions + +To programatically set the position of the slicer, create a `dcc.Store` with +a dictionary-id that has the following fields: + +* 'context': a unique name for this store. +* 'scene': the scene_id of the slicer objects to set the position for. +* 'name': 'setpos' + +The value in the store must be an 3-element tuple (x, y, z) in scene coordinates. +To apply the position for one dimension only, use e.g ``(None, None, x)``. + + +### Performance tips + +* Most importantly, when running the server in debug mode, consider setting + `dev_tools_props_check=False`. +* Also consider creating the `Dash` application with `update_title=None`. +* Setting `reverse_y` to False negatively affects performance. This will be + fixed in a future version of Plotly/Dash. +* For a smooth experience, avoid triggering unnecessary figure updates. +* When adding a callback that uses the slicer position, use the (rate limited) + `state` store rather than the slider value. + +""" + import numpy as np import plotly.graph_objects import dash @@ -14,60 +64,35 @@ class VolumeSlicer: - """A slicer to show 3D image data in Dash. - - Parameters: - app (dash.Dash): the Dash application instance. - volume (ndarray): the 3D numpy array to slice through. The dimensions - are assumed to be in zyx order. If this is not the case, you can - use ``np.swapaxes`` to make it so. - spacing (tuple of floats): The distance between voxels for each - dimension (zyx).The spacing and origin are applied to make the slice - drawn in "scene space" rather than "voxel space". - origin (tuple of floats): The offset for each dimension (zyx). - axis (int): the dimension to slice in. Default 0. - reverse_y (bool): Whether to reverse the y-axis, so that the origin of - the slice is in the top-left, rather than bottom-left. Default True. - Note: setting this to False affects performance, see #12. - scene_id (str): the scene that this slicer is part of. Slicers - that have the same scene-id show each-other's positions with - line indicators. By default this is derived from ``id(volume)``. - color (str): the color for this slicer. By default the color is - red, green, or blue, depending on the axis. Set to empty string - for "no color". - thumbnail (int or bool): linear size of low-resolution data to be - uploaded to the client. If ``False``, the full-resolution data are - uploaded client-side. If ``True`` (default), a default value of 32 is - used. - - This is a placeholder object, not a Dash component. The components - that make up the slicer can be accessed as attributes. These must all - be present in the app layout: - - * ``graph``: the dcc.Graph object. Use ``graph.figure`` to access the - Plotly Figure object. - * ``slider``: the dcc.Slider object, its value represents the slice - index. If you don't want to use the slider, wrap it in a div with - style ``display: none``. - * ``stores``: a list of dcc.Store objects. - - To programatically set the position of the slicer, use a store with - a dictionary-id with the following fields: - - * 'context': a unique name for this store. - * 'scene': the scene_id for which to set the position - * 'name': 'setpos' - - The value in the store must be an 3-element tuple (x, y, z) in scene coordinates. - To apply the position for one position only, use e.g ``(None, None, x)``. - - Some notes on performance: for a smooth experience, avoid triggering - unnecessary figure updates. When adding a callback that uses the - slicer position, use the (rate limited) `index` and `pos` stores - rather than the slider value. Further, create the `Dash` application - with `update_title=None`, and when running the server in debug mode, - consider setting `dev_tools_props_check=False`. - + """A slicer object to show 3D image data in Dash. Upon + instantiation one can provide the following parameters: + + * `app` (`dash.Dash`): the Dash application instance. + * `volume` (`ndarray`): the 3D numpy array to slice through. The dimensions + are assumed to be in zyx order. If this is not the case, you can + use ``np.swapaxes`` to make it so. + * `spacing` (tuple of `float`): The distance between voxels for each + dimension (zyx).The spacing and origin are applied to make the slice + drawn in "scene space" rather than "voxel space". + * `origin` (tuple of `float`): The offset for each dimension (zyx). + * `axis` (`int`): the dimension to slice in. Default 0. + * `reverse_y` (`bool`): Whether to reverse the y-axis, so that the origin of + the slice is in the top-left, rather than bottom-left. Default True. + Note: setting this to False affects performance, see #12. + * `scene_id` (`str`): the scene that this slicer is part of. Slicers + that have the same scene-id show each-other's positions with + line indicators. By default this is derived from ``id(volume)``. + * `color` (`str`): the color for this slicer. By default the color is + red, green, or blue, depending on the axis. Set to empty string + for "no color". + * thumbnail (int or bool): linear size of low-resolution data to be + uploaded to the client. If ``False``, the full-resolution data are + uploaded client-side. If ``True`` (default), a default value of 32 is + used. + + Note that this is not a Dash component. The components that make + up the slicer (and which must be present in the layout) are: + `slicer.graph`, `slicer.slider`, and `slicer.stores`. """ _global_slicer_counter = 0 @@ -152,7 +177,7 @@ def __init__( # Note(AK): we could make some stores public, but let's do this only when actual use-cases arise? @property - def scene_id(self): + def scene_id(self) -> str: """The id of the "virtual scene" for this slicer. Slicers that have the same scene_id show each-other's positions. """ @@ -170,12 +195,17 @@ def nslices(self): @property def graph(self): - """The dcc.Graph for this slicer.""" + """The dcc.Graph for this slicer. Use ``graph.figure`` to access the + Plotly Figure object. + """ return self._graph @property def slider(self): - """The dcc.Slider to change the index for this slicer.""" + """The `dcc.Slider` to change the index for this slicer. If you + don't want to use the slider, wrap it in a div with style + ``display: none``. + """ return self._slider @property diff --git a/tests/test_docs.py b/tests/test_docs.py new file mode 100644 index 0000000..168f349 --- /dev/null +++ b/tests/test_docs.py @@ -0,0 +1,26 @@ +import os + +from dash_slicer.docs import get_reference_docs + + +HERE = os.path.dirname(os.path.abspath(__file__)) + + +def test_that_the_docs_build(): + x = get_reference_docs() + assert "VolumeSlicer(app, vol" in x + assert "create_overlay_data(mask" in x + assert "performance" in x.lower() + + +def test_that_docs_are_up_to_date(): + filename = os.path.join(os.path.dirname(HERE), "README.md") + assert os.path.isfile(filename) + with open(filename, "rt", encoding="utf-8") as f: + text = f.read() + _, _, ref = text.partition("## Reference") + ref1 = ref.strip() + ref2 = get_reference_docs().strip() + assert ( + ref1 == ref2 + ), "Reference docs in readme are outdated. Run `python dash_slicer/docs.py`" From e64303519eede5abd641d0dfd69ebc3e91f7c68a Mon Sep 17 00:00:00 2001 From: Almar Klein Date: Tue, 8 Dec 2020 16:08:39 +0100 Subject: [PATCH 2/8] prevent newline handling --- README.md | 254 ++++++++++++++++++++++---------------------- dash_slicer/docs.py | 13 ++- tests/test_docs.py | 4 +- 3 files changed, 139 insertions(+), 132 deletions(-) diff --git a/README.md b/README.md index 8c01393..a92602b 100644 --- a/README.md +++ b/README.md @@ -57,128 +57,132 @@ This code is distributed under MIT license. On every PR, an app with the same name as your branch is deployed to the Dash playground instance so that you can change whether your changes did not break -the package. - - -## Reference - -### The VolumeSlicer class - -**class `VolumeSlicer(app, volume, *, spacing=None, origin=None, axis=0, reverse_y=True, scene_id=None, color=None)`** - -A slicer object to show 3D image data in Dash. Upon -instantiation one can provide the following parameters: - -* `app` (`dash.Dash`): the Dash application instance. -* `volume` (`ndarray`): the 3D numpy array to slice through. The dimensions - are assumed to be in zyx order. If this is not the case, you can - use ``np.swapaxes`` to make it so. -* `spacing` (tuple of `float`): The distance between voxels for each - dimension (zyx).The spacing and origin are applied to make the slice - drawn in "scene space" rather than "voxel space". -* `origin` (tuple of `float`): The offset for each dimension (zyx). -* `axis` (`int`): the dimension to slice in. Default 0. -* `reverse_y` (`bool`): Whether to reverse the y-axis, so that the origin of - the slice is in the top-left, rather than bottom-left. Default True. - Note: setting this to False affects performance, see #12. -* `scene_id` (`str`): the scene that this slicer is part of. Slicers - that have the same scene-id show each-other's positions with - line indicators. By default this is derived from ``id(volume)``. -* `color` (`str`): the color for this slicer. By default the color is - red, green, or blue, depending on the axis. Set to empty string - for "no color". - -Note that this is not a Dash component. The components that make -up the slicer (and which must be present in the layout) are: -`slicer.graph`, `slicer.slider`, and `slicer.stores`. - - -**method `VolumeSlicer.create_overlay_data(mask, color=None)`** - -Given a 3D mask array and an index, create an object that -can be used as output for ``slicer.overlay_data``. The color -can be a hex color or an rgb/rgba tuple. Alternatively, color -can be a list of such colors, defining a colormap. - - -**property `VolumeSlicer.axis`**: The axis at which the slicer is slicing. - -**property `VolumeSlicer.graph`**: The dcc.Graph for this slicer. Use ``graph.figure`` to access the -Plotly Figure object. - - -**property `VolumeSlicer.nslices`**: The number of slices for this slicer. - -**property `VolumeSlicer.overlay_data`**: A dcc.Store containing the overlay data. The form of this -data is considered an implementation detail; users are expected to use -``create_overlay_data`` to create it. - - -**property `VolumeSlicer.scene_id`** str: The id of the "virtual scene" for this slicer. Slicers that have -the same scene_id show each-other's positions. - - -**property `VolumeSlicer.slider`**: The `dcc.Slider` to change the index for this slicer. If you -don't want to use the slider, wrap it in a div with style -``display: none``. - - -**property `VolumeSlicer.state`**: A dcc.Store representing the current state of the slicer (present -in slicer.stores). Its data is a dict with the fields: index (int), -index_changed (bool), xrange (2 floats), yrange (2 floats), -zpos (float), axis (int), color (str). - -Its id is a dictionary so it can be used in a pattern matching Input. -Fields: context, scene, name. Where scene is the scene_id and name is "state". - - -**property `VolumeSlicer.stores`**: A list of dcc.Store objects that the slicer needs to work. -These must be added to the app layout. - - - - -### Reacting to slicer state - -It is possible to get notified of updates to slicer position and -view ranges. To get this for all slicers with a specific scene_id, create -a pattern matching input like this: -```py -Input({"scene": scene_id, "context": ALL, "name": "state"}) -``` - -These state values are objects with fields: - -* "index": the integer slice index. -* "index_changed": a bool indicating whether the index changed since last time. -* "xrange": the view range (2 floats) in the x-dimension (2D). -* "yrange": the view range (2 floats) in the y-dimension (2D). -* "zpos": the float position aling the axis, in scene coordinates. -* "axis": the axis (int) for this slicer. -* "color": the color (str) for this slicer. - - -### Setting slicer positions - -To programatically set the position of the slicer, create a `dcc.Store` with -a dictionary-id that has the following fields: - -* 'context': a unique name for this store. -* 'scene': the scene_id of the slicer objects to set the position for. -* 'name': 'setpos' - -The value in the store must be an 3-element tuple (x, y, z) in scene coordinates. -To apply the position for one dimension only, use e.g ``(None, None, x)``. - - -### Performance tips - -* Most importantly, when running the server in debug mode, consider setting - `dev_tools_props_check=False`. -* Also consider creating the `Dash` application with `update_title=None`. -* Setting `reverse_y` to False negatively affects performance. This will be - fixed in a future version of Plotly/Dash. -* For a smooth experience, avoid triggering unnecessary figure updates. -* When adding a callback that uses the slicer position, use the (rate limited) - `state` store rather than the slider value. - +the package. + + +## Reference + +### The VolumeSlicer class + +**class `VolumeSlicer(app, volume, *, spacing=None, origin=None, axis=0, reverse_y=True, scene_id=None, color=None, thumbnail=True)`** + +A slicer object to show 3D image data in Dash. Upon +instantiation one can provide the following parameters: + +* `app` (`dash.Dash`): the Dash application instance. +* `volume` (`ndarray`): the 3D numpy array to slice through. The dimensions + are assumed to be in zyx order. If this is not the case, you can + use ``np.swapaxes`` to make it so. +* `spacing` (tuple of `float`): The distance between voxels for each + dimension (zyx).The spacing and origin are applied to make the slice + drawn in "scene space" rather than "voxel space". +* `origin` (tuple of `float`): The offset for each dimension (zyx). +* `axis` (`int`): the dimension to slice in. Default 0. +* `reverse_y` (`bool`): Whether to reverse the y-axis, so that the origin of + the slice is in the top-left, rather than bottom-left. Default True. + Note: setting this to False affects performance, see #12. +* `scene_id` (`str`): the scene that this slicer is part of. Slicers + that have the same scene-id show each-other's positions with + line indicators. By default this is derived from ``id(volume)``. +* `color` (`str`): the color for this slicer. By default the color is + red, green, or blue, depending on the axis. Set to empty string + for "no color". +* thumbnail (int or bool): linear size of low-resolution data to be + uploaded to the client. If ``False``, the full-resolution data are + uploaded client-side. If ``True`` (default), a default value of 32 is + used. + +Note that this is not a Dash component. The components that make +up the slicer (and which must be present in the layout) are: +`slicer.graph`, `slicer.slider`, and `slicer.stores`. + + +**method `VolumeSlicer.create_overlay_data(mask, color=None)`** + +Given a 3D mask array and an index, create an object that +can be used as output for ``slicer.overlay_data``. The color +can be a hex color or an rgb/rgba tuple. Alternatively, color +can be a list of such colors, defining a colormap. + + +**property `VolumeSlicer.axis`**: The axis at which the slicer is slicing. + +**property `VolumeSlicer.graph`**: The dcc.Graph for this slicer. Use ``graph.figure`` to access the +Plotly Figure object. + + +**property `VolumeSlicer.nslices`**: The number of slices for this slicer. + +**property `VolumeSlicer.overlay_data`**: A dcc.Store containing the overlay data. The form of this +data is considered an implementation detail; users are expected to use +``create_overlay_data`` to create it. + + +**property `VolumeSlicer.scene_id`** str: The id of the "virtual scene" for this slicer. Slicers that have +the same scene_id show each-other's positions. + + +**property `VolumeSlicer.slider`**: The `dcc.Slider` to change the index for this slicer. If you +don't want to use the slider, wrap it in a div with style +``display: none``. + + +**property `VolumeSlicer.state`**: A dcc.Store representing the current state of the slicer (present +in slicer.stores). Its data is a dict with the fields: index (int), +index_changed (bool), xrange (2 floats), yrange (2 floats), +zpos (float), axis (int), color (str). + +Its id is a dictionary so it can be used in a pattern matching Input. +Fields: context, scene, name. Where scene is the scene_id and name is "state". + + +**property `VolumeSlicer.stores`**: A list of dcc.Store objects that the slicer needs to work. +These must be added to the app layout. + + + + +### Reacting to slicer state + +It is possible to get notified of updates to slicer position and +view ranges. To get this for all slicers with a specific scene_id, create +a pattern matching input like this: +```py +Input({"scene": scene_id, "context": ALL, "name": "state"}) +``` + +These state values are objects with fields: + +* "index": the integer slice index. +* "index_changed": a bool indicating whether the index changed since last time. +* "xrange": the view range (2 floats) in the x-dimension (2D). +* "yrange": the view range (2 floats) in the y-dimension (2D). +* "zpos": the float position aling the axis, in scene coordinates. +* "axis": the axis (int) for this slicer. +* "color": the color (str) for this slicer. + + +### Setting slicer positions + +To programatically set the position of the slicer, create a `dcc.Store` with +a dictionary-id that has the following fields: + +* 'context': a unique name for this store. +* 'scene': the scene_id of the slicer objects to set the position for. +* 'name': 'setpos' + +The value in the store must be an 3-element tuple (x, y, z) in scene coordinates. +To apply the position for one dimension only, use e.g ``(None, None, x)``. + + +### Performance tips + +* Most importantly, when running the server in debug mode, consider setting + `dev_tools_props_check=False`. +* Also consider creating the `Dash` application with `update_title=None`. +* Setting `reverse_y` to False negatively affects performance. This will be + fixed in a future version of Plotly/Dash. +* For a smooth experience, avoid triggering unnecessary figure updates. +* When adding a callback that uses the slicer position, use the (rate limited) + `state` store rather than the slider value. + diff --git a/dash_slicer/docs.py b/dash_slicer/docs.py index 9ab0f61..5bb3bcd 100644 --- a/dash_slicer/docs.py +++ b/dash_slicer/docs.py @@ -35,7 +35,9 @@ def get_reference_docs(): methods = [] props = [] - sig = str(inspect.signature(dash_slicer.VolumeSlicer.__init__)).replace("self, ", "") + sig = str(inspect.signature(dash_slicer.VolumeSlicer.__init__)).replace( + "self, ", "" + ) doc = f"**class `VolumeSlicer{sig}`**" doc += "\n\n" + dedent(dash_slicer.VolumeSlicer.__doc__) methods.append(doc) @@ -74,12 +76,13 @@ def write_reference_docs(): header = "## Reference" filename = os.path.join(os.path.dirname(HERE), "README.md") assert os.path.isfile(filename), "README.md not found" - with open(filename, "rt", encoding="utf-8") as f: - text = f.read() + with open(filename, "rb") as f: + text = f.read().decode() text, _, _ = text.partition(header) text = text.rstrip() + "\n\n\n" + header + "\n\n" + get_reference_docs() - with open(filename, "wt", encoding="utf-8") as f: - f.write(text) + with open(filename, "wb") as f: + f.write(text.encode()) + print("Wrote to README.md") if __name__ == "__main__": diff --git a/tests/test_docs.py b/tests/test_docs.py index 168f349..e9e46f5 100644 --- a/tests/test_docs.py +++ b/tests/test_docs.py @@ -16,8 +16,8 @@ def test_that_the_docs_build(): def test_that_docs_are_up_to_date(): filename = os.path.join(os.path.dirname(HERE), "README.md") assert os.path.isfile(filename) - with open(filename, "rt", encoding="utf-8") as f: - text = f.read() + with open(filename, "rb") as f: + text = f.read().decode() _, _, ref = text.partition("## Reference") ref1 = ref.strip() ref2 = get_reference_docs().strip() From b14bd9688c4ddee7273f65e11319ce0f50df316a Mon Sep 17 00:00:00 2001 From: Almar Klein Date: Tue, 8 Dec 2020 16:27:55 +0100 Subject: [PATCH 3/8] tweaks and fixes --- README.md | 69 ++++++++++++++++++++----------------------- dash_slicer/docs.py | 13 +++++--- dash_slicer/slicer.py | 68 ++++++++++++++++++++++-------------------- 3 files changed, 77 insertions(+), 73 deletions(-) diff --git a/README.md b/README.md index a92602b..55ff364 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ instantiation one can provide the following parameters: * `app` (`dash.Dash`): the Dash application instance. * `volume` (`ndarray`): the 3D numpy array to slice through. The dimensions are assumed to be in zyx order. If this is not the case, you can - use ``np.swapaxes`` to make it so. + use `np.swapaxes` to make it so. * `spacing` (tuple of `float`): The distance between voxels for each dimension (zyx).The spacing and origin are applied to make the slice drawn in "scene space" rather than "voxel space". @@ -83,65 +83,64 @@ instantiation one can provide the following parameters: Note: setting this to False affects performance, see #12. * `scene_id` (`str`): the scene that this slicer is part of. Slicers that have the same scene-id show each-other's positions with - line indicators. By default this is derived from ``id(volume)``. + line indicators. By default this is derived from `id(volume)`. * `color` (`str`): the color for this slicer. By default the color is red, green, or blue, depending on the axis. Set to empty string for "no color". -* thumbnail (int or bool): linear size of low-resolution data to be - uploaded to the client. If ``False``, the full-resolution data are - uploaded client-side. If ``True`` (default), a default value of 32 is +* thumbnail (`int` or `bool`): linear size of low-resolution data to be + uploaded to the client. If `False`, the full-resolution data are + uploaded client-side. If `True` (default), a default value of 32 is used. Note that this is not a Dash component. The components that make up the slicer (and which must be present in the layout) are: `slicer.graph`, `slicer.slider`, and `slicer.stores`. - **method `VolumeSlicer.create_overlay_data(mask, color=None)`** Given a 3D mask array and an index, create an object that -can be used as output for ``slicer.overlay_data``. The color +can be used as output for `slicer.overlay_data`. The color can be a hex color or an rgb/rgba tuple. Alternatively, color can be a list of such colors, defining a colormap. +**property `VolumeSlicer.axis`** (`int`): The axis at wich the slicer is slicing. -**property `VolumeSlicer.axis`**: The axis at which the slicer is slicing. - -**property `VolumeSlicer.graph`**: The dcc.Graph for this slicer. Use ``graph.figure`` to access the +**property `VolumeSlicer.graph`**: The `dcc.Graph` for this slicer. Use `graph.figure` to access the Plotly Figure object. +**property `VolumeSlicer.nslices`** (`int`): The number of slices for this slicer. -**property `VolumeSlicer.nslices`**: The number of slices for this slicer. - -**property `VolumeSlicer.overlay_data`**: A dcc.Store containing the overlay data. The form of this +**property `VolumeSlicer.overlay_data`**: A `dcc.Store` containing the overlay data. The form of this data is considered an implementation detail; users are expected to use -``create_overlay_data`` to create it. +`create_overlay_data` to create it. - -**property `VolumeSlicer.scene_id`** str: The id of the "virtual scene" for this slicer. Slicers that have +**property `VolumeSlicer.scene_id`** (`str`): The id of the "virtual scene" for this slicer. Slicers that have the same scene_id show each-other's positions. - **property `VolumeSlicer.slider`**: The `dcc.Slider` to change the index for this slicer. If you don't want to use the slider, wrap it in a div with style -``display: none``. - +`display: none`. -**property `VolumeSlicer.state`**: A dcc.Store representing the current state of the slicer (present -in slicer.stores). Its data is a dict with the fields: index (int), -index_changed (bool), xrange (2 floats), yrange (2 floats), -zpos (float), axis (int), color (str). +**property `VolumeSlicer.state`**: A `dcc.Store` representing the current state of the slicer (present +in slicer.stores). Its data is a dict with the fields: -Its id is a dictionary so it can be used in a pattern matching Input. -Fields: context, scene, name. Where scene is the scene_id and name is "state". +* "index": the integer slice index. +* "index_changed": a bool indicating whether the index changed since last time. +* "xrange": the view range (2 floats) in the x-dimension (2D). +* "yrange": the view range (2 floats) in the y-dimension (2D). +* "zpos": the float position aling the axis, in scene coordinates. +* "axis": the axis (int) for this slicer. +* "color": the color (str) for this slicer. +The id of the store is a dictionary so it can be used in a +pattern matching Input. Its field are: context, scene, name. +Where scene is the scene_id and name is "state". -**property `VolumeSlicer.stores`**: A list of dcc.Store objects that the slicer needs to work. +**property `VolumeSlicer.stores`**: A list of `dcc.Store` objects that the slicer needs to work. These must be added to the app layout. - ### Reacting to slicer state It is possible to get notified of updates to slicer position and @@ -151,15 +150,7 @@ a pattern matching input like this: Input({"scene": scene_id, "context": ALL, "name": "state"}) ``` -These state values are objects with fields: - -* "index": the integer slice index. -* "index_changed": a bool indicating whether the index changed since last time. -* "xrange": the view range (2 floats) in the x-dimension (2D). -* "yrange": the view range (2 floats) in the y-dimension (2D). -* "zpos": the float position aling the axis, in scene coordinates. -* "axis": the axis (int) for this slicer. -* "color": the color (str) for this slicer. +See the `state` properties for details. ### Setting slicer positions @@ -172,11 +163,15 @@ a dictionary-id that has the following fields: * 'name': 'setpos' The value in the store must be an 3-element tuple (x, y, z) in scene coordinates. -To apply the position for one dimension only, use e.g ``(None, None, x)``. +To apply the position for one dimension only, use e.g `(None, None, x)`. ### Performance tips +There tends to be a lot of interaction in an application that contains +slicer objects. Therefore, performance matters to realize a smooth user +experience. Here are some tips to help with that: + * Most importantly, when running the server in debug mode, consider setting `dev_tools_props_check=False`. * Also consider creating the `Dash` application with `update_title=None`. diff --git a/dash_slicer/docs.py b/dash_slicer/docs.py index 5bb3bcd..799ba69 100644 --- a/dash_slicer/docs.py +++ b/dash_slicer/docs.py @@ -1,3 +1,7 @@ +""" +Utilities to produce simple reference docs (in Markdown) from the source code. +""" + import os import inspect @@ -39,7 +43,7 @@ def get_reference_docs(): "self, ", "" ) doc = f"**class `VolumeSlicer{sig}`**" - doc += "\n\n" + dedent(dash_slicer.VolumeSlicer.__doc__) + doc += "\n\n" + dedent(dash_slicer.VolumeSlicer.__doc__).rstrip() methods.append(doc) for name in dir(dash_slicer.VolumeSlicer): @@ -51,16 +55,17 @@ def get_reference_docs(): # Method sig = str(inspect.signature(val)).replace("self, ", "") doc = f"**method `VolumeSlicer.{name}{sig}`**" - doc += "\n\n" + dedent(val.__doc__) + doc += "\n\n" + dedent(val.__doc__).rstrip() methods.append(doc) else: # Property doc = f"**property `VolumeSlicer.{name}`**" try: - doc += " " + val.fget.__annotations__["return"].__name__ + typ = val.fget.__annotations__["return"].__name__ + doc += f" (`{typ}`)" except (AttributeError, KeyError): pass - doc += ": " + dedent(val.__doc__) + doc += ": " + dedent(val.__doc__).rstrip() props.append(doc) parts = [] diff --git a/dash_slicer/slicer.py b/dash_slicer/slicer.py index a494caf..6b35691 100644 --- a/dash_slicer/slicer.py +++ b/dash_slicer/slicer.py @@ -1,4 +1,5 @@ -# The docstring below is used as part of the reference docs. +# The docstring below is used as part of the reference docs. It describes +# the parts that cannot be described well via the properties and methods. """ @@ -11,15 +12,7 @@ Input({"scene": scene_id, "context": ALL, "name": "state"}) ``` -These state values are objects with fields: - -* "index": the integer slice index. -* "index_changed": a bool indicating whether the index changed since last time. -* "xrange": the view range (2 floats) in the x-dimension (2D). -* "yrange": the view range (2 floats) in the y-dimension (2D). -* "zpos": the float position aling the axis, in scene coordinates. -* "axis": the axis (int) for this slicer. -* "color": the color (str) for this slicer. +See the `state` properties for details. ### Setting slicer positions @@ -32,11 +25,15 @@ * 'name': 'setpos' The value in the store must be an 3-element tuple (x, y, z) in scene coordinates. -To apply the position for one dimension only, use e.g ``(None, None, x)``. +To apply the position for one dimension only, use e.g `(None, None, x)`. ### Performance tips +There tends to be a lot of interaction in an application that contains +slicer objects. Therefore, performance matters to realize a smooth user +experience. Here are some tips to help with that: + * Most importantly, when running the server in debug mode, consider setting `dev_tools_props_check=False`. * Also consider creating the `Dash` application with `update_title=None`. @@ -70,7 +67,7 @@ class VolumeSlicer: * `app` (`dash.Dash`): the Dash application instance. * `volume` (`ndarray`): the 3D numpy array to slice through. The dimensions are assumed to be in zyx order. If this is not the case, you can - use ``np.swapaxes`` to make it so. + use `np.swapaxes` to make it so. * `spacing` (tuple of `float`): The distance between voxels for each dimension (zyx).The spacing and origin are applied to make the slice drawn in "scene space" rather than "voxel space". @@ -81,13 +78,13 @@ class VolumeSlicer: Note: setting this to False affects performance, see #12. * `scene_id` (`str`): the scene that this slicer is part of. Slicers that have the same scene-id show each-other's positions with - line indicators. By default this is derived from ``id(volume)``. + line indicators. By default this is derived from `id(volume)`. * `color` (`str`): the color for this slicer. By default the color is red, green, or blue, depending on the axis. Set to empty string for "no color". - * thumbnail (int or bool): linear size of low-resolution data to be - uploaded to the client. If ``False``, the full-resolution data are - uploaded client-side. If ``True`` (default), a default value of 32 is + * thumbnail (`int` or `bool`): linear size of low-resolution data to be + uploaded to the client. If `False`, the full-resolution data are + uploaded client-side. If `True` (default), a default value of 32 is used. Note that this is not a Dash component. The components that make @@ -184,18 +181,18 @@ def scene_id(self) -> str: return self._scene_id @property - def axis(self): - """The axis at which the slicer is slicing.""" + def axis(self) -> int: + """The axis at wich the slicer is slicing.""" return self._axis @property - def nslices(self): + def nslices(self) -> int: """The number of slices for this slicer.""" return self._volume.shape[self._axis] @property def graph(self): - """The dcc.Graph for this slicer. Use ``graph.figure`` to access the + """The `dcc.Graph` for this slicer. Use `graph.figure` to access the Plotly Figure object. """ return self._graph @@ -204,40 +201,47 @@ def graph(self): def slider(self): """The `dcc.Slider` to change the index for this slicer. If you don't want to use the slider, wrap it in a div with style - ``display: none``. + `display: none`. """ return self._slider @property def stores(self): - """A list of dcc.Store objects that the slicer needs to work. + """A list of `dcc.Store` objects that the slicer needs to work. These must be added to the app layout. """ return self._stores @property def state(self): - """A dcc.Store representing the current state of the slicer (present - in slicer.stores). Its data is a dict with the fields: index (int), - index_changed (bool), xrange (2 floats), yrange (2 floats), - zpos (float), axis (int), color (str). - - Its id is a dictionary so it can be used in a pattern matching Input. - Fields: context, scene, name. Where scene is the scene_id and name is "state". + """A `dcc.Store` representing the current state of the slicer (present + in slicer.stores). Its data is a dict with the fields: + + * "index": the integer slice index. + * "index_changed": a bool indicating whether the index changed since last time. + * "xrange": the view range (2 floats) in the x-dimension (2D). + * "yrange": the view range (2 floats) in the y-dimension (2D). + * "zpos": the float position aling the axis, in scene coordinates. + * "axis": the axis (int) for this slicer. + * "color": the color (str) for this slicer. + + The id of the store is a dictionary so it can be used in a + pattern matching Input. Its field are: context, scene, name. + Where scene is the scene_id and name is "state". """ return self._state @property def overlay_data(self): - """A dcc.Store containing the overlay data. The form of this + """A `dcc.Store` containing the overlay data. The form of this data is considered an implementation detail; users are expected to use - ``create_overlay_data`` to create it. + `create_overlay_data` to create it. """ return self._overlay_data def create_overlay_data(self, mask, color=None): """Given a 3D mask array and an index, create an object that - can be used as output for ``slicer.overlay_data``. The color + can be used as output for `slicer.overlay_data`. The color can be a hex color or an rgb/rgba tuple. Alternatively, color can be a list of such colors, defining a colormap. """ From 438b8bd1d048b3b4b61e8afcdbc358c51802e895 Mon Sep 17 00:00:00 2001 From: Almar Klein Date: Tue, 8 Dec 2020 16:32:15 +0100 Subject: [PATCH 4/8] newlines --- README.md | 118 +++++++++++++++++++++++++++--------------------------- 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index 55ff364..f289244 100644 --- a/README.md +++ b/README.md @@ -1,62 +1,62 @@ -![CI](https://github.com/pygfx/wgpu-py/workflows/CI/badge.svg) - -# dash_slicer - -A volume slicer for Dash - - -## Status - -This work is marked as alpha - some essential features are still in -development, and some parts of the API may change in future releases. - - -## Installation - -``` -$ pip install dash-slicer -``` - -Dash-slicer depends on Python 3.6+ plus some [dependencies](requirements.txt). - - -## Usage example - -```py -import dash -import dash_html_components as html -from dash_slicer import VolumeSlicer -import imageio - -app = dash.Dash(__name__) - -vol = imageio.volread("imageio:stent.npz") -slicer = VolumeSlicer(app, vol) -app.layout = html.Div([slicer.graph, slicer.slider, *slicer.stores]) - -if __name__ == "__main__": - app.run_server() -``` - - -## License - -This code is distributed under MIT license. - - -## Developers - - -* Make sure that you have Python with the appropriate dependencies installed, e.g. via `venv`. -* Run `pip install -e .` to do an in-place install of the package. -* Run the examples using e.g. `python examples/slicer_with_1_view.py` - -* Use `black .` to autoformat. -* Use `flake8 .` to lint. -* Use `pytest .` to run the tests. - -On every PR, an app with the same name as your branch is deployed to the Dash -playground instance so that you can change whether your changes did not break +![CI](https://github.com/pygfx/wgpu-py/workflows/CI/badge.svg) + +# dash_slicer + +A volume slicer for Dash + + +## Status + +This work is marked as alpha - some essential features are still in +development, and some parts of the API may change in future releases. + + +## Installation + +``` +$ pip install dash-slicer +``` + +Dash-slicer depends on Python 3.6+ plus some [dependencies](requirements.txt). + + +## Usage example + +```py +import dash +import dash_html_components as html +from dash_slicer import VolumeSlicer +import imageio + +app = dash.Dash(__name__) + +vol = imageio.volread("imageio:stent.npz") +slicer = VolumeSlicer(app, vol) +app.layout = html.Div([slicer.graph, slicer.slider, *slicer.stores]) + +if __name__ == "__main__": + app.run_server() +``` + + +## License + +This code is distributed under MIT license. + + +## Developers + + +* Make sure that you have Python with the appropriate dependencies installed, e.g. via `venv`. +* Run `pip install -e .` to do an in-place install of the package. +* Run the examples using e.g. `python examples/slicer_with_1_view.py` + +* Use `black .` to autoformat. +* Use `flake8 .` to lint. +* Use `pytest .` to run the tests. + +On every PR, an app with the same name as your branch is deployed to the Dash +playground instance so that you can change whether your changes did not break the package. From 1c7c7b8a259b56e1ac96cae231fed53388248af2 Mon Sep 17 00:00:00 2001 From: Almar Klein Date: Tue, 8 Dec 2020 16:36:39 +0100 Subject: [PATCH 5/8] some more tweaks --- README.md | 7 ++++--- dash_slicer/slicer.py | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index f289244..74a92d7 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ This code is distributed under MIT license. * Use `black .` to autoformat. * Use `flake8 .` to lint. * Use `pytest .` to run the tests. +* Use `python dash_slicer\docs.py` to update the ref docs in the readme. On every PR, an app with the same name as your branch is deployed to the Dash playground instance so that you can change whether your changes did not break @@ -87,7 +88,7 @@ instantiation one can provide the following parameters: * `color` (`str`): the color for this slicer. By default the color is red, green, or blue, depending on the axis. Set to empty string for "no color". -* thumbnail (`int` or `bool`): linear size of low-resolution data to be +* `thumbnail` (`int` or `bool`): linear size of low-resolution data to be uploaded to the client. If `False`, the full-resolution data are uploaded client-side. If `True` (default), a default value of 32 is used. @@ -103,7 +104,7 @@ can be used as output for `slicer.overlay_data`. The color can be a hex color or an rgb/rgba tuple. Alternatively, color can be a list of such colors, defining a colormap. -**property `VolumeSlicer.axis`** (`int`): The axis at wich the slicer is slicing. +**property `VolumeSlicer.axis`** (`int`): The axis at which the slicer is slicing. **property `VolumeSlicer.graph`**: The `dcc.Graph` for this slicer. Use `graph.figure` to access the Plotly Figure object. @@ -150,7 +151,7 @@ a pattern matching input like this: Input({"scene": scene_id, "context": ALL, "name": "state"}) ``` -See the `state` properties for details. +See the `state` property for details. ### Setting slicer positions diff --git a/dash_slicer/slicer.py b/dash_slicer/slicer.py index 6b35691..235a999 100644 --- a/dash_slicer/slicer.py +++ b/dash_slicer/slicer.py @@ -12,7 +12,7 @@ Input({"scene": scene_id, "context": ALL, "name": "state"}) ``` -See the `state` properties for details. +See the `state` property for details. ### Setting slicer positions @@ -82,7 +82,7 @@ class VolumeSlicer: * `color` (`str`): the color for this slicer. By default the color is red, green, or blue, depending on the axis. Set to empty string for "no color". - * thumbnail (`int` or `bool`): linear size of low-resolution data to be + * `thumbnail` (`int` or `bool`): linear size of low-resolution data to be uploaded to the client. If `False`, the full-resolution data are uploaded client-side. If `True` (default), a default value of 32 is used. @@ -182,7 +182,7 @@ def scene_id(self) -> str: @property def axis(self) -> int: - """The axis at wich the slicer is slicing.""" + """The axis at which the slicer is slicing.""" return self._axis @property From b4023e3796e7b9c04f2c6803f40fbdadf98a65fd Mon Sep 17 00:00:00 2001 From: Almar Klein Date: Wed, 9 Dec 2020 14:26:25 +0100 Subject: [PATCH 6/8] Update README.md Co-authored-by: Emmanuelle Gouillart --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 74a92d7..fef1168 100644 --- a/README.md +++ b/README.md @@ -146,7 +146,7 @@ These must be added to the app layout. It is possible to get notified of updates to slicer position and view ranges. To get this for all slicers with a specific scene_id, create -a pattern matching input like this: +a [pattern matching input](https://dash.plotly.com/pattern-matching-callbacks) like this: ```py Input({"scene": scene_id, "context": ALL, "name": "state"}) ``` @@ -181,4 +181,3 @@ experience. Here are some tips to help with that: * For a smooth experience, avoid triggering unnecessary figure updates. * When adding a callback that uses the slicer position, use the (rate limited) `state` store rather than the slider value. - From e7de2e7e135f7d6f93e707857726a7ea63aa051f Mon Sep 17 00:00:00 2001 From: Almar Klein Date: Wed, 9 Dec 2020 14:45:58 +0100 Subject: [PATCH 7/8] put script to update readme in root, and fix windows line endings --- README.md | 4 ++-- dash_slicer/docs.py | 22 ---------------------- dash_slicer/slicer.py | 1 - tests/test_docs.py | 6 +++--- 4 files changed, 5 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 74a92d7..64f4c8b 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ This code is distributed under MIT license. * Use `black .` to autoformat. * Use `flake8 .` to lint. * Use `pytest .` to run the tests. -* Use `python dash_slicer\docs.py` to update the ref docs in the readme. +* Use `python update_docs_in_readme.py` to update the readme when needed. On every PR, an app with the same name as your branch is deployed to the Dash playground instance so that you can change whether your changes did not break @@ -88,7 +88,7 @@ instantiation one can provide the following parameters: * `color` (`str`): the color for this slicer. By default the color is red, green, or blue, depending on the axis. Set to empty string for "no color". -* `thumbnail` (`int` or `bool`): linear size of low-resolution data to be +* `thumbnail` (`int` or `bool`): preferred size of low-resolution data to be uploaded to the client. If `False`, the full-resolution data are uploaded client-side. If `True` (default), a default value of 32 is used. diff --git a/dash_slicer/docs.py b/dash_slicer/docs.py index 799ba69..219993e 100644 --- a/dash_slicer/docs.py +++ b/dash_slicer/docs.py @@ -2,13 +2,9 @@ Utilities to produce simple reference docs (in Markdown) from the source code. """ -import os import inspect - import dash_slicer -HERE = os.path.dirname(os.path.abspath(__file__)) - def dedent(text): """Dedent a docstring, removing leading whitespace.""" @@ -74,21 +70,3 @@ def get_reference_docs(): parts += props parts.append(dash_slicer.slicer.__doc__) return "\n\n".join(parts) - - -def write_reference_docs(): - """Write the reference docs to the README.""" - header = "## Reference" - filename = os.path.join(os.path.dirname(HERE), "README.md") - assert os.path.isfile(filename), "README.md not found" - with open(filename, "rb") as f: - text = f.read().decode() - text, _, _ = text.partition(header) - text = text.rstrip() + "\n\n\n" + header + "\n\n" + get_reference_docs() - with open(filename, "wb") as f: - f.write(text.encode()) - print("Wrote to README.md") - - -if __name__ == "__main__": - write_reference_docs() diff --git a/dash_slicer/slicer.py b/dash_slicer/slicer.py index 60340ec..d526c81 100644 --- a/dash_slicer/slicer.py +++ b/dash_slicer/slicer.py @@ -61,7 +61,6 @@ class VolumeSlicer: -<<<<<<< HEAD """A slicer object to show 3D image data in Dash. Upon instantiation one can provide the following parameters: diff --git a/tests/test_docs.py b/tests/test_docs.py index e9e46f5..44c4eaf 100644 --- a/tests/test_docs.py +++ b/tests/test_docs.py @@ -13,14 +13,14 @@ def test_that_the_docs_build(): assert "performance" in x.lower() -def test_that_docs_are_up_to_date(): +def test_that_reference_docs_in_readme_are_up_to_date(): filename = os.path.join(os.path.dirname(HERE), "README.md") assert os.path.isfile(filename) with open(filename, "rb") as f: text = f.read().decode() _, _, ref = text.partition("## Reference") - ref1 = ref.strip() + ref1 = ref.strip().replace("\r\n", "\n") ref2 = get_reference_docs().strip() assert ( ref1 == ref2 - ), "Reference docs in readme are outdated. Run `python dash_slicer/docs.py`" + ), "Reference docs in readme are outdated. Run `python update_docs_in_readme.py`" From 4fd95f1e84e2b4a62ff47c1e8147dd8dec11c06f Mon Sep 17 00:00:00 2001 From: Almar Klein Date: Wed, 9 Dec 2020 14:53:36 +0100 Subject: [PATCH 8/8] update docs --- README.md | 4 +++- dash_slicer/slicer.py | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 96b1308..581493b 100644 --- a/README.md +++ b/README.md @@ -146,7 +146,8 @@ These must be added to the app layout. It is possible to get notified of updates to slicer position and view ranges. To get this for all slicers with a specific scene_id, create -a [pattern matching input](https://dash.plotly.com/pattern-matching-callbacks) like this: +a [pattern matching input](https://dash.plotly.com/pattern-matching-callbacks) +like this: ```py Input({"scene": scene_id, "context": ALL, "name": "state"}) ``` @@ -181,3 +182,4 @@ experience. Here are some tips to help with that: * For a smooth experience, avoid triggering unnecessary figure updates. * When adding a callback that uses the slicer position, use the (rate limited) `state` store rather than the slider value. + diff --git a/dash_slicer/slicer.py b/dash_slicer/slicer.py index d526c81..4355b34 100644 --- a/dash_slicer/slicer.py +++ b/dash_slicer/slicer.py @@ -7,7 +7,8 @@ It is possible to get notified of updates to slicer position and view ranges. To get this for all slicers with a specific scene_id, create -a pattern matching input like this: +a [pattern matching input](https://dash.plotly.com/pattern-matching-callbacks) +like this: ```py Input({"scene": scene_id, "context": ALL, "name": "state"}) ```