lets-plot 4.8.1rc1__cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lets_plot/__init__.py +382 -0
- lets_plot/_global_settings.py +192 -0
- lets_plot/_kbridge.py +197 -0
- lets_plot/_type_utils.py +133 -0
- lets_plot/_version.py +6 -0
- lets_plot/bistro/__init__.py +16 -0
- lets_plot/bistro/_plot2d_common.py +106 -0
- lets_plot/bistro/corr.py +448 -0
- lets_plot/bistro/im.py +196 -0
- lets_plot/bistro/joint.py +192 -0
- lets_plot/bistro/qq.py +207 -0
- lets_plot/bistro/residual.py +341 -0
- lets_plot/bistro/waterfall.py +332 -0
- lets_plot/export/__init__.py +6 -0
- lets_plot/export/ggsave_.py +172 -0
- lets_plot/frontend_context/__init__.py +8 -0
- lets_plot/frontend_context/_configuration.py +140 -0
- lets_plot/frontend_context/_dynamic_configure_html.py +115 -0
- lets_plot/frontend_context/_frontend_ctx.py +16 -0
- lets_plot/frontend_context/_html_contexts.py +223 -0
- lets_plot/frontend_context/_intellij_python_json_ctx.py +38 -0
- lets_plot/frontend_context/_isolated_webview_panel_ctx.py +81 -0
- lets_plot/frontend_context/_json_contexts.py +39 -0
- lets_plot/frontend_context/_jupyter_notebook_ctx.py +82 -0
- lets_plot/frontend_context/_mime_types.py +7 -0
- lets_plot/frontend_context/_static_html_page_ctx.py +76 -0
- lets_plot/frontend_context/_static_svg_ctx.py +26 -0
- lets_plot/frontend_context/_webbr_html_page_ctx.py +29 -0
- lets_plot/frontend_context/sandbox.py +5 -0
- lets_plot/geo_data/__init__.py +19 -0
- lets_plot/geo_data/core.py +335 -0
- lets_plot/geo_data/geocoder.py +988 -0
- lets_plot/geo_data/geocodes.py +512 -0
- lets_plot/geo_data/gis/__init__.py +0 -0
- lets_plot/geo_data/gis/fluent_dict.py +201 -0
- lets_plot/geo_data/gis/geocoding_service.py +42 -0
- lets_plot/geo_data/gis/geometry.py +91 -0
- lets_plot/geo_data/gis/json_request.py +232 -0
- lets_plot/geo_data/gis/json_response.py +308 -0
- lets_plot/geo_data/gis/request.py +492 -0
- lets_plot/geo_data/gis/response.py +247 -0
- lets_plot/geo_data/livemap_helper.py +65 -0
- lets_plot/geo_data/to_geo_data_frame.py +141 -0
- lets_plot/geo_data/type_assertion.py +34 -0
- lets_plot/geo_data_internals/__init__.py +4 -0
- lets_plot/geo_data_internals/constants.py +13 -0
- lets_plot/geo_data_internals/utils.py +33 -0
- lets_plot/mapping.py +115 -0
- lets_plot/package_data/lets-plot.min.js +3 -0
- lets_plot/plot/__init__.py +64 -0
- lets_plot/plot/_global_theme.py +14 -0
- lets_plot/plot/annotation.py +290 -0
- lets_plot/plot/coord.py +242 -0
- lets_plot/plot/core.py +1071 -0
- lets_plot/plot/expand_limits_.py +78 -0
- lets_plot/plot/facet.py +210 -0
- lets_plot/plot/font_features.py +71 -0
- lets_plot/plot/geom.py +9146 -0
- lets_plot/plot/geom_extras.py +53 -0
- lets_plot/plot/geom_function_.py +219 -0
- lets_plot/plot/geom_imshow_.py +393 -0
- lets_plot/plot/geom_livemap_.py +343 -0
- lets_plot/plot/ggbunch_.py +96 -0
- lets_plot/plot/gggrid_.py +139 -0
- lets_plot/plot/ggtb_.py +81 -0
- lets_plot/plot/guide.py +231 -0
- lets_plot/plot/label.py +187 -0
- lets_plot/plot/marginal_layer.py +181 -0
- lets_plot/plot/plot.py +245 -0
- lets_plot/plot/pos.py +344 -0
- lets_plot/plot/sampling.py +338 -0
- lets_plot/plot/sandbox_.py +26 -0
- lets_plot/plot/scale.py +3580 -0
- lets_plot/plot/scale_colormap_mpl.py +300 -0
- lets_plot/plot/scale_convenience.py +155 -0
- lets_plot/plot/scale_identity_.py +653 -0
- lets_plot/plot/scale_position.py +1342 -0
- lets_plot/plot/series_meta.py +209 -0
- lets_plot/plot/stat.py +585 -0
- lets_plot/plot/subplots.py +331 -0
- lets_plot/plot/subplots_util.py +24 -0
- lets_plot/plot/theme_.py +790 -0
- lets_plot/plot/theme_set.py +418 -0
- lets_plot/plot/tooltip.py +486 -0
- lets_plot/plot/util.py +267 -0
- lets_plot/settings_utils.py +244 -0
- lets_plot/tilesets.py +429 -0
- lets_plot-4.8.1rc1.dist-info/METADATA +221 -0
- lets_plot-4.8.1rc1.dist-info/RECORD +97 -0
- lets_plot-4.8.1rc1.dist-info/WHEEL +6 -0
- lets_plot-4.8.1rc1.dist-info/licenses/LICENSE +21 -0
- lets_plot-4.8.1rc1.dist-info/licenses/licenses/LICENSE.FreeType +166 -0
- lets_plot-4.8.1rc1.dist-info/licenses/licenses/LICENSE.ImageMagick +106 -0
- lets_plot-4.8.1rc1.dist-info/licenses/licenses/LICENSE.expat +21 -0
- lets_plot-4.8.1rc1.dist-info/licenses/licenses/LICENSE.fontconfig +200 -0
- lets_plot-4.8.1rc1.dist-info/top_level.txt +2 -0
- lets_plot_kotlin_bridge.cpython-311-x86_64-linux-gnu.so +0 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2019. JetBrains s.r.o.
|
|
3
|
+
# Use of this source code is governed by the MIT license that can be found in the LICENSE file.
|
|
4
|
+
#
|
|
5
|
+
from .core import FeatureSpec
|
|
6
|
+
|
|
7
|
+
__all__ = ['arrow']
|
|
8
|
+
|
|
9
|
+
#
|
|
10
|
+
# See R doc: https://www.rdocumentation.org/packages/grid/versions/3.4.1/topics/arrow
|
|
11
|
+
#
|
|
12
|
+
def arrow(angle=None, length=None, ends=None, type=None):
|
|
13
|
+
"""
|
|
14
|
+
Describe arrows to add to a line.
|
|
15
|
+
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
angle : float
|
|
19
|
+
The angle of the arrow head in degrees (smaller numbers produce narrower, pointer arrows).
|
|
20
|
+
Essentially describes the width of the arrow head.
|
|
21
|
+
length : int
|
|
22
|
+
The length of the arrow head (px).
|
|
23
|
+
ends : {'last', 'first', 'both'}
|
|
24
|
+
Indicating which ends of the line to draw arrow heads.
|
|
25
|
+
type : {'open', 'closed'}
|
|
26
|
+
Indicating whether the arrow head should be a closed triangle.
|
|
27
|
+
|
|
28
|
+
Returns
|
|
29
|
+
-------
|
|
30
|
+
``FeatureSpec``
|
|
31
|
+
Arrow object specification.
|
|
32
|
+
|
|
33
|
+
Examples
|
|
34
|
+
--------
|
|
35
|
+
.. jupyter-execute::
|
|
36
|
+
:linenos:
|
|
37
|
+
:emphasize-lines: 5, 7, 9, 11
|
|
38
|
+
|
|
39
|
+
from lets_plot import *
|
|
40
|
+
LetsPlot.setup_html()
|
|
41
|
+
ggplot() + \\
|
|
42
|
+
geom_segment(x=2, y=10, xend=4, yend=9, \\
|
|
43
|
+
arrow=arrow(type='closed')) + \\
|
|
44
|
+
geom_segment(x=3, y=6, xend=3, yend=9, \\
|
|
45
|
+
arrow=arrow(type='open')) + \\
|
|
46
|
+
geom_segment(x=4, y=7, xend=5, yend=10, \\
|
|
47
|
+
arrow=arrow(type='closed', ends='both', length=23)) + \\
|
|
48
|
+
geom_segment(x=5, y=8, xend=7, yend=7, \\
|
|
49
|
+
arrow=arrow(type='open', ends='first', angle=120, length=23))
|
|
50
|
+
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
return FeatureSpec('arrow', 'arrow', **locals())
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# Copyright (c) 2023. JetBrains s.r.o.
|
|
2
|
+
# Use of this source code is governed by the MIT license that can be found in the LICENSE file.
|
|
3
|
+
try:
|
|
4
|
+
import pandas as pd
|
|
5
|
+
except ImportError:
|
|
6
|
+
pd = None
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
import polars as pl
|
|
10
|
+
except ImportError:
|
|
11
|
+
pl = None
|
|
12
|
+
|
|
13
|
+
from .core import aes
|
|
14
|
+
from .geom import _geom
|
|
15
|
+
|
|
16
|
+
__all__ = ['geom_function']
|
|
17
|
+
|
|
18
|
+
_fun_x_name, _fun_y_name = 'x', 'y'
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _linspace(start, stop, num):
|
|
22
|
+
if num == 1:
|
|
23
|
+
return [start]
|
|
24
|
+
|
|
25
|
+
step = (stop - start) / (num - 1)
|
|
26
|
+
|
|
27
|
+
return [start + step * i for i in range(num)]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _get_default_xrange(xlim, n):
|
|
31
|
+
default_xlim = [0.0, 1.0]
|
|
32
|
+
default_size = 512
|
|
33
|
+
|
|
34
|
+
start, stop = xlim if xlim is not None else default_xlim
|
|
35
|
+
size = n if n is not None else default_size
|
|
36
|
+
|
|
37
|
+
return _linspace(start, stop, size)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _get_fun_data(mapping, data, fun, xlim, n):
|
|
41
|
+
aes_x_value = None
|
|
42
|
+
if mapping is not None and 'x' in mapping.as_dict():
|
|
43
|
+
aes_x_value = mapping.as_dict()['x']
|
|
44
|
+
|
|
45
|
+
if isinstance(aes_x_value, str) and data is not None:
|
|
46
|
+
xs = data[aes_x_value]
|
|
47
|
+
elif hasattr(aes_x_value, '__iter__'):
|
|
48
|
+
xs = aes_x_value
|
|
49
|
+
else:
|
|
50
|
+
xs = _get_default_xrange(xlim, n)
|
|
51
|
+
|
|
52
|
+
if fun is None:
|
|
53
|
+
ys = [None] * len(xs)
|
|
54
|
+
else:
|
|
55
|
+
ys = [fun(x) for x in xs]
|
|
56
|
+
|
|
57
|
+
if data is None:
|
|
58
|
+
return {_fun_x_name: xs, _fun_y_name: ys}
|
|
59
|
+
else:
|
|
60
|
+
if isinstance(data, dict):
|
|
61
|
+
return {**data, **{_fun_y_name: ys}}
|
|
62
|
+
elif pd is not None and isinstance(data, pd.DataFrame):
|
|
63
|
+
return data.assign(**{_fun_y_name: ys})
|
|
64
|
+
elif pl is not None and isinstance(data, pl.DataFrame):
|
|
65
|
+
return data.with_columns(**{_fun_y_name: pl.Series(values=ys)})
|
|
66
|
+
else:
|
|
67
|
+
raise Exception("Unsupported type of data: {0}".format(data))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _get_mapping(mapping):
|
|
71
|
+
mapping_dict = mapping.as_dict() if mapping is not None else {}
|
|
72
|
+
x_mapping_dict = {'x': _fun_x_name}
|
|
73
|
+
y_mapping_dict = {'y': _fun_y_name}
|
|
74
|
+
|
|
75
|
+
return aes(**{**x_mapping_dict, **mapping_dict, **y_mapping_dict})
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def geom_function(mapping=None, *, data=None, stat=None, geom=None, position=None, show_legend=None, inherit_aes=None,
|
|
79
|
+
manual_key=None,
|
|
80
|
+
tooltips=None,
|
|
81
|
+
fun=None, xlim=None, n=None,
|
|
82
|
+
color_by=None,
|
|
83
|
+
**other_args):
|
|
84
|
+
"""
|
|
85
|
+
Compute and draw a function.
|
|
86
|
+
|
|
87
|
+
Parameters
|
|
88
|
+
----------
|
|
89
|
+
mapping : ``FeatureSpec``
|
|
90
|
+
Set of aesthetic mappings created by `aes() <https://lets-plot.org/python/pages/api/lets_plot.aes.html>`__ function.
|
|
91
|
+
Aesthetic mappings describe the way that variables in the data are
|
|
92
|
+
mapped to plot "aesthetics".
|
|
93
|
+
data : dict or Pandas or Polars ``DataFrame``
|
|
94
|
+
The data to be used in this layer. Specify to describe the definition area of a function.
|
|
95
|
+
If None, the default, the data will not be used at all.
|
|
96
|
+
stat : str, default='identity'
|
|
97
|
+
The statistical transformation to use on the data generated by the function.
|
|
98
|
+
Supported transformations: 'identity' (leaves the data unchanged),
|
|
99
|
+
'smooth' (performs smoothing - linear default),
|
|
100
|
+
'density2d' (computes and draws 2D kernel density estimate).
|
|
101
|
+
geom : str, default='line'
|
|
102
|
+
The geometry to display the function, as a string.
|
|
103
|
+
position : str or ``FeatureSpec``, default='identity'
|
|
104
|
+
Position adjustment.
|
|
105
|
+
Either a position adjustment name: 'dodge', 'jitter', 'nudge', 'jitterdodge', 'fill',
|
|
106
|
+
'stack' or 'identity', or the result of calling a position adjustment function
|
|
107
|
+
(e.g., `position_dodge() <https://lets-plot.org/python/pages/api/lets_plot.position_dodge.html>`__ etc.).
|
|
108
|
+
show_legend : bool, default=True
|
|
109
|
+
False - do not show legend for this layer.
|
|
110
|
+
inherit_aes : bool, default=True
|
|
111
|
+
False - do not combine the layer aesthetic mappings with the plot shared mappings.
|
|
112
|
+
manual_key : str or ``layer_key``
|
|
113
|
+
The key to show in the manual legend.
|
|
114
|
+
Specify text for the legend label or advanced settings using the
|
|
115
|
+
`layer_key() <https://lets-plot.org/python/pages/api/lets_plot.layer_key.html>`__ function.
|
|
116
|
+
tooltips : ``layer_tooltips``
|
|
117
|
+
Result of the call to the
|
|
118
|
+
`layer_tooltips() <https://lets-plot.org/python/pages/api/lets_plot.layer_tooltips.html>`__ function.
|
|
119
|
+
Specify appearance, style and content.
|
|
120
|
+
Set tooltips='none' to hide tooltips from the layer.
|
|
121
|
+
fun : function
|
|
122
|
+
A function of one variable in Python syntax.
|
|
123
|
+
xlim : list of float, default=[0.0, 1.0]
|
|
124
|
+
Range of the function. Float array of length 2.
|
|
125
|
+
n : int, default=512
|
|
126
|
+
Number of points to interpolate along the x axis.
|
|
127
|
+
color_by : {'fill', 'color', 'paint_a', 'paint_b', 'paint_c'}, default='color'
|
|
128
|
+
Define the color aesthetic for the geometry.
|
|
129
|
+
other_args
|
|
130
|
+
Other arguments passed on to the layer.
|
|
131
|
+
These are often aesthetics settings used to set an aesthetic to a fixed value,
|
|
132
|
+
like color='red', fill='blue', size=3 or shape=21.
|
|
133
|
+
They may also be parameters to the paired geom/stat.
|
|
134
|
+
|
|
135
|
+
Returns
|
|
136
|
+
-------
|
|
137
|
+
``LayerSpec``
|
|
138
|
+
Geom object specification.
|
|
139
|
+
|
|
140
|
+
Notes
|
|
141
|
+
-----
|
|
142
|
+
``geom_function()`` understands the following aesthetics mappings:
|
|
143
|
+
|
|
144
|
+
- x : x-axis value.
|
|
145
|
+
- alpha : transparency level of a layer. Accept values between 0 and 1.
|
|
146
|
+
- color (colour) : color of the geometry. For more info see `Color and Fill <https://lets-plot.org/python/pages/aesthetics.html#color-and-fill>`__.
|
|
147
|
+
- linetype : type of the line. Accept codes or names (0 = 'blank', 1 = 'solid', 2 = 'dashed', 3 = 'dotted', 4 = 'dotdash', 5 = 'longdash', 6 = 'twodash'), a hex string (up to 8 digits for dash-gap lengths), or a list pattern [offset, [dash, gap, ...]] / [dash, gap, ...]. For more info see `Line Types <https://lets-plot.org/python/pages/aesthetics.html#line-types>`__.
|
|
148
|
+
- size : line width.
|
|
149
|
+
|
|
150
|
+
----
|
|
151
|
+
|
|
152
|
+
To hide axis tooltips, set 'blank' or the result of `element_blank() <https://lets-plot.org/python/pages/api/lets_plot.element_blank.html>`__
|
|
153
|
+
to the ``axis_tooltip``, ``axis_tooltip_x`` or ``axis_tooltip_y`` parameter of the `theme() <https://lets-plot.org/python/pages/api/lets_plot.theme.html>`__.
|
|
154
|
+
|
|
155
|
+
Examples
|
|
156
|
+
--------
|
|
157
|
+
.. jupyter-execute::
|
|
158
|
+
:linenos:
|
|
159
|
+
:emphasize-lines: 9
|
|
160
|
+
|
|
161
|
+
import numpy as np
|
|
162
|
+
from scipy.stats import norm
|
|
163
|
+
from lets_plot import *
|
|
164
|
+
LetsPlot.setup_html()
|
|
165
|
+
np.random.seed(42)
|
|
166
|
+
x = np.random.normal(size=500)
|
|
167
|
+
ggplot() + \\
|
|
168
|
+
geom_density(aes(x='x'), data={'x': x}) + \\
|
|
169
|
+
geom_function(fun=norm.pdf, xlim=[-4, 4], color="red")
|
|
170
|
+
|
|
171
|
+
|
|
|
172
|
+
|
|
173
|
+
.. jupyter-execute::
|
|
174
|
+
:linenos:
|
|
175
|
+
:emphasize-lines: 5-6
|
|
176
|
+
|
|
177
|
+
from lets_plot import *
|
|
178
|
+
LetsPlot.setup_html()
|
|
179
|
+
data = {'x': list(range(-5, 6))}
|
|
180
|
+
ggplot() + \\
|
|
181
|
+
geom_function(aes(x='x', color='y', size='y'), \\
|
|
182
|
+
data=data, fun=lambda t: t**2, show_legend=False) + \\
|
|
183
|
+
scale_color_gradient(low="red", high="green") + \\
|
|
184
|
+
scale_size(range=[1, 4], trans='reverse')
|
|
185
|
+
|
|
186
|
+
|
|
|
187
|
+
|
|
188
|
+
.. jupyter-execute::
|
|
189
|
+
:linenos:
|
|
190
|
+
:emphasize-lines: 4-5
|
|
191
|
+
|
|
192
|
+
from math import sqrt
|
|
193
|
+
from lets_plot import *
|
|
194
|
+
LetsPlot.setup_html()
|
|
195
|
+
fun_layer = lambda fun: geom_function(fun=fun, xlim=[-2, 2], n=9, \\
|
|
196
|
+
stat='density2d', geom='density2d')
|
|
197
|
+
gggrid([
|
|
198
|
+
ggplot() + fun_layer(lambda t: t),
|
|
199
|
+
ggplot() + fun_layer(lambda t: t**2),
|
|
200
|
+
ggplot() + fun_layer(lambda t: 2**t),
|
|
201
|
+
ggplot() + fun_layer(lambda t: sqrt(4 - t**2)) + coord_fixed(ratio=2),
|
|
202
|
+
], ncol=2)
|
|
203
|
+
|
|
204
|
+
"""
|
|
205
|
+
fun_stat = stat if stat is not None else 'identity'
|
|
206
|
+
fun_geom = geom if geom is not None else 'line'
|
|
207
|
+
|
|
208
|
+
return _geom(fun_geom,
|
|
209
|
+
mapping=_get_mapping(mapping),
|
|
210
|
+
data=_get_fun_data(mapping, data, fun, xlim, n),
|
|
211
|
+
stat=fun_stat,
|
|
212
|
+
position=position,
|
|
213
|
+
show_legend=show_legend,
|
|
214
|
+
inherit_aes=inherit_aes,
|
|
215
|
+
manual_key=manual_key,
|
|
216
|
+
sampling=None,
|
|
217
|
+
tooltips=tooltips,
|
|
218
|
+
color_by=color_by,
|
|
219
|
+
**other_args)
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright (c) 2019. JetBrains s.r.o.
|
|
3
|
+
# Use of this source code is governed by the MIT license that can be found in the LICENSE file.
|
|
4
|
+
#
|
|
5
|
+
import base64
|
|
6
|
+
import io
|
|
7
|
+
|
|
8
|
+
from .core import aes
|
|
9
|
+
from .geom import _geom
|
|
10
|
+
from .scale import scale_gradientn
|
|
11
|
+
from .scale import scale_grey
|
|
12
|
+
from .util import as_boolean
|
|
13
|
+
from .._type_utils import is_ndarray
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
import png
|
|
17
|
+
except ImportError:
|
|
18
|
+
png = None
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
import numpy
|
|
22
|
+
except ImportError:
|
|
23
|
+
numpy = None
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
from palettable.matplotlib import matplotlib as palettable
|
|
27
|
+
except ImportError:
|
|
28
|
+
palettable = None
|
|
29
|
+
|
|
30
|
+
__all__ = ['geom_imshow']
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _hex2rgb(hex_c, alpha):
|
|
34
|
+
hex_s = hex_c.lstrip('#')
|
|
35
|
+
list_rgb = [int(hex_s[i:i + 2], 16) for i in (0, 2, 4)]
|
|
36
|
+
if alpha is not None:
|
|
37
|
+
list_rgb.append(int(alpha + 0.5))
|
|
38
|
+
return list_rgb
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _hex2rgb_arr_uint8(hex_c, alpha=None):
|
|
42
|
+
"""
|
|
43
|
+
Create 'palette' for PyPNG PNG writer
|
|
44
|
+
"""
|
|
45
|
+
return numpy.array(_hex2rgb(hex_c, alpha), dtype=numpy.uint8)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _normalize_2D(image_data, norm, vmin, vmax, min_lum):
|
|
49
|
+
"""
|
|
50
|
+
Take numpy 2D array of float or int-s and
|
|
51
|
+
return 2D array of ints with the target range [0..255].
|
|
52
|
+
Values outside the target range will be later clipped.
|
|
53
|
+
"""
|
|
54
|
+
min_lum = max(0, min_lum)
|
|
55
|
+
max_lum = 255 - min_lum
|
|
56
|
+
|
|
57
|
+
vmin = float(vmin if vmin is not None else numpy.nanmin(image_data))
|
|
58
|
+
vmax = float(vmax if vmax is not None else numpy.nanmax(image_data))
|
|
59
|
+
if vmin > vmax:
|
|
60
|
+
raise ValueError("vmin value must be less then vmax value, was: {} > {}".format(vmin, vmax))
|
|
61
|
+
|
|
62
|
+
normalize = as_boolean(norm, default=True)
|
|
63
|
+
|
|
64
|
+
# Make a copy via `numpy.copy()` or via `arr.astype()`
|
|
65
|
+
# - prevent modification of the original image
|
|
66
|
+
# - work around read-only flag in the original image
|
|
67
|
+
|
|
68
|
+
if normalize:
|
|
69
|
+
if vmin == vmax:
|
|
70
|
+
image_data = numpy.copy(image_data)
|
|
71
|
+
image_data[True] = 127
|
|
72
|
+
else:
|
|
73
|
+
# float array for scaling
|
|
74
|
+
if image_data.dtype.kind == 'f':
|
|
75
|
+
image_data = numpy.copy(image_data)
|
|
76
|
+
else:
|
|
77
|
+
image_data = image_data.astype(numpy.float32)
|
|
78
|
+
|
|
79
|
+
image_data.clip(vmin, vmax, out=image_data)
|
|
80
|
+
|
|
81
|
+
ratio = max_lum / (vmax - vmin)
|
|
82
|
+
image_data -= vmin
|
|
83
|
+
image_data *= ratio
|
|
84
|
+
image_data += min_lum
|
|
85
|
+
else:
|
|
86
|
+
# no normalization
|
|
87
|
+
image_data = numpy.copy(image_data)
|
|
88
|
+
image_data.clip(min_lum, max_lum, out=image_data)
|
|
89
|
+
vmin = float(numpy.nanmin(image_data))
|
|
90
|
+
vmax = float(numpy.nanmax(image_data))
|
|
91
|
+
|
|
92
|
+
return (image_data, vmin, vmax)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def geom_imshow(image_data, cmap=None, *,
|
|
96
|
+
norm=None, alpha=None,
|
|
97
|
+
vmin=None, vmax=None,
|
|
98
|
+
extent=None,
|
|
99
|
+
compression=None,
|
|
100
|
+
show_legend=True,
|
|
101
|
+
color_by="paint_c",
|
|
102
|
+
):
|
|
103
|
+
"""
|
|
104
|
+
Display image specified by ndarray with shape.
|
|
105
|
+
|
|
106
|
+
- (M, N) - grey-scale image
|
|
107
|
+
- (M, N, 3) - color RGB image
|
|
108
|
+
- (M, N, 4) - color RGB image with alpha channel
|
|
109
|
+
|
|
110
|
+
This geom is not as flexible as `geom_raster() <https://lets-plot.org/python/pages/api/lets_plot.geom_raster.html>`__
|
|
111
|
+
or `geom_tile() <https://lets-plot.org/python/pages/api/lets_plot.geom_tile.html>`__
|
|
112
|
+
but vastly superior in the terms of rendering efficiency.
|
|
113
|
+
|
|
114
|
+
Parameters
|
|
115
|
+
----------
|
|
116
|
+
image_data : ndarray
|
|
117
|
+
Specify image type, size and pixel values.
|
|
118
|
+
Supported array shapes are:
|
|
119
|
+
|
|
120
|
+
- (M, N): an image with scalar data. The values are mapped to colors (greys by default) using normalization. See parameters ``norm``, ``cmap``, ``vmin``, ``vmax``.
|
|
121
|
+
- (M, N, 3): an image with RGB values (0-1 float or 0-255 int).
|
|
122
|
+
- (M, N, 4): an image with RGBA values (0-1 float or 0-255 int).
|
|
123
|
+
|
|
124
|
+
The first two dimensions (M, N) define the rows and columns of the image.
|
|
125
|
+
Out-of-range values are clipped.
|
|
126
|
+
cmap : str, optional
|
|
127
|
+
Name of colormap. For example "viridis", "magma", "plasma", "inferno", or any other colormap
|
|
128
|
+
which is supported by the Palettable package (https://github.com/jiffyclub/palettable)
|
|
129
|
+
This parameter is ignored for RGB(A) images.
|
|
130
|
+
norm : bool, default=True
|
|
131
|
+
True - luminance values in grey-scale image will be scaled to [0-255] range using a linear scaler.
|
|
132
|
+
False - disables scaling of luminance values in grey-scale image.
|
|
133
|
+
This parameter is ignored for RGB(A) images.
|
|
134
|
+
alpha: float, optional
|
|
135
|
+
The alpha blending value, between 0 (transparent) and 1 (opaque).
|
|
136
|
+
vmin, vmax : number, optional
|
|
137
|
+
Define the data range used for luminance normalization in grey-scale images.
|
|
138
|
+
This parameter is ignored for RGB(A) images or if parameter ``norm=False``.
|
|
139
|
+
extent : list of 4 numbers: [left, right, bottom, top], optional
|
|
140
|
+
Define image's bounding box in terms of the "data coordinates".
|
|
141
|
+
|
|
142
|
+
- ``left``, ``right``: coordinates of pixels' outer edge along the x-axis for pixels in the 1-st and the last column.
|
|
143
|
+
- ``bottom``, ``top``: coordinates of pixels' outer edge along the y-axis for pixels in the 1-st and the last row.
|
|
144
|
+
|
|
145
|
+
The default is: [-0.5, ncol-0.5, -0.5, nrow-0.5]
|
|
146
|
+
compression : int, optional
|
|
147
|
+
The compression level to be used by the ``zlib`` module.
|
|
148
|
+
Values from 0 (no compression) to 9 (highest).
|
|
149
|
+
Value None means that the ``zlib`` module uses
|
|
150
|
+
the default level of compression (which is generally acceptable).
|
|
151
|
+
show_legend : bool, default=True
|
|
152
|
+
Greyscale images only.
|
|
153
|
+
False - do not show legend for this layer.
|
|
154
|
+
color_by : {'fill', 'color', 'paint_a', 'paint_b', 'paint_c'}, default='paint_c'
|
|
155
|
+
Define the color aesthetic used by the legend shown for a greyscale image.
|
|
156
|
+
|
|
157
|
+
Returns
|
|
158
|
+
-------
|
|
159
|
+
``LayerSpec``
|
|
160
|
+
Geom object specification.
|
|
161
|
+
|
|
162
|
+
Notes
|
|
163
|
+
-----
|
|
164
|
+
This geom doesn't understand any aesthetics.
|
|
165
|
+
It doesn't support color scales either.
|
|
166
|
+
|
|
167
|
+
Examples
|
|
168
|
+
--------
|
|
169
|
+
.. jupyter-execute::
|
|
170
|
+
:linenos:
|
|
171
|
+
:emphasize-lines: 6
|
|
172
|
+
|
|
173
|
+
import numpy as np
|
|
174
|
+
from lets_plot import *
|
|
175
|
+
LetsPlot.setup_html()
|
|
176
|
+
np.random.seed(42)
|
|
177
|
+
image = np.random.randint(256, size=(64, 64, 4))
|
|
178
|
+
ggplot() + geom_imshow(image)
|
|
179
|
+
|
|
180
|
+
|
|
|
181
|
+
|
|
182
|
+
.. jupyter-execute::
|
|
183
|
+
:linenos:
|
|
184
|
+
:emphasize-lines: 7
|
|
185
|
+
|
|
186
|
+
import numpy as np
|
|
187
|
+
from lets_plot import *
|
|
188
|
+
LetsPlot.setup_html()
|
|
189
|
+
n = 64
|
|
190
|
+
image = 256 * np.linspace(np.linspace(0, .5, n), \\
|
|
191
|
+
np.linspace(.5, .5, n), n)
|
|
192
|
+
ggplot() + geom_imshow(image, norm=False)
|
|
193
|
+
|
|
194
|
+
|
|
|
195
|
+
|
|
196
|
+
.. jupyter-execute::
|
|
197
|
+
:linenos:
|
|
198
|
+
:emphasize-lines: 6
|
|
199
|
+
|
|
200
|
+
import numpy as np
|
|
201
|
+
from lets_plot import *
|
|
202
|
+
LetsPlot.setup_html()
|
|
203
|
+
np.random.seed(42)
|
|
204
|
+
image = np.random.normal(size=(64, 64))
|
|
205
|
+
ggplot() + geom_imshow(image, vmin=-1, vmax=1)
|
|
206
|
+
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
if png is None:
|
|
210
|
+
raise ValueError("pypng is not installed")
|
|
211
|
+
|
|
212
|
+
if not is_ndarray(image_data):
|
|
213
|
+
raise ValueError("Invalid image_data: ndarray is expected but was {}".format(type(image_data)))
|
|
214
|
+
|
|
215
|
+
if image_data.ndim not in (2, 3):
|
|
216
|
+
raise ValueError(
|
|
217
|
+
"Invalid image_data: 2d or 3d array is expected but was {}-dimensional".format(image_data.ndim))
|
|
218
|
+
|
|
219
|
+
if alpha is not None:
|
|
220
|
+
if not (0 <= alpha <= 1):
|
|
221
|
+
raise ValueError(
|
|
222
|
+
"Invalid alpha: expected float in range [0..1] but was {}".format(alpha))
|
|
223
|
+
|
|
224
|
+
if compression is not None:
|
|
225
|
+
if not (0 <= compression <= 9):
|
|
226
|
+
raise ValueError(
|
|
227
|
+
"Invalid compression: expected integer in range [0..9] but was {}".format(compression))
|
|
228
|
+
|
|
229
|
+
greyscale = (image_data.ndim == 2)
|
|
230
|
+
if greyscale:
|
|
231
|
+
# Greyscale image
|
|
232
|
+
|
|
233
|
+
has_nan = numpy.isnan(image_data.max())
|
|
234
|
+
min_lum = 0 if not (has_nan and cmap) else 1 # index 0 reserved for NaN-s
|
|
235
|
+
|
|
236
|
+
(image_data, greyscale_data_min, greyscale_data_max) = _normalize_2D(image_data, norm, vmin, vmax, min_lum)
|
|
237
|
+
height, width = image_data.shape
|
|
238
|
+
has_nan = numpy.isnan(image_data.max())
|
|
239
|
+
|
|
240
|
+
if cmap:
|
|
241
|
+
# colormap via palettable
|
|
242
|
+
if not palettable:
|
|
243
|
+
raise ValueError(
|
|
244
|
+
"Can't process `cmap`: please install 'Palettable' (https://pypi.org/project/palettable/) to your "
|
|
245
|
+
"Python environment. "
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
# prepare palette
|
|
249
|
+
palette = None
|
|
250
|
+
if not has_nan:
|
|
251
|
+
alpha_ch_val = 255 if alpha is None else 255 * alpha
|
|
252
|
+
cmap_256 = palettable.get_map(cmap + "_256")
|
|
253
|
+
palette = [_hex2rgb_arr_uint8(c, alpha_ch_val) for c in cmap_256.hex_colors]
|
|
254
|
+
else:
|
|
255
|
+
alpha_ch_val = 255 if alpha is None else 255 * alpha
|
|
256
|
+
cmap_255 = palettable.get_map(cmap + "_255")
|
|
257
|
+
# transparent color at index 0
|
|
258
|
+
palette = [numpy.array([0, 0, 0, 0], dtype=numpy.uint8)] \
|
|
259
|
+
+ [_hex2rgb_arr_uint8(c, alpha_ch_val) for c in cmap_255.hex_colors]
|
|
260
|
+
|
|
261
|
+
# replace indexes with palette colors
|
|
262
|
+
if has_nan:
|
|
263
|
+
# replace all NaN-s with 0 (index 0 for transparent color)
|
|
264
|
+
numpy.nan_to_num(image_data, copy=False, nan=0)
|
|
265
|
+
image_data = numpy.take(palette, numpy.round(image_data).astype(numpy.int32), axis=0)
|
|
266
|
+
else:
|
|
267
|
+
# Greyscale
|
|
268
|
+
alpha_ch_scaler = 1 if alpha is None else alpha
|
|
269
|
+
is_nan = numpy.isnan(image_data)
|
|
270
|
+
im_shape = numpy.shape(image_data)
|
|
271
|
+
alpha_ch = numpy.zeros(im_shape, dtype=image_data.dtype)
|
|
272
|
+
alpha_ch[is_nan == False] = 255 * alpha_ch_scaler
|
|
273
|
+
image_data[is_nan] = 0
|
|
274
|
+
image_data = numpy.repeat(image_data[:, :, numpy.newaxis], 3, axis=2) # convert to RGB
|
|
275
|
+
image_data = numpy.dstack((image_data, alpha_ch)) # convert to RGBA
|
|
276
|
+
else:
|
|
277
|
+
# Color RGB/RGBA image
|
|
278
|
+
# Make a copy:
|
|
279
|
+
# - prevent modification of the original image
|
|
280
|
+
# - drop read-only flag
|
|
281
|
+
image_data = numpy.copy(image_data)
|
|
282
|
+
if image_data.dtype.kind == 'f':
|
|
283
|
+
image_data *= 255
|
|
284
|
+
|
|
285
|
+
height, width, nchannels = image_data.shape
|
|
286
|
+
|
|
287
|
+
if nchannels == 3:
|
|
288
|
+
alpha_ch_scaler = 1 if alpha is None else alpha
|
|
289
|
+
# RGB image: add alpha channel (RGBA)
|
|
290
|
+
alpha_ch = numpy.full((height, width, 1), 255 * alpha_ch_scaler, dtype=image_data.dtype)
|
|
291
|
+
image_data = numpy.dstack((image_data, alpha_ch))
|
|
292
|
+
elif nchannels == 4 and alpha is not None:
|
|
293
|
+
# RGBA image: apply alpha scaling
|
|
294
|
+
image_data[:, :, 3] *= alpha
|
|
295
|
+
|
|
296
|
+
# Make sure all values are ints in range 0-255.
|
|
297
|
+
image_data.clip(0, 255, out=image_data)
|
|
298
|
+
|
|
299
|
+
# Image extent with possible axis flipping.
|
|
300
|
+
# The default image bounds include 1/2 unit size expand in all directions.
|
|
301
|
+
ext_x0, ext_x1, ext_y0, ext_y1 = -.5, width - .5, -.5, height - .5
|
|
302
|
+
if extent:
|
|
303
|
+
try:
|
|
304
|
+
ext_x0, ext_x1, ext_y0, ext_y1 = [float(v) for v in extent]
|
|
305
|
+
except ValueError as e:
|
|
306
|
+
raise ValueError(
|
|
307
|
+
"Invalid `extent`: list of 4 numbers expected: {}".format(e)
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
if ext_x0 > ext_x1:
|
|
311
|
+
# copy after flip to work around this numpy issue: https://github.com/drj11/pypng/issues/91
|
|
312
|
+
image_data = numpy.flip(image_data, axis=1).copy()
|
|
313
|
+
ext_x0, ext_x1 = ext_x1, ext_x0
|
|
314
|
+
|
|
315
|
+
if ext_y0 > ext_y1:
|
|
316
|
+
image_data = numpy.flip(image_data, axis=0)
|
|
317
|
+
ext_y0, ext_y1 = ext_y1, ext_y0
|
|
318
|
+
|
|
319
|
+
# Make sure each value is 1 byte and the type is numpy.int8.
|
|
320
|
+
# Otherwise, pypng will produce broken colors.
|
|
321
|
+
if image_data.dtype.kind == 'f':
|
|
322
|
+
# Can't cast directly from float to np.int8.
|
|
323
|
+
image_data += 0.5
|
|
324
|
+
image_data = image_data.astype(numpy.int16)
|
|
325
|
+
|
|
326
|
+
if image_data.dtype != numpy.int8:
|
|
327
|
+
image_data = image_data.astype(numpy.int8)
|
|
328
|
+
|
|
329
|
+
# Reshape to 2d-array:
|
|
330
|
+
image_2d = image_data.reshape(-1, width * 4) # always 4 channels (RGBA)
|
|
331
|
+
|
|
332
|
+
# PNG writer
|
|
333
|
+
png_bytes = io.BytesIO()
|
|
334
|
+
png.Writer(
|
|
335
|
+
width=width,
|
|
336
|
+
height=height,
|
|
337
|
+
greyscale=False,
|
|
338
|
+
alpha=True,
|
|
339
|
+
bitdepth=8,
|
|
340
|
+
compression=compression
|
|
341
|
+
).write(png_bytes, image_2d)
|
|
342
|
+
|
|
343
|
+
href = 'data:image/png;base64,' + str(base64.standard_b64encode(png_bytes.getvalue()), 'utf-8')
|
|
344
|
+
|
|
345
|
+
# The Legend (colorbar)
|
|
346
|
+
show_legend = as_boolean(show_legend, default=True)
|
|
347
|
+
normalize = as_boolean(norm, default=True)
|
|
348
|
+
legend_title = ""
|
|
349
|
+
color_scale = None
|
|
350
|
+
color_scale_mapping = None
|
|
351
|
+
if greyscale and show_legend:
|
|
352
|
+
# aes(color=[greyscale_data_min, greyscale_data_max])
|
|
353
|
+
color_scale_mapping = aes(**{color_by: [greyscale_data_min, greyscale_data_max]})
|
|
354
|
+
if cmap and normalize:
|
|
355
|
+
cmap_32 = palettable.get_map(cmap + "_32")
|
|
356
|
+
# color_scale = scale_color_gradientn(colors=cmap_32.hex_colors, name=legend_title)
|
|
357
|
+
color_scale = scale_gradientn(aesthetic=color_by, colors=cmap_32.hex_colors, name=legend_title)
|
|
358
|
+
elif cmap and not normalize:
|
|
359
|
+
cmap_256 = palettable.get_map(cmap + "_256")
|
|
360
|
+
start = max(0, round(greyscale_data_min))
|
|
361
|
+
end = min(255, round(greyscale_data_max))
|
|
362
|
+
cmap_hex_colors = cmap_256.hex_colors[start:end]
|
|
363
|
+
if len(cmap_hex_colors) > 32:
|
|
364
|
+
# reduce number of colors to 32
|
|
365
|
+
indices = numpy.linspace(0, len(cmap_hex_colors) - 1, 32, dtype=int)
|
|
366
|
+
cmap_hex_colors = [cmap_hex_colors[i] for i in indices]
|
|
367
|
+
|
|
368
|
+
# color_scale = scale_color_gradientn(colors=cmap_hex_colors, name=legend_title)
|
|
369
|
+
color_scale = scale_gradientn(aesthetic=color_by, colors=cmap_hex_colors, name=legend_title)
|
|
370
|
+
else:
|
|
371
|
+
start = 0 if normalize else greyscale_data_min / 255.
|
|
372
|
+
end = 1 if normalize else greyscale_data_max / 255.
|
|
373
|
+
# color_scale = scale_color_grey(start=start, end=end, name=legend_title)
|
|
374
|
+
color_scale = scale_grey(aesthetic=color_by, start=start, end=end, name=legend_title)
|
|
375
|
+
|
|
376
|
+
# Image geom layer
|
|
377
|
+
geom_image_layer = _geom(
|
|
378
|
+
'image',
|
|
379
|
+
mapping=color_scale_mapping,
|
|
380
|
+
href=href,
|
|
381
|
+
xmin=ext_x0,
|
|
382
|
+
ymin=ext_y0,
|
|
383
|
+
xmax=ext_x1,
|
|
384
|
+
ymax=ext_y1,
|
|
385
|
+
show_legend=show_legend,
|
|
386
|
+
inherit_aes=False,
|
|
387
|
+
color_by=color_by if (show_legend and greyscale) else None,
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
if (color_scale is not None):
|
|
391
|
+
geom_image_layer = geom_image_layer + color_scale
|
|
392
|
+
|
|
393
|
+
return geom_image_layer
|