Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fast image: add binary_string parameter to imshow #2691

Merged
merged 37 commits into from
Sep 3, 2020
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
f628e94
Merge branch 'master' of https://github.com/plotly/plotly.py
emmanuelle Jun 12, 2020
d2f8b72
Merge branch 'master' of https://github.com/plotly/plotly.py
emmanuelle Jun 23, 2020
3ef3764
Merge branch 'master' of https://github.com/plotly/plotly.py
emmanuelle Jul 14, 2020
902a98e
Merge branch 'master' of https://github.com/plotly/plotly.py
emmanuelle Jul 27, 2020
71b14c1
run updateplotlyjsdev using js fast-image branch
emmanuelle Aug 5, 2020
1984998
work in progress on imshow with source
emmanuelle Aug 6, 2020
e6feb33
added dev dep
emmanuelle Aug 6, 2020
e551672
added dev dep
emmanuelle Aug 6, 2020
34cc0de
more tests and docstring
emmanuelle Aug 6, 2020
febdfaf
range_color case
emmanuelle Aug 6, 2020
01195f8
values for contrast_rescaling are now minmax and infer
emmanuelle Aug 11, 2020
81ce9f6
vendored code from scikit-image for rescale_intensity
emmanuelle Aug 13, 2020
d3d88b8
different backends for b64 creation
emmanuelle Aug 14, 2020
bd25f05
more tests and documented parameters in docstring
emmanuelle Aug 17, 2020
78d2454
no color in hovertemplate when contrast has been modified
emmanuelle Aug 17, 2020
0752df7
modified tutorial
emmanuelle Aug 17, 2020
34c1acf
modified requirements
emmanuelle Aug 17, 2020
08e0781
solve bug for rgba images
emmanuelle Aug 20, 2020
91f608e
tweaks so that zmin/zmax are set less often
emmanuelle Aug 20, 2020
0cecbd6
do not do anything when origin='lower' because the JS already takes c…
emmanuelle Aug 20, 2020
eeb1988
use z instead of color in hover
emmanuelle Aug 20, 2020
dd21b48
version added in tutorial
emmanuelle Aug 20, 2020
226817e
remove circle ci modifications
emmanuelle Aug 20, 2020
d3cb22b
codegen
emmanuelle Aug 31, 2020
7b117dc
add generated files
emmanuelle Aug 31, 2020
4db5fe4
remove pypng dependency
emmanuelle Aug 31, 2020
63a4dc9
use rgba256 colormodel
emmanuelle Aug 31, 2020
29aab27
try to fix CI
emmanuelle Aug 31, 2020
32ca251
fix test
emmanuelle Aug 31, 2020
cdbfefe
improve examples
emmanuelle Sep 2, 2020
03ce500
add jpg compression option
emmanuelle Sep 2, 2020
c0247d2
improve docstring
emmanuelle Sep 2, 2020
ff6bd6e
bump plotly.js to 1.55.1
nicolaskruchten Sep 3, 2020
3640abc
Merge branch 'master' into fast-image
nicolaskruchten Sep 3, 2020
56494ba
blacken png
nicolaskruchten Sep 3, 2020
cb72f88
remove f-string
nicolaskruchten Sep 3, 2020
96cebe3
fix png.py tests
nicolaskruchten Sep 3, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 73 additions & 11 deletions doc/python/imshow.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ jupyter:
extension: .md
format_name: markdown
format_version: '1.2'
jupytext_version: 1.4.2
jupytext_version: 1.3.0
kernelspec:
display_name: Python 3
language: python
Expand Down Expand Up @@ -88,7 +88,7 @@ fig.show()

### Choose the colorscale to display a single-channel image

You can customize the [continuous color scale](/python/colorscales/) just like with any other Plotly Express function:
You can customize the [continuous color scale](/python/colorscales/) just like with any other Plotly Express function. However, `color_continuous_scale` is ignored when using `binary_string=True`, since the image is always represented as grayscale (and no colorbar is displayed).

```python
import plotly.express as px
Expand Down Expand Up @@ -167,7 +167,7 @@ fig.show()

### Display multichannel image data with go.Image

It is also possible to use the `go.Image` trace from the low-level `graph_objects` API in order to display image data. Note that `go.Image` only accepts multichannel images. For single images, use [`go.Heatmap`](/python/heatmaps).
It is also possible to use the `go.Image` trace from the low-level `graph_objects` API in order to display image data. Note that `go.Image` only accepts multichannel images. For single-channel images, use [`go.Heatmap`](/python/heatmaps).

Note that the `go.Image` trace is different from the `go.layout.Image` class, which can be used for [adding background images or logos to figures](/python/images).

Expand All @@ -179,22 +179,41 @@ fig = go.Figure(go.Image(z=img_rgb))
fig.show()
```

### Defining the data range covered by the color range with zmin and zmax
nicolaskruchten marked this conversation as resolved.
Show resolved Hide resolved
### Passing image data as a binary string to `go.Image`

The data range and color range are mapped together using the parameters `zmin` and `zmax`, which correspond respectively to the data values mapped to black `[0, 0, 0]` and white `[255, 255, 255]`, or to the extreme colors of the colorscale in the case on single-channel data.
The `z` parameter of `go.Image` passes image data in the form of an array or a list of numerical values, but it is also possible to use the `source` parameter, which takes a b64 binary string. Thanks to png or jpg compression, using `source` is a way to reduce the quantity of data passed to the browser, and also to reduce the serialization time of the figure, resulting in increased performance.

For single-channel data, the defaults values of `zmin` and `zmax` used by `px.imshow` and `go.Heatmap` are the extrema of the data range. For multichannel data, `px.imshow` and `go.Image` use slightly different default values for `zmin` and `zmax`. For `go.Image`, the default value is `zmin=[0, 0, 0]` and `zmax=[255, 255, 255]`, no matter the data type. On the other hand, `px.imshow` adapts the default `zmin` and `zmax` to the data type:
- for integer data types, `zmin` and `zmax` correspond to the extreme values of the data type, for example 0 and 255 for `uint8`, 0 and 65535 for `uint16`, etc.
- for float numbers, the maximum value of the data is computed, and zmax is 1 if the max is smaller than 1, 255 if the max is smaller than 255, etc. (with higher thresholds 2**16 - 1 and 2**32 -1).
Note than an easier way of creating binary strings with `px.imshow` is explained below.

```python
import plotly.graph_objects as go
from skimage import data
from PIL import Image
import base64
from io import BytesIO

img = data.astronaut() # numpy array
pil_img = Image.fromarray(img) # PIL image object
prefix = "data:image/png;base64,"
with BytesIO() as stream:
pil_img.save(stream, format="png")
base64_string = prefix + base64.b64encode(stream.getvalue()).decode("utf-8")
fig = go.Figure(go.Image(source=base64_string))
fig.show()
```

### Defining the data range covered by the color range with zmin and zmax

These defaults can be overriden by setting the values of `zmin` and `zmax`. For `go.Image`, `zmin` and `zmax` need to be given for all channels, whereas it is also possible to pass a scalar value (used for all channels) to `px.imshow`.
The data range and color range are mapped together using the parameters `zmin` and `zmax` of `px.imshow` or `go.Image`, which correspond respectively to the data values mapped to black `[0, 0, 0]` and white `[255, 255, 255]`, or to the extreme colors of the colorscale in the case on single-channel data.

For `go.Image`, `zmin` and `zmax` need to be given for all channels, whereas it is also possible to pass a scalar value (used for all channels) to `px.imshow`.

```python
import plotly.express as px
from skimage import data
img = data.astronaut()
# Increase contrast by clipping the data range between 50 and 200
fig = px.imshow(img, zmin=50, zmax=200)
fig = px.imshow(img, zmin=50, zmax=200, binary_string=False)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should explain why the binary_string=False is necessary here, and/or explain what happens if you don't do it... in this case the hover ends up different, right? We don't do it in the example immediately below, though, is that intentional?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I modified this example so that it does not use binary_string, which has not been introduced for imshow at this point of the tutorial, and then I added two examples about rescaling and hover in the section on imshow and binary_string.

# We customize the hovertemplate to show both the data and the color values
# See https://plotly.com/python/hover-text-and-formatting/#customize-tooltip-text-with-a-hovertemplate
fig.update_traces(hovertemplate="x: %{x} <br> y: %{y} <br> z: %{z} <br> color: %{color}")
Expand All @@ -210,6 +229,21 @@ fig = px.imshow(img, zmin=[50, 0, 0], zmax=[200, 255, 255])
fig.show()
```

### Automatic contrast rescaling in `px.imshow`

When `zmin` and `zmax` are not specified, the `contrast_rescaling` arguments determines how `zmin` and `zmax` are computed. For `contrast_rescaling='minmax'`, the extrema of the data range are used. For `contrast_rescaling='infer'`, a heuristic based on the data type is used:
- for integer data types, `zmin` and `zmax` correspond to the extreme values of the data type, for example 0 and 255 for `uint8`, 0 and 65535 for `uint16`, etc.
- for float numbers, the maximum value of the data is computed, and zmax is 1 if the max is smaller than 1, 255 if the max is smaller than 255, etc. (with higher thresholds 2**16 - 1 and 2**32 -1).

These two modes can be used for single- and multichannel data. The default value is to use `'minmax'` for single-channel data (as in a Heatmap trace) and `infer` for multi-channel data (which often consist of uint8 data). In the example below we override the default value by setting `contrast_rescaling='infer'` for a single-channel image.

```python
import plotly.express as px
img = np.arange(100, dtype=np.uint8).reshape((10, 10))
fig = px.imshow(img, contrast_rescaling='infer')
fig.show()
```

### Ticks and margins around image data

```python
Expand Down Expand Up @@ -307,5 +341,33 @@ fig.show(config={'modeBarButtonsToAdd':['drawline',
]})
```

### Passing image data as a binary string

_introduced in plotly.py 4.10_

`px.imshow` can pass the data to the figure object either as a list of numerical values, or as a png binary string which is passed directly to the browser. While the former solution offers more flexibility (values can be of float or int type, while values are rescaled to the range [0-255] for an image string), using a binary string is usually faster for large arrays. The parameter `binary_string` controls whether the image is passed as a png string (when `True`) or a list of values (`False`). Its default value is `True` for multi-channel images and `False` for single-channel images. When `binary_string=True`, image data are always represented using a `go.Image` trace.

```python
import plotly.express as px
import numpy as np
img = np.arange(15**2).reshape((15, 15))
fig = px.imshow(img, binary_string=True)
fig.show()
```

### Changing the level of compression of the binary string in `px.imshow`

The `binary_compression_level` parameter controls the level of compression to be used by the backend creating the png string. Two different backends can be used, `pypng` (which is a dependency of `plotly` and is therefore always available), and `pil` for Pillow, which is often more performant. The compression level has to be between 0 (no compression) and 9 (highest compression), although increasing the compression above 4 and 5 usually only offers diminishing returns (no significant compression gain, at the cost of a longer execution time).

```python
import plotly.express as px
from skimage import data
img = data.camera()
for compression_level in range(0, 9):
fig = px.imshow(img, binary_string=True, binary_compression_level=compression_level)
print(f"compression level {compression_level}: length of {len(fig.data[0].source)}")
fig.show()
```

#### Reference
See https://plotly.com/python/reference/#image for more information and chart attribute options!
See https://plotly.com/python/reference/#image for more information and chart attribute options!
109 changes: 64 additions & 45 deletions packages/javascript/plotlywidget/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/javascript/plotlywidget/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"typescript": "~3.1.1"
},
"dependencies": {
"plotly.js": "^1.54.6",
"plotly.js": "https://85822-45646037-gh.circle-artifacts.com/0/plotly.js.tgz",
"@jupyter-widgets/base": "^2.0.0 || ^3.0.0",
"lodash": "^4.17.4"
},
Expand Down
Loading