geone 1.3.0__py313-none-manylinux_2_35_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.
- geone/__init__.py +32 -0
- geone/_version.py +6 -0
- geone/blockdata.py +250 -0
- geone/covModel.py +15529 -0
- geone/customcolors.py +508 -0
- geone/deesse_core/__init__.py +5 -0
- geone/deesse_core/_deesse.so +0 -0
- geone/deesse_core/deesse.py +2450 -0
- geone/deesseinterface.py +11323 -0
- geone/geosclassic_core/__init__.py +5 -0
- geone/geosclassic_core/_geosclassic.so +0 -0
- geone/geosclassic_core/geosclassic.py +1429 -0
- geone/geosclassicinterface.py +20092 -0
- geone/grf.py +5927 -0
- geone/img.py +7152 -0
- geone/imgplot.py +1464 -0
- geone/imgplot3d.py +1918 -0
- geone/markovChain.py +666 -0
- geone/multiGaussian.py +388 -0
- geone/pgs.py +1258 -0
- geone/randProcess.py +1258 -0
- geone/srf.py +3661 -0
- geone/tools.py +861 -0
- geone-1.3.0.dist-info/METADATA +207 -0
- geone-1.3.0.dist-info/RECORD +28 -0
- geone-1.3.0.dist-info/WHEEL +5 -0
- geone-1.3.0.dist-info/licenses/LICENSE +58 -0
- geone-1.3.0.dist-info/top_level.txt +1 -0
geone/imgplot3d.py
ADDED
|
@@ -0,0 +1,1918 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
# -------------------------------------------------------------------------
|
|
5
|
+
# Python module: 'imgplot3d.py'
|
|
6
|
+
# author: Julien Straubhaar
|
|
7
|
+
# date: feb-2020
|
|
8
|
+
# -------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
"""
|
|
11
|
+
Module for custom plots of images (class :class:`geone.img.Img`) in 3D (based on `pyvista`).
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import numpy as np
|
|
15
|
+
import matplotlib.pyplot as plt
|
|
16
|
+
|
|
17
|
+
import matplotlib.colors as mcolors
|
|
18
|
+
|
|
19
|
+
import pyvista as pv
|
|
20
|
+
|
|
21
|
+
from geone import customcolors as ccol
|
|
22
|
+
|
|
23
|
+
# ============================================================================
|
|
24
|
+
class Imgplot3dError(Exception):
|
|
25
|
+
"""
|
|
26
|
+
Custom exception related to `imgplot3d` module.
|
|
27
|
+
"""
|
|
28
|
+
pass
|
|
29
|
+
# ============================================================================
|
|
30
|
+
|
|
31
|
+
# ----------------------------------------------------------------------------
|
|
32
|
+
def drawImage3D_surface (
|
|
33
|
+
im,
|
|
34
|
+
plotter=None,
|
|
35
|
+
ix0=0, ix1=None,
|
|
36
|
+
iy0=0, iy1=None,
|
|
37
|
+
iz0=0, iz1=None,
|
|
38
|
+
iv=0,
|
|
39
|
+
cmap=ccol.cmap_def,
|
|
40
|
+
cmin=None, cmax=None,
|
|
41
|
+
alpha=None,
|
|
42
|
+
excludedVal=None,
|
|
43
|
+
categ=False,
|
|
44
|
+
ncateg_max=30,
|
|
45
|
+
categVal=None,
|
|
46
|
+
categCol=None,
|
|
47
|
+
categColCycle=False,
|
|
48
|
+
categActive=None,
|
|
49
|
+
use_clip_plane=False,
|
|
50
|
+
show_scalar_bar=True,
|
|
51
|
+
show_outline=True,
|
|
52
|
+
show_bounds=False,
|
|
53
|
+
show_axes=True,
|
|
54
|
+
text=None,
|
|
55
|
+
scalar_bar_annotations=None,
|
|
56
|
+
scalar_bar_annotations_max=20,
|
|
57
|
+
scalar_bar_kwargs=None,
|
|
58
|
+
outline_kwargs=None,
|
|
59
|
+
bounds_kwargs=None,
|
|
60
|
+
axes_kwargs=None,
|
|
61
|
+
text_kwargs=None,
|
|
62
|
+
background_color=None,
|
|
63
|
+
foreground_color=None,
|
|
64
|
+
cpos=None,
|
|
65
|
+
verbose=1,
|
|
66
|
+
logger=None,
|
|
67
|
+
**kwargs):
|
|
68
|
+
"""
|
|
69
|
+
Displays a 3D image as surface(s) (based on `pyvista`).
|
|
70
|
+
|
|
71
|
+
Parameters
|
|
72
|
+
----------
|
|
73
|
+
im : :class:`geone.img.Img`
|
|
74
|
+
image (3D)
|
|
75
|
+
|
|
76
|
+
plotter : :class:`pyvista.Plotter`, optional
|
|
77
|
+
- if given (not `None`), add element to the plotter, a further call to \
|
|
78
|
+
`plotter.show()` will be required to show the plot
|
|
79
|
+
- if not given (`None`, default): a plotter is created and the plot \
|
|
80
|
+
is shown
|
|
81
|
+
|
|
82
|
+
ix0 : int, default: 0
|
|
83
|
+
index of first slice along x direction, considered for plotting
|
|
84
|
+
|
|
85
|
+
ix1 : int, optional
|
|
86
|
+
1+index of last slice along x direction (`ix0 < ix1`), considered for
|
|
87
|
+
plotting; by default: number of cells in x direction (`ix1=im.nx`) is
|
|
88
|
+
used
|
|
89
|
+
|
|
90
|
+
iy0 : int, default: 0
|
|
91
|
+
index of first slice along y direction, considered for plotting
|
|
92
|
+
|
|
93
|
+
iy1 : int, optional
|
|
94
|
+
1+index of last slice along y direction (`iy0 < iy1`), considered for
|
|
95
|
+
plotting; by default: number of cells in x direction (`iy1=im.ny`) is
|
|
96
|
+
used
|
|
97
|
+
|
|
98
|
+
iz0 : int, default: 0
|
|
99
|
+
index of first slice along z direction, considered for plotting
|
|
100
|
+
|
|
101
|
+
iz1 : int, optional
|
|
102
|
+
1+index of last slice along z direction (`iz0 < iz1`), considered for
|
|
103
|
+
plotting; by default: number of cells in z direction (`iz1=im.nz`) is
|
|
104
|
+
used
|
|
105
|
+
|
|
106
|
+
iv : int, default: 0
|
|
107
|
+
index of the variable to be displayed
|
|
108
|
+
|
|
109
|
+
cmap : colormap, default: `geone.customcolors.cmap_def`
|
|
110
|
+
color map (can be a string, in this case the color map is obtained by
|
|
111
|
+
`matplotlib.pyplot.get_cmap(cmap)`)
|
|
112
|
+
|
|
113
|
+
cmin : float, optional
|
|
114
|
+
used only if `categ=False`:
|
|
115
|
+
minimal value to be displayed; by default: minimal value of the displayed
|
|
116
|
+
variable is used for `cmin`
|
|
117
|
+
|
|
118
|
+
cmax : float, optional
|
|
119
|
+
used only if `categ=False`:
|
|
120
|
+
maximal value to be displayed; by default: maximal value of the displayed
|
|
121
|
+
variable is used for `cmax`
|
|
122
|
+
|
|
123
|
+
alpha : float, optional
|
|
124
|
+
value of the "alpha" channel (for transparency);
|
|
125
|
+
by default (`None`): `alpha=1.0` is used (no transparency)
|
|
126
|
+
|
|
127
|
+
excludedVal : sequence of values, or single value, optional
|
|
128
|
+
values to be excluded from the plot;
|
|
129
|
+
note not used if `categ=True` and `categVal` is not `None`
|
|
130
|
+
|
|
131
|
+
categ : bool, default: False
|
|
132
|
+
indicates if the variable of the image to diplay has to be treated as a
|
|
133
|
+
categorical (discrete) variable (True), or continuous variable (False)
|
|
134
|
+
|
|
135
|
+
ncateg_max : int, default: 30
|
|
136
|
+
used only if `categ=True`:
|
|
137
|
+
maximal number of categories, if there are more category values and
|
|
138
|
+
`categVal=None`, nothing is plotted (`categ` should set to False)
|
|
139
|
+
|
|
140
|
+
categVal : sequence of values, or single value, optional
|
|
141
|
+
used only if `categ=True`:
|
|
142
|
+
explicit list of the category values to be displayed;
|
|
143
|
+
by default (`None`): the list of all unique values are automatically
|
|
144
|
+
retrieved
|
|
145
|
+
|
|
146
|
+
categCol: sequence of colors, optional
|
|
147
|
+
used only if `categ=True`:
|
|
148
|
+
sequence of colors, (given as 3-tuple (RGB code), 4-tuple (RGBA code) or
|
|
149
|
+
str), used for the category values that will be displayed:
|
|
150
|
+
|
|
151
|
+
- if `categVal` is not `None`: `categCol` must have the same length as \
|
|
152
|
+
`categVal`
|
|
153
|
+
- if `categVal=None`:
|
|
154
|
+
- first colors of `categCol` are used if its length is greater than \
|
|
155
|
+
or equal to the number of displayed category values,
|
|
156
|
+
- otherwise: the colors of `categCol` are used cyclically if \
|
|
157
|
+
`categColCycle=True`, and the colors taken from the color map `cmap` \
|
|
158
|
+
are used if `categColCycle=False`
|
|
159
|
+
|
|
160
|
+
categColCycle : bool, default: False
|
|
161
|
+
used only if `categ=True`:
|
|
162
|
+
indicates if the colors of `categCol` can be used cyclically or not
|
|
163
|
+
(when the number of displayed category values exceeds the length of
|
|
164
|
+
`categCol`)
|
|
165
|
+
|
|
166
|
+
categActive : 1D array-like of bools, optional
|
|
167
|
+
used only if `categ=True`, sequence of same length as `categVal`:
|
|
168
|
+
|
|
169
|
+
- `categActive[i]=True`: `categVal[i]` is displayed
|
|
170
|
+
- `categActive[i]=False`: `categVal[i]` is not displayed
|
|
171
|
+
|
|
172
|
+
by default (`None`): all category values `categVal` are displayed
|
|
173
|
+
|
|
174
|
+
use_clip_plane : bool, default: False
|
|
175
|
+
if `True`: the function `pyvista.add_mesh_clip_plane` (allowing
|
|
176
|
+
interactive clipping) is used instead of `pyvista.add_mesh`
|
|
177
|
+
|
|
178
|
+
show_scalar_bar : bool, default: True
|
|
179
|
+
indicates if scalar bar (color bar) is displayed
|
|
180
|
+
|
|
181
|
+
show_outline : bool, default: True
|
|
182
|
+
indicates if outline (around the image) is displayed
|
|
183
|
+
|
|
184
|
+
show_bounds : bool, default: False
|
|
185
|
+
indicates if bounds are displayed (box with graduation)
|
|
186
|
+
|
|
187
|
+
show_axes : bool, default: True
|
|
188
|
+
indicates if axes are displayed
|
|
189
|
+
|
|
190
|
+
text : str, optional
|
|
191
|
+
text (title) to be displayed on the figure
|
|
192
|
+
|
|
193
|
+
scalar_bar_annotations : dict, optional
|
|
194
|
+
annotation (ticks) on the scalar bar (color bar), used if
|
|
195
|
+
`show_scalar_bar=True`
|
|
196
|
+
|
|
197
|
+
scalar_bar_annotations_max : int, default: 20
|
|
198
|
+
maximal number of annotations (ticks) on the scalar bar (color bar)
|
|
199
|
+
when `categ=True` and `scalar_bar_annotations=None`
|
|
200
|
+
|
|
201
|
+
scalar_bar_kwargs : dict, optional
|
|
202
|
+
keyword arguments passed to function `plotter.add_scalar_bar`
|
|
203
|
+
(can be useful for customization, used if `show_scalar_bar=True`)
|
|
204
|
+
note: in subplots (multi-sub-window), key 'title' should be distinct for
|
|
205
|
+
each subplot
|
|
206
|
+
|
|
207
|
+
outline_kwargs : dict, optional
|
|
208
|
+
keyword arguments passed to function `plotter.add_mesh`
|
|
209
|
+
(can be useful for customization, used if `show_outline=True`)
|
|
210
|
+
|
|
211
|
+
bounds_kwargs : dict, optional
|
|
212
|
+
keyword arguments passed to function `plotter.show_bounds`
|
|
213
|
+
(can be useful for customization, used if `show_bounds=True`)
|
|
214
|
+
|
|
215
|
+
axes_kwargs : dict, optional
|
|
216
|
+
keyword arguments passed to function `plotter.add_axes`
|
|
217
|
+
(can be useful for customization, used if `show_axes=True`)
|
|
218
|
+
|
|
219
|
+
text_kwargs : dict, optional
|
|
220
|
+
keyword arguments passed to function `plotter.add_text`
|
|
221
|
+
(can be useful for customization, used if `text` is not `None`)
|
|
222
|
+
|
|
223
|
+
background_color : color, optional
|
|
224
|
+
background color (3-tuple (RGB code), 4-tuple (RGBA code) or str)
|
|
225
|
+
|
|
226
|
+
foreground_color : color, optional
|
|
227
|
+
foreground color (3-tuple (RGB code), 4-tuple (RGBA code) or str)
|
|
228
|
+
|
|
229
|
+
cpos : sequence[sequence[float]], optional
|
|
230
|
+
camera position (unsused if `plotter=None`);
|
|
231
|
+
`cpos` = [camera_location, focus_point, viewup_vector], with
|
|
232
|
+
|
|
233
|
+
- camera_location: (tuple of length 3) camera location ("eye")
|
|
234
|
+
- focus_point : (tuple of length 3) focus point
|
|
235
|
+
- viewup_vector : (tuple of length 3) viewup vector (vector \
|
|
236
|
+
attached to the "head" and pointed to the "sky")
|
|
237
|
+
|
|
238
|
+
note: in principle, (focus_point - camera_location) is orthogonal to
|
|
239
|
+
viewup_vector
|
|
240
|
+
|
|
241
|
+
verbose : int, default: 1
|
|
242
|
+
verbose mode, higher implies more printing (info)
|
|
243
|
+
|
|
244
|
+
logger : :class:`logging.Logger`, optional
|
|
245
|
+
logger (see package `logging`)
|
|
246
|
+
if specified, messages are written via `logger` (no print)
|
|
247
|
+
|
|
248
|
+
kwargs : dict
|
|
249
|
+
additional keyword arguments passed to `plotter.add_mesh[_clip_plane]`
|
|
250
|
+
when plotting the variable, such as
|
|
251
|
+
|
|
252
|
+
- opacity (float, or str) : \
|
|
253
|
+
opacity for colors; \
|
|
254
|
+
default: 'linear', (set 'linear_r' to invert opacity)
|
|
255
|
+
- show_edges (bool) : \
|
|
256
|
+
indicates if edges of the grid are displayed
|
|
257
|
+
- edge_color (color) : \
|
|
258
|
+
color (3-tuple (RGB code), 4-tuple (RGBA code) or str) for edges \
|
|
259
|
+
(used if `show_edges=True`)
|
|
260
|
+
- line_width (float) \
|
|
261
|
+
line width for edges (used if `show_edges=True`)
|
|
262
|
+
- etc.
|
|
263
|
+
|
|
264
|
+
Notes
|
|
265
|
+
-----
|
|
266
|
+
- 'scalar bar', and 'axes' may be not displayed in multiple-plot, bug ?
|
|
267
|
+
"""
|
|
268
|
+
fname = 'drawImage3D_surface'
|
|
269
|
+
|
|
270
|
+
# Check iv
|
|
271
|
+
if iv < 0:
|
|
272
|
+
iv = im.nv + iv
|
|
273
|
+
|
|
274
|
+
if iv < 0 or iv >= im.nv:
|
|
275
|
+
err_msg = f'{fname}: invalid `iv` index'
|
|
276
|
+
if logger: logger.error(err_msg)
|
|
277
|
+
raise Imgplot3dError(err_msg)
|
|
278
|
+
|
|
279
|
+
# Set indices to be plotted
|
|
280
|
+
if ix1 is None:
|
|
281
|
+
ix1 = im.nx
|
|
282
|
+
|
|
283
|
+
if iy1 is None:
|
|
284
|
+
iy1 = im.ny
|
|
285
|
+
|
|
286
|
+
if iz1 is None:
|
|
287
|
+
iz1 = im.nz
|
|
288
|
+
|
|
289
|
+
if ix0 >= ix1 or ix0 < 0 or ix1 > im.nx:
|
|
290
|
+
err_msg = f'{fname}: invalid indices along x axis'
|
|
291
|
+
if logger: logger.error(err_msg)
|
|
292
|
+
raise Imgplot3dError(err_msg)
|
|
293
|
+
|
|
294
|
+
if iy0 >= iy1 or iy0 < 0 or iy1 > im.ny:
|
|
295
|
+
err_msg = f'{fname}: invalid indices along y axis'
|
|
296
|
+
if logger: logger.error(err_msg)
|
|
297
|
+
raise Imgplot3dError(err_msg)
|
|
298
|
+
|
|
299
|
+
if iz0 >= iz1 or iz0 < 0 or iz1 > im.nz:
|
|
300
|
+
err_msg = f'{fname}: invalid indices along z axis'
|
|
301
|
+
if logger: logger.error(err_msg)
|
|
302
|
+
raise Imgplot3dError(err_msg)
|
|
303
|
+
|
|
304
|
+
# Get the color map
|
|
305
|
+
if isinstance(cmap, str):
|
|
306
|
+
try:
|
|
307
|
+
cmap = plt.get_cmap(cmap)
|
|
308
|
+
except:
|
|
309
|
+
err_msg = f'{fname}: invalid `cmap` string'
|
|
310
|
+
if logger: logger.error(err_msg)
|
|
311
|
+
raise Imgplot3dError(err_msg)
|
|
312
|
+
|
|
313
|
+
# Initialization of dictionary (do not use {} as default argument, it is not re-initialized...)
|
|
314
|
+
if scalar_bar_annotations is None:
|
|
315
|
+
scalar_bar_annotations = {}
|
|
316
|
+
|
|
317
|
+
if scalar_bar_kwargs is None:
|
|
318
|
+
scalar_bar_kwargs = {}
|
|
319
|
+
|
|
320
|
+
if outline_kwargs is None:
|
|
321
|
+
outline_kwargs = {}
|
|
322
|
+
|
|
323
|
+
if bounds_kwargs is None:
|
|
324
|
+
bounds_kwargs = {}
|
|
325
|
+
|
|
326
|
+
if axes_kwargs is None:
|
|
327
|
+
axes_kwargs = {}
|
|
328
|
+
|
|
329
|
+
if text_kwargs is None:
|
|
330
|
+
text_kwargs = {}
|
|
331
|
+
|
|
332
|
+
# Extract what to be plotted
|
|
333
|
+
# zz = np.array(im.val[iv][iz0:iz1, iy0:iy1, ix0:ix1]) # np.array() to get a copy
|
|
334
|
+
zz = im.val[iv][iz0:iz1, iy0:iy1, ix0:ix1].flatten() # .flatten() provides a copy
|
|
335
|
+
|
|
336
|
+
if categ:
|
|
337
|
+
# --- Treat categorical variable ---
|
|
338
|
+
if categCol is not None \
|
|
339
|
+
and type(categCol) is not list \
|
|
340
|
+
and type(categCol) is not tuple:
|
|
341
|
+
err_msg = f'{fname}: `categCol` must be a list or a tuple (if not `None`)'
|
|
342
|
+
if logger: logger.error(err_msg)
|
|
343
|
+
raise Imgplot3dError(err_msg)
|
|
344
|
+
|
|
345
|
+
# Get array 'dval' of displayed values (at least for color bar)
|
|
346
|
+
if categVal is not None:
|
|
347
|
+
dval = np.array(categVal).reshape(-1) # force to be an 1d array
|
|
348
|
+
|
|
349
|
+
if len(np.unique(dval)) != len(dval):
|
|
350
|
+
err_msg = f'{fname}: `categVal` contains duplicated entries'
|
|
351
|
+
if logger: logger.error(err_msg)
|
|
352
|
+
raise Imgplot3dError(err_msg)
|
|
353
|
+
|
|
354
|
+
# Check 'categCol' (if not None)
|
|
355
|
+
if categCol is not None and len(categCol) != len(dval):
|
|
356
|
+
err_msg = f'{fname}: length of `categVal` and length of `categCol` differ'
|
|
357
|
+
if logger: logger.error(err_msg)
|
|
358
|
+
raise Imgplot3dError(err_msg)
|
|
359
|
+
|
|
360
|
+
else:
|
|
361
|
+
# Possibly exclude values from zz
|
|
362
|
+
if excludedVal is not None:
|
|
363
|
+
for val in np.array(excludedVal).reshape(-1):
|
|
364
|
+
np.putmask(zz, zz == val, np.nan)
|
|
365
|
+
|
|
366
|
+
# Get the unique values in zz
|
|
367
|
+
dval = np.array([v for v in np.unique(zz).reshape(-1) if ~np.isnan(v)])
|
|
368
|
+
if len(dval) > ncateg_max:
|
|
369
|
+
err_msg = f'{fname}: too many categories, set `categ=False`'
|
|
370
|
+
if logger: logger.error(err_msg)
|
|
371
|
+
raise Imgplot3dError(err_msg)
|
|
372
|
+
|
|
373
|
+
if not len(dval): # len(dval) == 0
|
|
374
|
+
err_msg = f'{fname}: no value to be drawn'
|
|
375
|
+
if logger: logger.error(err_msg)
|
|
376
|
+
raise Imgplot3dError(err_msg)
|
|
377
|
+
|
|
378
|
+
if categActive is not None:
|
|
379
|
+
if len(categActive) != len(dval):
|
|
380
|
+
err_msg = f'{fname}: length of `categActive` invalid (should be the same as length of `categVal`)'
|
|
381
|
+
if logger: logger.error(err_msg)
|
|
382
|
+
raise Imgplot3dError(err_msg)
|
|
383
|
+
|
|
384
|
+
else:
|
|
385
|
+
categActive = np.ones(len(dval), dtype='bool')
|
|
386
|
+
|
|
387
|
+
# Replace dval[i] by i in zz if categActive[i] is True otherwise by np.nan, and other values by np.nan
|
|
388
|
+
zz2 = np.array(zz) # copy array
|
|
389
|
+
zz[...] = np.nan # initialize
|
|
390
|
+
for i, v in enumerate(dval):
|
|
391
|
+
if categActive[i]:
|
|
392
|
+
zz[zz2 == v] = i
|
|
393
|
+
|
|
394
|
+
del zz2
|
|
395
|
+
|
|
396
|
+
# Set 'colorList': the list of colors to use
|
|
397
|
+
colorList = None
|
|
398
|
+
if categCol is not None:
|
|
399
|
+
if len(categCol) >= len(dval):
|
|
400
|
+
colorList = [categCol[i] for i in range(len(dval))]
|
|
401
|
+
# colorList = [mcolors.ColorConverter().to_rgba(categCol[i]) for i in range(len(dval))]
|
|
402
|
+
|
|
403
|
+
elif categColCycle:
|
|
404
|
+
if verbose > 0:
|
|
405
|
+
if logger:
|
|
406
|
+
logger.warning(f'{fname}: `categCol` is used cyclically (too few entries)')
|
|
407
|
+
else:
|
|
408
|
+
print(f'{fname}: WARNING: `categCol` is used cyclically (too few entries)')
|
|
409
|
+
colorList = [categCol[i%len(categCol)] for i in range(len(dval))]
|
|
410
|
+
|
|
411
|
+
else:
|
|
412
|
+
if verbose > 0:
|
|
413
|
+
if logger:
|
|
414
|
+
logger.warning(f'{fname}: `categCol` not used (too few entries)')
|
|
415
|
+
else:
|
|
416
|
+
print(f'{fname}: WARNING: `categCol` not used (too few entries)')
|
|
417
|
+
|
|
418
|
+
if colorList is None:
|
|
419
|
+
# Use colors from cmap
|
|
420
|
+
colorList = [cmap(x) for x in np.arange(len(dval)) * 1.0/(len(dval)-1)]
|
|
421
|
+
|
|
422
|
+
# Set the colormap: 'cmap'
|
|
423
|
+
# - Trick: duplicate last color (if len(colorList)> 1)!
|
|
424
|
+
if len(colorList) == 1:
|
|
425
|
+
colorList.append(colorList[-1])
|
|
426
|
+
cmap = ccol.custom_cmap(colorList, ncol=len(colorList), alpha=alpha)
|
|
427
|
+
|
|
428
|
+
# Set the min and max of the colorbar
|
|
429
|
+
cmin, cmax = 0, len(dval) # works, but scalar bar annotations may be shifted of +0.5, see below
|
|
430
|
+
# cmin, cmax = -0.5, len(dval) - 0.5 # does not work
|
|
431
|
+
|
|
432
|
+
# Set scalar bar annotations if not given
|
|
433
|
+
if scalar_bar_annotations == {}:
|
|
434
|
+
if len(dval) <= scalar_bar_annotations_max: # avoid too many annotations (very slow and useless)
|
|
435
|
+
for i, v in enumerate(dval):
|
|
436
|
+
scalar_bar_annotations[i+0.5] = f'{v:.3g}'
|
|
437
|
+
|
|
438
|
+
scalar_bar_kwargs['n_labels'] = 0
|
|
439
|
+
scalar_bar_kwargs['n_colors'] = len(dval)
|
|
440
|
+
|
|
441
|
+
else: # categ == False
|
|
442
|
+
# --- Treat continuous variable ---
|
|
443
|
+
# Possibly exclude values from zz
|
|
444
|
+
if excludedVal is not None:
|
|
445
|
+
for val in np.array(excludedVal).reshape(-1): # force to be an 1d array
|
|
446
|
+
np.putmask(zz, zz == val, np.nan)
|
|
447
|
+
|
|
448
|
+
# Set cmin and cmax if not specified
|
|
449
|
+
if cmin is None:
|
|
450
|
+
cmin = np.nanmin(zz)
|
|
451
|
+
|
|
452
|
+
if cmax is None:
|
|
453
|
+
cmax = np.nanmax(zz)
|
|
454
|
+
|
|
455
|
+
# Set pyvista ImageData (previously: UniformGrid)
|
|
456
|
+
xmin = im.ox + ix0 * im.sx
|
|
457
|
+
xmax = im.ox + ix1 * im.sx
|
|
458
|
+
xdim = ix1 - ix0 + 1
|
|
459
|
+
|
|
460
|
+
ymin = im.oy + iy0 * im.sy
|
|
461
|
+
ymay = im.oy + iy1 * im.sy
|
|
462
|
+
ydim = iy1 - iy0 + 1
|
|
463
|
+
|
|
464
|
+
zmin = im.oz + iz0 * im.sz
|
|
465
|
+
zmaz = im.oz + iz1 * im.sz
|
|
466
|
+
zdim = iz1 - iz0 + 1
|
|
467
|
+
|
|
468
|
+
# pg = pv.UniformGrid(dims=(xdim, ydim, zdim), spacing=(im.sx, im.sy, im.sz), origin=(xmin, ymin, zmin))
|
|
469
|
+
# pg = pv.UniformGrid(dimensions=(xdim, ydim, zdim), spacing=(im.sx, im.sy, im.sz), origin=(xmin, ymin, zmin))
|
|
470
|
+
pg = pv.ImageData(dimensions=(xdim, ydim, zdim), spacing=(im.sx, im.sy, im.sz), origin=(xmin, ymin, zmin))
|
|
471
|
+
|
|
472
|
+
pg.cell_data[im.varname[iv]] = zz #.flatten()
|
|
473
|
+
|
|
474
|
+
if plotter is not None:
|
|
475
|
+
pp = plotter
|
|
476
|
+
else:
|
|
477
|
+
pp = pv.Plotter()
|
|
478
|
+
|
|
479
|
+
if use_clip_plane:
|
|
480
|
+
add_mesh_func = pp.add_mesh_clip_plane
|
|
481
|
+
else:
|
|
482
|
+
add_mesh_func = pp.add_mesh
|
|
483
|
+
|
|
484
|
+
add_mesh_func(pg.threshold(value=(cmin, cmax)), cmap=cmap, clim=(cmin, cmax), annotations=scalar_bar_annotations, show_scalar_bar=False, **kwargs)
|
|
485
|
+
|
|
486
|
+
if background_color is not None:
|
|
487
|
+
pp.background_color = background_color
|
|
488
|
+
|
|
489
|
+
if foreground_color is not None:
|
|
490
|
+
for d in [scalar_bar_kwargs, outline_kwargs, bounds_kwargs, axes_kwargs, text_kwargs]:
|
|
491
|
+
if 'color' not in d.keys():
|
|
492
|
+
d['color'] = foreground_color
|
|
493
|
+
|
|
494
|
+
if show_scalar_bar:
|
|
495
|
+
# # - old -
|
|
496
|
+
# # pg.cell_arrays[im.varname[iv]][...] = np.nan # trick: set all value to nan and use nan_opacity = 0 for empty plot but 'saving' the scalar bar...
|
|
497
|
+
# # # pp.add_mesh(pg, cmap=cmap, clim=(cmin, cmax), nan_opacity=0., annotations=scalar_bar_annotations, show_scalar_bar=True, scalar_bar_args=scalar_bar_kwargs)
|
|
498
|
+
# # pp.add_mesh(pg, cmap=cmap, clim=(cmin, cmax), nan_opacity=0., annotations=scalar_bar_annotations, show_scalar_bar=False)
|
|
499
|
+
# # - old -
|
|
500
|
+
# # Trick: set opacity=0 and nan_opacity=0 for empty plot but 'saving' the scalar bar...
|
|
501
|
+
# pp.add_mesh(pg, cmap=cmap, clim=(cmin, cmax), opacity=0., nan_opacity=0., annotations=scalar_bar_annotations, show_scalar_bar=False)
|
|
502
|
+
if 'title' not in scalar_bar_kwargs.keys():
|
|
503
|
+
scalar_bar_kwargs['title'] = im.varname[iv]
|
|
504
|
+
pp.add_scalar_bar(**scalar_bar_kwargs)
|
|
505
|
+
|
|
506
|
+
if show_outline:
|
|
507
|
+
pp.add_mesh(pg.outline(), **outline_kwargs)
|
|
508
|
+
|
|
509
|
+
if show_bounds:
|
|
510
|
+
pp.show_bounds(**bounds_kwargs)
|
|
511
|
+
|
|
512
|
+
if show_axes:
|
|
513
|
+
pp.add_axes(**axes_kwargs)
|
|
514
|
+
|
|
515
|
+
if text is not None:
|
|
516
|
+
pp.add_text(text, **text_kwargs)
|
|
517
|
+
|
|
518
|
+
if plotter is None:
|
|
519
|
+
pp.show(cpos=cpos)
|
|
520
|
+
# ----------------------------------------------------------------------------
|
|
521
|
+
|
|
522
|
+
# ----------------------------------------------------------------------------
|
|
523
|
+
def drawImage3D_slice (
|
|
524
|
+
im,
|
|
525
|
+
plotter=None,
|
|
526
|
+
ix0=0, ix1=None,
|
|
527
|
+
iy0=0, iy1=None,
|
|
528
|
+
iz0=0, iz1=None,
|
|
529
|
+
iv=0,
|
|
530
|
+
slice_normal_x=None,
|
|
531
|
+
slice_normal_y=None,
|
|
532
|
+
slice_normal_z=None,
|
|
533
|
+
slice_normal_custom=None,
|
|
534
|
+
cmap=ccol.cmap_def,
|
|
535
|
+
cmin=None, cmax=None,
|
|
536
|
+
alpha=None,
|
|
537
|
+
excludedVal=None,
|
|
538
|
+
categ=False,
|
|
539
|
+
ncateg_max=30,
|
|
540
|
+
categVal=None,
|
|
541
|
+
categCol=None,
|
|
542
|
+
categColCycle=False,
|
|
543
|
+
categActive=None,
|
|
544
|
+
show_scalar_bar=True,
|
|
545
|
+
show_outline=True,
|
|
546
|
+
show_bounds=False,
|
|
547
|
+
show_axes=True,
|
|
548
|
+
text=None,
|
|
549
|
+
scalar_bar_annotations=None,
|
|
550
|
+
scalar_bar_annotations_max=20,
|
|
551
|
+
scalar_bar_kwargs=None,
|
|
552
|
+
outline_kwargs=None,
|
|
553
|
+
bounds_kwargs=None,
|
|
554
|
+
axes_kwargs=None,
|
|
555
|
+
text_kwargs=None,
|
|
556
|
+
background_color=None,
|
|
557
|
+
foreground_color=None,
|
|
558
|
+
cpos=None,
|
|
559
|
+
verbose=1,
|
|
560
|
+
logger=None,
|
|
561
|
+
**kwargs):
|
|
562
|
+
"""
|
|
563
|
+
Displays a 3D image as slices(s) (based on `pyvista`).
|
|
564
|
+
|
|
565
|
+
Parameters
|
|
566
|
+
----------
|
|
567
|
+
im : :class:`geone.img.Img`
|
|
568
|
+
image (3D)
|
|
569
|
+
|
|
570
|
+
plotter : :class:`pyvista.Plotter`, optional
|
|
571
|
+
- if given (not `None`), add element to the plotter, a further call to \
|
|
572
|
+
`plotter.show()` will be required to show the plot
|
|
573
|
+
- if not given (`None`, default): a plotter is created and the plot \
|
|
574
|
+
is shown
|
|
575
|
+
|
|
576
|
+
ix0 : int, default: 0
|
|
577
|
+
index of first slice along x direction, considered for plotting
|
|
578
|
+
|
|
579
|
+
ix1 : int, optional
|
|
580
|
+
1+index of last slice along x direction (`ix0 < ix1`), considered for
|
|
581
|
+
plotting; by default: number of cells in x direction (`ix1=im.nx`) is
|
|
582
|
+
used
|
|
583
|
+
|
|
584
|
+
iy0 : int, default: 0
|
|
585
|
+
index of first slice along y direction, considered for plotting
|
|
586
|
+
|
|
587
|
+
iy1 : int, optional
|
|
588
|
+
1+index of last slice along y direction (`iy0 < iy1`), considered for
|
|
589
|
+
plotting; by default: number of cells in x direction (`iy1=im.ny`) is
|
|
590
|
+
used
|
|
591
|
+
|
|
592
|
+
iz0 : int, default: 0
|
|
593
|
+
index of first slice along z direction, considered for plotting
|
|
594
|
+
|
|
595
|
+
iz1 : int, optional
|
|
596
|
+
1+index of last slice along z direction (`iz0 < iz1`), considered for
|
|
597
|
+
plotting; by default: number of cells in z direction (`iz1=im.nz`) is
|
|
598
|
+
used
|
|
599
|
+
|
|
600
|
+
iv : int, default: 0
|
|
601
|
+
index of the variable to be displayed
|
|
602
|
+
|
|
603
|
+
slice_normal_x : sequence of values, or single value, optional
|
|
604
|
+
values of the (float) x coordinate where a slice normal to x axis is
|
|
605
|
+
displayed
|
|
606
|
+
|
|
607
|
+
slice_normal_y : sequence of values, or single value, optional
|
|
608
|
+
values of the (float) y coordinate where a slice normal to y axis is
|
|
609
|
+
displayed
|
|
610
|
+
|
|
611
|
+
slice_normal_z : sequence of values, or single value, optional
|
|
612
|
+
values of the (float) z coordinate where a slice normal to z axis is
|
|
613
|
+
displayed
|
|
614
|
+
|
|
615
|
+
slice_normal_custom : (sequence of) sequence(s) of two 3-tuple, optional
|
|
616
|
+
definition of custom normal slice(s) to be displayed, a slice is
|
|
617
|
+
defined by a sequence two 3-tuple, ((vx, vy, vz), (px, py, pz)): slice
|
|
618
|
+
normal to the vector (vx, vy, vz) and going through the point (px, py, pz)
|
|
619
|
+
|
|
620
|
+
cmap : colormap, default: `geone.customcolors.cmap_def`
|
|
621
|
+
color map (can be a string, in this case the color map
|
|
622
|
+
`matplotlib.pyplot.get_cmap(cmap)`)
|
|
623
|
+
|
|
624
|
+
cmin : float, optional
|
|
625
|
+
used only if `categ=False`:
|
|
626
|
+
minimal value to be displayed; by default: minimal value of the displayed
|
|
627
|
+
variable is used for `cmin`
|
|
628
|
+
|
|
629
|
+
cmax : float, optional
|
|
630
|
+
used only if `categ=False`:
|
|
631
|
+
maximal value to be displayed; by default: maximal value of the displayed
|
|
632
|
+
variable is used for `cmax`
|
|
633
|
+
|
|
634
|
+
alpha : float, optional
|
|
635
|
+
value of the "alpha" channel (for transparency);
|
|
636
|
+
by default (`None`): `alpha=1.0` is used (no transparency)
|
|
637
|
+
|
|
638
|
+
excludedVal : sequence of values, or single value, optional
|
|
639
|
+
values to be excluded from the plot;
|
|
640
|
+
note not used if `categ=True` and `categVal` is not `None`
|
|
641
|
+
|
|
642
|
+
categ : bool, default: False
|
|
643
|
+
indicates if the variable of the image to diplay has to be treated as a
|
|
644
|
+
categorical (discrete) variable (True), or continuous variable (False)
|
|
645
|
+
|
|
646
|
+
ncateg_max : int, default: 30
|
|
647
|
+
used only if `categ=True`:
|
|
648
|
+
maximal number of categories, if there are more category values and
|
|
649
|
+
`categVal=None`, nothing is plotted (`categ` should set to False)
|
|
650
|
+
|
|
651
|
+
categVal : sequence of values, or single value, optional
|
|
652
|
+
used only if `categ=True`:
|
|
653
|
+
explicit list of the category values to be displayed;
|
|
654
|
+
by default (`None`): the list of all unique values are automatically
|
|
655
|
+
retrieved
|
|
656
|
+
|
|
657
|
+
categCol: sequence of colors, optional
|
|
658
|
+
used only if `categ=True`:
|
|
659
|
+
sequence of colors, (given as 3-tuple (RGB code), 4-tuple (RGBA code) or
|
|
660
|
+
str), used for the category values that will be displayed:
|
|
661
|
+
|
|
662
|
+
- if `categVal` is not `None`: `categCol` must have the same length as \
|
|
663
|
+
`categVal`
|
|
664
|
+
- if `categVal=None`:
|
|
665
|
+
- first colors of `categCol` are used if its length is greater than \
|
|
666
|
+
or equal to the number of displayed category values,
|
|
667
|
+
- otherwise: the colors of `categCol` are used cyclically if \
|
|
668
|
+
`categColCycle=True`, and the colors taken from the color map `cmap` \
|
|
669
|
+
are used if `categColCycle=False`
|
|
670
|
+
|
|
671
|
+
categColCycle : bool, default: False
|
|
672
|
+
used only if `categ=True`:
|
|
673
|
+
indicates if the colors of `categCol` can be used cyclically or not
|
|
674
|
+
(when the number of displayed category values exceeds the length of
|
|
675
|
+
`categCol`)
|
|
676
|
+
|
|
677
|
+
categActive : 1D array-like of bools, optional
|
|
678
|
+
used only if `categ=True`, sequence of same length as `categVal`:
|
|
679
|
+
|
|
680
|
+
- `categActive[i]=True`: `categVal[i]` is displayed
|
|
681
|
+
- `categActive[i]=False`: `categVal[i]` is not displayed
|
|
682
|
+
|
|
683
|
+
by default (`None`): all category values `categVal` are displayed
|
|
684
|
+
|
|
685
|
+
show_scalar_bar : bool, default: True
|
|
686
|
+
indicates if scalar bar (color bar) is displayed
|
|
687
|
+
|
|
688
|
+
show_outline : bool, default: True
|
|
689
|
+
indicates if outline (around the image) is displayed
|
|
690
|
+
|
|
691
|
+
show_bounds : bool, default: False
|
|
692
|
+
indicates if bounds are displayed (box with graduation)
|
|
693
|
+
|
|
694
|
+
show_axes : bool, default: True
|
|
695
|
+
indicates if axes are displayed
|
|
696
|
+
|
|
697
|
+
text : str, optional
|
|
698
|
+
text (title) to be displayed on the figure
|
|
699
|
+
|
|
700
|
+
scalar_bar_annotations : dict, optional
|
|
701
|
+
annotation (ticks) on the scalar bar (color bar), used if
|
|
702
|
+
`show_scalar_bar=True`
|
|
703
|
+
scalar_bar_annotations_max : int, default: 20
|
|
704
|
+
maximal number of annotations (ticks) on the scalar bar (color bar)
|
|
705
|
+
when `categ=True` and `scalar_bar_annotations=None`
|
|
706
|
+
|
|
707
|
+
scalar_bar_kwargs : dict, optional
|
|
708
|
+
keyword arguments passed to function `plotter.add_scalar_bar`
|
|
709
|
+
(can be useful for customization, used if `show_scalar_bar=True`)
|
|
710
|
+
note: in subplots (multi-sub-window), key 'title' should be distinct for
|
|
711
|
+
each subplot
|
|
712
|
+
|
|
713
|
+
outline_kwargs : dict, optional
|
|
714
|
+
keyword arguments passed to function `plotter.add_mesh`
|
|
715
|
+
(can be useful for customization, used if `show_outline=True`)
|
|
716
|
+
|
|
717
|
+
bounds_kwargs : dict, optional
|
|
718
|
+
keyword arguments passed to function `plotter.show_bounds`
|
|
719
|
+
(can be useful for customization, used if `show_bounds=True`)
|
|
720
|
+
|
|
721
|
+
axes_kwargs : dict, optional
|
|
722
|
+
keyword arguments passed to function `plotter.add_axes`
|
|
723
|
+
(can be useful for customization, used if `show_axes=True`)
|
|
724
|
+
|
|
725
|
+
text_kwargs : dict, optional
|
|
726
|
+
keyword arguments passed to function `plotter.add_text`
|
|
727
|
+
(can be useful for customization, used if `text` is not `None`)
|
|
728
|
+
|
|
729
|
+
background_color : color, optional
|
|
730
|
+
background color (3-tuple (RGB code), 4-tuple (RGBA code) or str)
|
|
731
|
+
|
|
732
|
+
foreground_color : color, optional
|
|
733
|
+
foreground color (3-tuple (RGB code), 4-tuple (RGBA code) or str)
|
|
734
|
+
|
|
735
|
+
cpos : sequence[sequence[float]], optional
|
|
736
|
+
camera position (unsused if `plotter=None`);
|
|
737
|
+
`cpos` = [camera_location, focus_point, viewup_vector], with
|
|
738
|
+
|
|
739
|
+
- camera_location: (tuple of length 3) camera location ("eye")
|
|
740
|
+
- focus_point : (tuple of length 3) focus point
|
|
741
|
+
- viewup_vector : (tuple of length 3) viewup vector (vector \
|
|
742
|
+
attached to the "head" and pointed to the "sky")
|
|
743
|
+
|
|
744
|
+
note: in principle, (focus_point - camera_location) is orthogonal to
|
|
745
|
+
viewup_vector
|
|
746
|
+
|
|
747
|
+
verbose : int, default: 1
|
|
748
|
+
verbose mode, higher implies more printing (info)
|
|
749
|
+
|
|
750
|
+
logger : :class:`logging.Logger`, optional
|
|
751
|
+
logger (see package `logging`)
|
|
752
|
+
if specified, messages are written via `logger` (no print)
|
|
753
|
+
|
|
754
|
+
kwargs : dict
|
|
755
|
+
additional keyword arguments passed to `plotter.add_mesh`
|
|
756
|
+
when plotting the variable, such as
|
|
757
|
+
|
|
758
|
+
- opacity (float, or str) : \
|
|
759
|
+
opacity for colors; \
|
|
760
|
+
default: 'linear', (set 'linear_r' to invert opacity)
|
|
761
|
+
- show_edges (bool) : \
|
|
762
|
+
indicates if edges of the grid are displayed
|
|
763
|
+
- edge_color (color) : \
|
|
764
|
+
color (3-tuple (RGB code), 4-tuple (RGBA code) or str) for edges \
|
|
765
|
+
(used if `show_edges=True`)
|
|
766
|
+
- line_width (float) \
|
|
767
|
+
line width for edges (used if `show_edges=True`)
|
|
768
|
+
- etc.
|
|
769
|
+
|
|
770
|
+
Notes
|
|
771
|
+
-----
|
|
772
|
+
- 'scalar bar', and 'axes' may be not displayed in multiple-plot, bug ?
|
|
773
|
+
"""
|
|
774
|
+
fname = 'drawImage3D_slice'
|
|
775
|
+
|
|
776
|
+
# Check iv
|
|
777
|
+
if iv < 0:
|
|
778
|
+
iv = im.nv + iv
|
|
779
|
+
|
|
780
|
+
if iv < 0 or iv >= im.nv:
|
|
781
|
+
err_msg = f'{fname}: invalid `iv` index'
|
|
782
|
+
if logger: logger.error(err_msg)
|
|
783
|
+
raise Imgplot3dError(err_msg)
|
|
784
|
+
|
|
785
|
+
# Set indices to be plotted
|
|
786
|
+
if ix1 is None:
|
|
787
|
+
ix1 = im.nx
|
|
788
|
+
|
|
789
|
+
if iy1 is None:
|
|
790
|
+
iy1 = im.ny
|
|
791
|
+
|
|
792
|
+
if iz1 is None:
|
|
793
|
+
iz1 = im.nz
|
|
794
|
+
|
|
795
|
+
if ix0 >= ix1 or ix0 < 0 or ix1 > im.nx:
|
|
796
|
+
err_msg = f'{fname}: invalid indices along x axis'
|
|
797
|
+
if logger: logger.error(err_msg)
|
|
798
|
+
raise Imgplot3dError(err_msg)
|
|
799
|
+
|
|
800
|
+
if iy0 >= iy1 or iy0 < 0 or iy1 > im.ny:
|
|
801
|
+
err_msg = f'{fname}: invalid indices along y axis'
|
|
802
|
+
if logger: logger.error(err_msg)
|
|
803
|
+
raise Imgplot3dError(err_msg)
|
|
804
|
+
|
|
805
|
+
if iz0 >= iz1 or iz0 < 0 or iz1 > im.nz:
|
|
806
|
+
err_msg = f'{fname}: invalid indices along z axis'
|
|
807
|
+
if logger: logger.error(err_msg)
|
|
808
|
+
raise Imgplot3dError(err_msg)
|
|
809
|
+
|
|
810
|
+
# Get the color map
|
|
811
|
+
if isinstance(cmap, str):
|
|
812
|
+
try:
|
|
813
|
+
cmap = plt.get_cmap(cmap)
|
|
814
|
+
except:
|
|
815
|
+
err_msg = f'{fname}: invalid `cmap` string'
|
|
816
|
+
if logger: logger.error(err_msg)
|
|
817
|
+
raise Imgplot3dError(err_msg)
|
|
818
|
+
|
|
819
|
+
# Initialization of dictionary (do not use {} as default argument, it is not re-initialized...)
|
|
820
|
+
if scalar_bar_annotations is None:
|
|
821
|
+
scalar_bar_annotations = {}
|
|
822
|
+
|
|
823
|
+
if scalar_bar_kwargs is None:
|
|
824
|
+
scalar_bar_kwargs = {}
|
|
825
|
+
|
|
826
|
+
if outline_kwargs is None:
|
|
827
|
+
outline_kwargs = {}
|
|
828
|
+
|
|
829
|
+
if bounds_kwargs is None:
|
|
830
|
+
bounds_kwargs = {}
|
|
831
|
+
|
|
832
|
+
if axes_kwargs is None:
|
|
833
|
+
axes_kwargs = {}
|
|
834
|
+
|
|
835
|
+
if text_kwargs is None:
|
|
836
|
+
text_kwargs = {}
|
|
837
|
+
|
|
838
|
+
# Extract what to be plotted
|
|
839
|
+
# zz = np.array(im.val[iv][iz0:iz1, iy0:iy1, ix0:ix1]) # np.array() to get a copy
|
|
840
|
+
zz = im.val[iv][iz0:iz1, iy0:iy1, ix0:ix1].flatten() # .flatten() provides a copy
|
|
841
|
+
|
|
842
|
+
if categ:
|
|
843
|
+
# --- Treat categorical variable ---
|
|
844
|
+
if categCol is not None \
|
|
845
|
+
and type(categCol) is not list \
|
|
846
|
+
and type(categCol) is not tuple:
|
|
847
|
+
err_msg = f'{fname}: `categCol` must be a list or a tuple (if not `None`)'
|
|
848
|
+
if logger: logger.error(err_msg)
|
|
849
|
+
raise Imgplot3dError(err_msg)
|
|
850
|
+
|
|
851
|
+
# Get array 'dval' of displayed values (at least for color bar)
|
|
852
|
+
if categVal is not None:
|
|
853
|
+
dval = np.array(categVal).reshape(-1) # force to be an 1d array
|
|
854
|
+
|
|
855
|
+
if len(np.unique(dval)) != len(dval):
|
|
856
|
+
err_msg = f'{fname}: `categVal` contains duplicated entries'
|
|
857
|
+
if logger: logger.error(err_msg)
|
|
858
|
+
raise Imgplot3dError(err_msg)
|
|
859
|
+
|
|
860
|
+
# Check 'categCol' (if not None)
|
|
861
|
+
if categCol is not None and len(categCol) != len(dval):
|
|
862
|
+
err_msg = f'{fname}: length of `categVal` and length of `categCol` differ'
|
|
863
|
+
if logger: logger.error(err_msg)
|
|
864
|
+
raise Imgplot3dError(err_msg)
|
|
865
|
+
|
|
866
|
+
else:
|
|
867
|
+
# Possibly exclude values from zz
|
|
868
|
+
if excludedVal is not None:
|
|
869
|
+
for val in np.array(excludedVal).reshape(-1):
|
|
870
|
+
np.putmask(zz, zz == val, np.nan)
|
|
871
|
+
|
|
872
|
+
# Get the unique values in zz
|
|
873
|
+
dval = np.array([v for v in np.unique(zz).reshape(-1) if ~np.isnan(v)])
|
|
874
|
+
if len(dval) > ncateg_max:
|
|
875
|
+
err_msg = f'{fname}: too many categories, set `categ=False`'
|
|
876
|
+
if logger: logger.error(err_msg)
|
|
877
|
+
raise Imgplot3dError(err_msg)
|
|
878
|
+
|
|
879
|
+
if not len(dval): # len(dval) == 0
|
|
880
|
+
err_msg = f'{fname}: no value to be drawn'
|
|
881
|
+
if logger: logger.error(err_msg)
|
|
882
|
+
raise Imgplot3dError(err_msg)
|
|
883
|
+
|
|
884
|
+
if categActive is not None:
|
|
885
|
+
if len(categActive) != len(dval):
|
|
886
|
+
err_msg = f'{fname}: length of `categActive` invalid (should be the same as length of `categVal`)'
|
|
887
|
+
if logger: logger.error(err_msg)
|
|
888
|
+
raise Imgplot3dError(err_msg)
|
|
889
|
+
|
|
890
|
+
else:
|
|
891
|
+
categActive = np.ones(len(dval), dtype='bool')
|
|
892
|
+
|
|
893
|
+
# Replace dval[i] by i in zz if categActive[i] is True otherwise by np.nan, and other values by np.nan
|
|
894
|
+
zz2 = np.array(zz) # copy array
|
|
895
|
+
zz[...] = np.nan # initialize
|
|
896
|
+
for i, v in enumerate(dval):
|
|
897
|
+
if categActive[i]:
|
|
898
|
+
zz[zz2 == v] = i
|
|
899
|
+
|
|
900
|
+
del zz2
|
|
901
|
+
|
|
902
|
+
# Set 'colorList': the list of colors to use
|
|
903
|
+
colorList = None
|
|
904
|
+
if categCol is not None:
|
|
905
|
+
if len(categCol) >= len(dval):
|
|
906
|
+
colorList = [categCol[i] for i in range(len(dval))]
|
|
907
|
+
# colorList = [mcolors.ColorConverter().to_rgba(categCol[i]) for i in range(len(dval))]
|
|
908
|
+
|
|
909
|
+
elif categColCycle:
|
|
910
|
+
if verbose > 0:
|
|
911
|
+
if logger:
|
|
912
|
+
logger.warning(f'{fname}: `categCol` is used cyclically (too few entries)')
|
|
913
|
+
else:
|
|
914
|
+
print(f'{fname}: WARNING: `categCol` is used cyclically (too few entries)')
|
|
915
|
+
colorList = [categCol[i%len(categCol)] for i in range(len(dval))]
|
|
916
|
+
|
|
917
|
+
else:
|
|
918
|
+
if verbose > 0:
|
|
919
|
+
if logger:
|
|
920
|
+
logger.warning(f'{fname}: `categCol` not used (too few entries)')
|
|
921
|
+
else:
|
|
922
|
+
print(f'{fname}: WARNING: `categCol` not used (too few entries)')
|
|
923
|
+
|
|
924
|
+
if colorList is None:
|
|
925
|
+
# Use colors from cmap
|
|
926
|
+
colorList = [cmap(x) for x in np.arange(len(dval)) * 1.0/(len(dval)-1)]
|
|
927
|
+
|
|
928
|
+
# Set the colormap: 'cmap'
|
|
929
|
+
# - Trick: duplicate last color (if len(colorList)> 1)!
|
|
930
|
+
if len(colorList) == 1:
|
|
931
|
+
colorList.append(colorList[-1])
|
|
932
|
+
cmap = ccol.custom_cmap(colorList, ncol=len(colorList), alpha=alpha)
|
|
933
|
+
|
|
934
|
+
# Set the min and max of the colorbar
|
|
935
|
+
cmin, cmax = 0, len(dval) # works, but scalar bar annotations may be shifted of +0.5, see below
|
|
936
|
+
# cmin, cmax = -0.5, len(dval) - 0.5 # does not work
|
|
937
|
+
|
|
938
|
+
# Set scalar bar annotations if not given
|
|
939
|
+
if scalar_bar_annotations == {}:
|
|
940
|
+
if len(dval) <= scalar_bar_annotations_max: # avoid too many annotations (very slow and useless)
|
|
941
|
+
for i, v in enumerate(dval):
|
|
942
|
+
scalar_bar_annotations[i+0.5] = f'{v:.3g}'
|
|
943
|
+
|
|
944
|
+
scalar_bar_kwargs['n_labels'] = 0
|
|
945
|
+
scalar_bar_kwargs['n_colors'] = len(dval)
|
|
946
|
+
|
|
947
|
+
else: # categ == False
|
|
948
|
+
# --- Treat continuous variable ---
|
|
949
|
+
# Possibly exclude values from zz
|
|
950
|
+
if excludedVal is not None:
|
|
951
|
+
for val in np.array(excludedVal).reshape(-1): # force to be an 1d array
|
|
952
|
+
np.putmask(zz, zz == val, np.nan)
|
|
953
|
+
|
|
954
|
+
# Set cmin and cmax if not specified
|
|
955
|
+
if cmin is None:
|
|
956
|
+
cmin = np.nanmin(zz)
|
|
957
|
+
|
|
958
|
+
if cmax is None:
|
|
959
|
+
cmax = np.nanmax(zz)
|
|
960
|
+
|
|
961
|
+
# Set pyvista ImageData (previously: UniformGrid)
|
|
962
|
+
xmin = im.ox + ix0 * im.sx
|
|
963
|
+
xmax = im.ox + ix1 * im.sx
|
|
964
|
+
xdim = ix1 - ix0 + 1
|
|
965
|
+
|
|
966
|
+
ymin = im.oy + iy0 * im.sy
|
|
967
|
+
ymay = im.oy + iy1 * im.sy
|
|
968
|
+
ydim = iy1 - iy0 + 1
|
|
969
|
+
|
|
970
|
+
zmin = im.oz + iz0 * im.sz
|
|
971
|
+
zmaz = im.oz + iz1 * im.sz
|
|
972
|
+
zdim = iz1 - iz0 + 1
|
|
973
|
+
|
|
974
|
+
# pg = pv.UniformGrid(dims=(xdim, ydim, zdim), spacing=(im.sx, im.sy, im.sz), origin=(xmin, ymin, zmin))
|
|
975
|
+
# pg = pv.UniformGrid(dimensions=(xdim, ydim, zdim), spacing=(im.sx, im.sy, im.sz), origin=(xmin, ymin, zmin))
|
|
976
|
+
pg = pv.ImageData(dimensions=(xdim, ydim, zdim), spacing=(im.sx, im.sy, im.sz), origin=(xmin, ymin, zmin))
|
|
977
|
+
|
|
978
|
+
pg.cell_data[im.varname[iv]] = zz #.flatten()
|
|
979
|
+
|
|
980
|
+
if plotter is not None:
|
|
981
|
+
pp = plotter
|
|
982
|
+
else:
|
|
983
|
+
pp = pv.Plotter()
|
|
984
|
+
|
|
985
|
+
if slice_normal_x is not None:
|
|
986
|
+
for v in np.array(slice_normal_x).reshape(-1):
|
|
987
|
+
pp.add_mesh(pg.slice(normal=(1,0,0), origin=(v,0,0)), cmap=cmap, clim=(cmin, cmax), annotations=scalar_bar_annotations, show_scalar_bar=False, **kwargs)
|
|
988
|
+
|
|
989
|
+
if slice_normal_y is not None:
|
|
990
|
+
for v in np.array(slice_normal_y).reshape(-1):
|
|
991
|
+
pp.add_mesh(pg.slice(normal=(0,1,0), origin=(0,v,0)), cmap=cmap, clim=(cmin, cmax), annotations=scalar_bar_annotations, show_scalar_bar=False, **kwargs)
|
|
992
|
+
|
|
993
|
+
if slice_normal_z is not None:
|
|
994
|
+
for v in np.array(slice_normal_z).reshape(-1):
|
|
995
|
+
pp.add_mesh(pg.slice(normal=(0,0,1), origin=(0,0,v)), cmap=cmap, clim=(cmin, cmax), annotations=scalar_bar_annotations, show_scalar_bar=False, **kwargs)
|
|
996
|
+
|
|
997
|
+
if slice_normal_custom is not None:
|
|
998
|
+
for nor, ori in np.array(slice_normal_custom).reshape(-1, 2, 3):
|
|
999
|
+
pp.add_mesh(pg.slice(normal=nor, origin=ori), cmap=cmap, clim=(cmin, cmax), annotations=scalar_bar_annotations, show_scalar_bar=False, **kwargs)
|
|
1000
|
+
|
|
1001
|
+
if background_color is not None:
|
|
1002
|
+
pp.background_color = background_color
|
|
1003
|
+
|
|
1004
|
+
if foreground_color is not None:
|
|
1005
|
+
for d in [scalar_bar_kwargs, outline_kwargs, bounds_kwargs, axes_kwargs, text_kwargs]:
|
|
1006
|
+
if 'color' not in d.keys():
|
|
1007
|
+
d['color'] = foreground_color
|
|
1008
|
+
|
|
1009
|
+
if show_scalar_bar:
|
|
1010
|
+
# # - old -
|
|
1011
|
+
# # pg.cell_arrays[im.varname[iv]][...] = np.nan # trick: set all value to nan and use nan_opacity = 0 for empty plot but 'saving' the scalar bar...
|
|
1012
|
+
# # # pp.add_mesh(pg, cmap=cmap, clim=(cmin, cmax), nan_opacity=0., annotations=scalar_bar_annotations, show_scalar_bar=True, scalar_bar_args=scalar_bar_kwargs)
|
|
1013
|
+
# # pp.add_mesh(pg, cmap=cmap, clim=(cmin, cmax), nan_opacity=0., annotations=scalar_bar_annotations, show_scalar_bar=False)
|
|
1014
|
+
# # - old -
|
|
1015
|
+
# # Trick: set opacity=0 and nan_opacity=0 for empty plot but 'saving' the scalar bar...
|
|
1016
|
+
# pp.add_mesh(pg, cmap=cmap, clim=(cmin, cmax), opacity=0., nan_opacity=0., annotations=scalar_bar_annotations, show_scalar_bar=False)
|
|
1017
|
+
if 'title' not in scalar_bar_kwargs.keys():
|
|
1018
|
+
scalar_bar_kwargs['title'] = im.varname[iv]
|
|
1019
|
+
pp.add_scalar_bar(**scalar_bar_kwargs)
|
|
1020
|
+
|
|
1021
|
+
if show_outline:
|
|
1022
|
+
pp.add_mesh(pg.outline(), **outline_kwargs)
|
|
1023
|
+
|
|
1024
|
+
if show_bounds:
|
|
1025
|
+
pp.show_bounds(**bounds_kwargs)
|
|
1026
|
+
|
|
1027
|
+
if show_axes:
|
|
1028
|
+
pp.add_axes(**axes_kwargs)
|
|
1029
|
+
|
|
1030
|
+
if text is not None:
|
|
1031
|
+
pp.add_text(text, **text_kwargs)
|
|
1032
|
+
|
|
1033
|
+
if plotter is None:
|
|
1034
|
+
pp.show(cpos=cpos)
|
|
1035
|
+
# ----------------------------------------------------------------------------
|
|
1036
|
+
|
|
1037
|
+
# ----------------------------------------------------------------------------
|
|
1038
|
+
def drawImage3D_empty_grid (
|
|
1039
|
+
im,
|
|
1040
|
+
plotter=None,
|
|
1041
|
+
ix0=0, ix1=None,
|
|
1042
|
+
iy0=0, iy1=None,
|
|
1043
|
+
iz0=0, iz1=None,
|
|
1044
|
+
cmap=ccol.cmap_def,
|
|
1045
|
+
cmin=None, cmax=None,
|
|
1046
|
+
alpha=None,
|
|
1047
|
+
excludedVal=None,
|
|
1048
|
+
categ=False,
|
|
1049
|
+
ncateg_max=30,
|
|
1050
|
+
categVal=None,
|
|
1051
|
+
categCol=None,
|
|
1052
|
+
categColCycle=False,
|
|
1053
|
+
categActive=None,
|
|
1054
|
+
show_scalar_bar=True,
|
|
1055
|
+
show_outline=True,
|
|
1056
|
+
show_bounds=False,
|
|
1057
|
+
show_axes=True,
|
|
1058
|
+
text=None,
|
|
1059
|
+
scalar_bar_annotations=None,
|
|
1060
|
+
scalar_bar_annotations_max=20,
|
|
1061
|
+
scalar_bar_kwargs=None,
|
|
1062
|
+
outline_kwargs=None,
|
|
1063
|
+
bounds_kwargs=None,
|
|
1064
|
+
axes_kwargs=None,
|
|
1065
|
+
text_kwargs=None,
|
|
1066
|
+
background_color=None,
|
|
1067
|
+
foreground_color=None,
|
|
1068
|
+
cpos=None,
|
|
1069
|
+
verbose=1,
|
|
1070
|
+
logger=None,
|
|
1071
|
+
**kwargs):
|
|
1072
|
+
"""
|
|
1073
|
+
Displays an empty grid from a 3D image.
|
|
1074
|
+
|
|
1075
|
+
Same parameters (if present) as in function :func:`drawImage3D_slice` are used,
|
|
1076
|
+
see this function. (Tricks are applied.)
|
|
1077
|
+
"""
|
|
1078
|
+
fname = 'drawImage3D_empty_grid'
|
|
1079
|
+
|
|
1080
|
+
# Set indices to be plotted
|
|
1081
|
+
if ix1 is None:
|
|
1082
|
+
ix1 = im.nx
|
|
1083
|
+
|
|
1084
|
+
if iy1 is None:
|
|
1085
|
+
iy1 = im.ny
|
|
1086
|
+
|
|
1087
|
+
if iz1 is None:
|
|
1088
|
+
iz1 = im.nz
|
|
1089
|
+
|
|
1090
|
+
if ix0 >= ix1 or ix0 < 0 or ix1 > im.nx:
|
|
1091
|
+
err_msg = f'{fname}: invalid indices along x axis'
|
|
1092
|
+
if logger: logger.error(err_msg)
|
|
1093
|
+
raise Imgplot3dError(err_msg)
|
|
1094
|
+
|
|
1095
|
+
if iy0 >= iy1 or iy0 < 0 or iy1 > im.ny:
|
|
1096
|
+
err_msg = f'{fname}: invalid indices along y axis'
|
|
1097
|
+
if logger: logger.error(err_msg)
|
|
1098
|
+
raise Imgplot3dError(err_msg)
|
|
1099
|
+
|
|
1100
|
+
if iz0 >= iz1 or iz0 < 0 or iz1 > im.nz:
|
|
1101
|
+
err_msg = f'{fname}: invalid indices along z axis'
|
|
1102
|
+
if logger: logger.error(err_msg)
|
|
1103
|
+
raise Imgplot3dError(err_msg)
|
|
1104
|
+
|
|
1105
|
+
# Get the color map
|
|
1106
|
+
if isinstance(cmap, str):
|
|
1107
|
+
try:
|
|
1108
|
+
cmap = plt.get_cmap(cmap)
|
|
1109
|
+
except:
|
|
1110
|
+
err_msg = f'{fname}: invalid `cmap` string'
|
|
1111
|
+
if logger: logger.error(err_msg)
|
|
1112
|
+
raise Imgplot3dError(err_msg)
|
|
1113
|
+
|
|
1114
|
+
# Initialization of dictionary (do not use {} as default argument, it is not re-initialized...)
|
|
1115
|
+
if scalar_bar_annotations is None:
|
|
1116
|
+
scalar_bar_annotations = {}
|
|
1117
|
+
|
|
1118
|
+
if scalar_bar_kwargs is None:
|
|
1119
|
+
scalar_bar_kwargs = {}
|
|
1120
|
+
|
|
1121
|
+
if outline_kwargs is None:
|
|
1122
|
+
outline_kwargs = {}
|
|
1123
|
+
|
|
1124
|
+
if bounds_kwargs is None:
|
|
1125
|
+
bounds_kwargs = {}
|
|
1126
|
+
|
|
1127
|
+
if axes_kwargs is None:
|
|
1128
|
+
axes_kwargs = {}
|
|
1129
|
+
|
|
1130
|
+
if text_kwargs is None:
|
|
1131
|
+
text_kwargs = {}
|
|
1132
|
+
|
|
1133
|
+
# Extract what to be plotted
|
|
1134
|
+
vname = 'tmp'
|
|
1135
|
+
zz = np.nan * np.ones((iz1-iz0, iy1-iy0, ix1-ix0)).flatten()
|
|
1136
|
+
|
|
1137
|
+
if categ:
|
|
1138
|
+
# --- Treat categorical variable ---
|
|
1139
|
+
if categCol is not None \
|
|
1140
|
+
and type(categCol) is not list \
|
|
1141
|
+
and type(categCol) is not tuple:
|
|
1142
|
+
err_msg = f'{fname}: `categCol` must be a list or a tuple (if not `None`)'
|
|
1143
|
+
if logger: logger.error(err_msg)
|
|
1144
|
+
raise Imgplot3dError(err_msg)
|
|
1145
|
+
|
|
1146
|
+
# Get array 'dval' of displayed values (at least for color bar)
|
|
1147
|
+
if categVal is not None:
|
|
1148
|
+
dval = np.array(categVal).reshape(-1) # force to be an 1d array
|
|
1149
|
+
|
|
1150
|
+
if len(np.unique(dval)) != len(dval):
|
|
1151
|
+
err_msg = f'{fname}: `categVal` contains duplicated entries'
|
|
1152
|
+
if logger: logger.error(err_msg)
|
|
1153
|
+
raise Imgplot3dError(err_msg)
|
|
1154
|
+
|
|
1155
|
+
# Check 'categCol' (if not None)
|
|
1156
|
+
if categCol is not None and len(categCol) != len(dval):
|
|
1157
|
+
err_msg = f'{fname}: length of `categVal` and length of `categCol` differ'
|
|
1158
|
+
if logger: logger.error(err_msg)
|
|
1159
|
+
raise Imgplot3dError(err_msg)
|
|
1160
|
+
|
|
1161
|
+
else:
|
|
1162
|
+
# Possibly exclude values from zz
|
|
1163
|
+
if excludedVal is not None:
|
|
1164
|
+
for val in np.array(excludedVal).reshape(-1):
|
|
1165
|
+
np.putmask(zz, zz == val, np.nan)
|
|
1166
|
+
|
|
1167
|
+
# Get the unique values in zz
|
|
1168
|
+
dval = np.array([v for v in np.unique(zz).reshape(-1) if ~np.isnan(v)])
|
|
1169
|
+
if len(dval) > ncateg_max:
|
|
1170
|
+
err_msg = f'{fname}: too many categories, set `categ=False`'
|
|
1171
|
+
if logger: logger.error(err_msg)
|
|
1172
|
+
raise Imgplot3dError(err_msg)
|
|
1173
|
+
|
|
1174
|
+
if not len(dval): # len(dval) == 0
|
|
1175
|
+
err_msg = f'{fname}: no value to be drawn'
|
|
1176
|
+
if logger: logger.error(err_msg)
|
|
1177
|
+
raise Imgplot3dError(err_msg)
|
|
1178
|
+
|
|
1179
|
+
if categActive is not None:
|
|
1180
|
+
if len(categActive) != len(dval):
|
|
1181
|
+
err_msg = f'{fname}: length of `categActive` invalid (should be the same as length of `categVal`)'
|
|
1182
|
+
if logger: logger.error(err_msg)
|
|
1183
|
+
raise Imgplot3dError(err_msg)
|
|
1184
|
+
else:
|
|
1185
|
+
categActive = np.ones(len(dval), dtype='bool')
|
|
1186
|
+
|
|
1187
|
+
# Replace dval[i] by i in zz if categActive[i] is True otherwise by np.nan, and other values by np.nan
|
|
1188
|
+
zz2 = np.array(zz) # copy array
|
|
1189
|
+
zz[...] = np.nan # initialize
|
|
1190
|
+
for i, v in enumerate(dval):
|
|
1191
|
+
if categActive[i]:
|
|
1192
|
+
zz[zz2 == v] = i
|
|
1193
|
+
|
|
1194
|
+
del zz2
|
|
1195
|
+
|
|
1196
|
+
# Set 'colorList': the list of colors to use
|
|
1197
|
+
colorList = None
|
|
1198
|
+
if categCol is not None:
|
|
1199
|
+
if len(categCol) >= len(dval):
|
|
1200
|
+
colorList = [categCol[i] for i in range(len(dval))]
|
|
1201
|
+
# colorList = [mcolors.ColorConverter().to_rgba(categCol[i]) for i in range(len(dval))]
|
|
1202
|
+
|
|
1203
|
+
elif categColCycle:
|
|
1204
|
+
if verbose > 0:
|
|
1205
|
+
if logger:
|
|
1206
|
+
logger.warning(f'{fname}: `categCol` is used cyclically (too few entries)')
|
|
1207
|
+
else:
|
|
1208
|
+
print(f'{fname}: WARNING: `categCol` is used cyclically (too few entries)')
|
|
1209
|
+
colorList = [categCol[i%len(categCol)] for i in range(len(dval))]
|
|
1210
|
+
|
|
1211
|
+
else:
|
|
1212
|
+
if verbose > 0:
|
|
1213
|
+
if logger:
|
|
1214
|
+
logger.warning(f'{fname}: `categCol` not used (too few entries)')
|
|
1215
|
+
else:
|
|
1216
|
+
print(f'{fname}: WARNING: `categCol` not used (too few entries)')
|
|
1217
|
+
|
|
1218
|
+
if colorList is None:
|
|
1219
|
+
# Use colors from cmap
|
|
1220
|
+
colorList = [cmap(x) for x in np.arange(len(dval)) * 1.0/(len(dval)-1)]
|
|
1221
|
+
|
|
1222
|
+
# Set the colormap: 'cmap'
|
|
1223
|
+
# - Trick: duplicate last color (if len(colorList)> 1)!
|
|
1224
|
+
if len(colorList) == 1:
|
|
1225
|
+
colorList.append(colorList[-1])
|
|
1226
|
+
cmap = ccol.custom_cmap(colorList, ncol=len(colorList), alpha=alpha)
|
|
1227
|
+
|
|
1228
|
+
# Set the min and max of the colorbar
|
|
1229
|
+
cmin, cmax = 0, len(dval) # works, but scalar bar annotations may be shifted of +0.5, see below
|
|
1230
|
+
# cmin, cmax = -0.5, len(dval) - 0.5 # does not work
|
|
1231
|
+
|
|
1232
|
+
# Set scalar bar annotations if not given
|
|
1233
|
+
if scalar_bar_annotations == {}:
|
|
1234
|
+
if len(dval) <= scalar_bar_annotations_max: # avoid too many annotations (very slow and useless)
|
|
1235
|
+
for i, v in enumerate(dval):
|
|
1236
|
+
scalar_bar_annotations[i+0.5] = f'{v:.3g}'
|
|
1237
|
+
|
|
1238
|
+
scalar_bar_kwargs['n_labels'] = 0
|
|
1239
|
+
scalar_bar_kwargs['n_colors'] = len(dval)
|
|
1240
|
+
|
|
1241
|
+
else: # categ == False
|
|
1242
|
+
# --- Treat continuous variable ---
|
|
1243
|
+
# Possibly exclude values from zz
|
|
1244
|
+
if excludedVal is not None:
|
|
1245
|
+
for val in np.array(excludedVal).reshape(-1): # force to be an 1d array
|
|
1246
|
+
np.putmask(zz, zz == val, np.nan)
|
|
1247
|
+
|
|
1248
|
+
# Set cmin and cmax if not specified
|
|
1249
|
+
if cmin is None:
|
|
1250
|
+
cmin = np.nanmin(zz)
|
|
1251
|
+
|
|
1252
|
+
if cmax is None:
|
|
1253
|
+
cmax = np.nanmax(zz)
|
|
1254
|
+
|
|
1255
|
+
# Set pyvista ImageData (previously: UniformGrid)
|
|
1256
|
+
xmin = im.ox + ix0 * im.sx
|
|
1257
|
+
xmax = im.ox + ix1 * im.sx
|
|
1258
|
+
xdim = ix1 - ix0 + 1
|
|
1259
|
+
|
|
1260
|
+
ymin = im.oy + iy0 * im.sy
|
|
1261
|
+
ymay = im.oy + iy1 * im.sy
|
|
1262
|
+
ydim = iy1 - iy0 + 1
|
|
1263
|
+
|
|
1264
|
+
zmin = im.oz + iz0 * im.sz
|
|
1265
|
+
zmaz = im.oz + iz1 * im.sz
|
|
1266
|
+
zdim = iz1 - iz0 + 1
|
|
1267
|
+
|
|
1268
|
+
# pg = pv.UniformGrid(dims=(xdim, ydim, zdim), spacing=(im.sx, im.sy, im.sz), origin=(xmin, ymin, zmin))
|
|
1269
|
+
# pg = pv.UniformGrid(dimensions=(xdim, ydim, zdim), spacing=(im.sx, im.sy, im.sz), origin=(xmin, ymin, zmin))
|
|
1270
|
+
pg = pv.ImageData(dimensions=(xdim, ydim, zdim), spacing=(im.sx, im.sy, im.sz), origin=(xmin, ymin, zmin))
|
|
1271
|
+
|
|
1272
|
+
pg.cell_data[vname] = zz #.flatten()
|
|
1273
|
+
|
|
1274
|
+
if plotter is not None:
|
|
1275
|
+
pp = plotter
|
|
1276
|
+
else:
|
|
1277
|
+
pp = pv.Plotter()
|
|
1278
|
+
|
|
1279
|
+
# Here is the trick!
|
|
1280
|
+
kwargs['opacity']=0.0
|
|
1281
|
+
pp.add_mesh(pg.slice(normal=(1,0,0), origin=(im.ox + (ix0+0.5)*im.sx,0,0)), cmap=cmap, clim=(cmin, cmax), annotations=scalar_bar_annotations, show_scalar_bar=False, **kwargs)
|
|
1282
|
+
|
|
1283
|
+
if background_color is not None:
|
|
1284
|
+
pp.background_color = background_color
|
|
1285
|
+
|
|
1286
|
+
if foreground_color is not None:
|
|
1287
|
+
for d in [scalar_bar_kwargs, outline_kwargs, bounds_kwargs, axes_kwargs, text_kwargs]:
|
|
1288
|
+
if 'color' not in d.keys():
|
|
1289
|
+
d['color'] = foreground_color
|
|
1290
|
+
|
|
1291
|
+
if show_scalar_bar:
|
|
1292
|
+
pp.add_scalar_bar(**scalar_bar_kwargs)
|
|
1293
|
+
|
|
1294
|
+
if show_outline:
|
|
1295
|
+
pp.add_mesh(pg.outline(), **outline_kwargs)
|
|
1296
|
+
|
|
1297
|
+
if show_bounds:
|
|
1298
|
+
pp.show_bounds(**bounds_kwargs)
|
|
1299
|
+
|
|
1300
|
+
if show_axes:
|
|
1301
|
+
pp.add_axes(**axes_kwargs)
|
|
1302
|
+
|
|
1303
|
+
if text is not None:
|
|
1304
|
+
pp.add_text(text, **text_kwargs)
|
|
1305
|
+
|
|
1306
|
+
if plotter is None:
|
|
1307
|
+
pp.show(cpos=cpos)
|
|
1308
|
+
# ----------------------------------------------------------------------------
|
|
1309
|
+
|
|
1310
|
+
# ----------------------------------------------------------------------------
|
|
1311
|
+
def drawImage3D_volume (
|
|
1312
|
+
im,
|
|
1313
|
+
plotter=None,
|
|
1314
|
+
ix0=0, ix1=None,
|
|
1315
|
+
iy0=0, iy1=None,
|
|
1316
|
+
iz0=0, iz1=None,
|
|
1317
|
+
iv=0,
|
|
1318
|
+
cmap=ccol.cmap_def,
|
|
1319
|
+
cmin=None, cmax=None,
|
|
1320
|
+
set_out_values_to_nan=True,
|
|
1321
|
+
show_scalar_bar=True,
|
|
1322
|
+
show_outline=True,
|
|
1323
|
+
show_bounds=False,
|
|
1324
|
+
show_axes=True,
|
|
1325
|
+
text=None,
|
|
1326
|
+
scalar_bar_annotations=None,
|
|
1327
|
+
scalar_bar_kwargs=None,
|
|
1328
|
+
outline_kwargs=None,
|
|
1329
|
+
bounds_kwargs=None,
|
|
1330
|
+
axes_kwargs=None,
|
|
1331
|
+
text_kwargs=None,
|
|
1332
|
+
background_color=None,
|
|
1333
|
+
foreground_color=None,
|
|
1334
|
+
cpos=None,
|
|
1335
|
+
logger=None,
|
|
1336
|
+
**kwargs):
|
|
1337
|
+
"""
|
|
1338
|
+
Displays a 3D image as volume (based on `pyvista`).
|
|
1339
|
+
|
|
1340
|
+
Parameters
|
|
1341
|
+
----------
|
|
1342
|
+
im : :class:`geone.img.Img`
|
|
1343
|
+
image (3D)
|
|
1344
|
+
|
|
1345
|
+
plotter : :class:`pyvista.Plotter`, optional
|
|
1346
|
+
- if given (not `None`), add element to the plotter, a further call to \
|
|
1347
|
+
`plotter.show()` will be required to show the plot
|
|
1348
|
+
- if not given (`None`, default): a plotter is created and the plot \
|
|
1349
|
+
is shown
|
|
1350
|
+
|
|
1351
|
+
ix0 : int, default: 0
|
|
1352
|
+
index of first slice along x direction, considered for plotting
|
|
1353
|
+
|
|
1354
|
+
ix1 : int, optional
|
|
1355
|
+
1+index of last slice along x direction (`ix0 < ix1`), considered for
|
|
1356
|
+
plotting; by default: number of cells in x direction (`ix1=im.nx`) is
|
|
1357
|
+
used
|
|
1358
|
+
|
|
1359
|
+
iy0 : int, default: 0
|
|
1360
|
+
index of first slice along y direction, considered for plotting
|
|
1361
|
+
|
|
1362
|
+
iy1 : int, optional
|
|
1363
|
+
1+index of last slice along y direction (`iy0 < iy1`), considered for
|
|
1364
|
+
plotting; by default: number of cells in x direction (`iy1=im.ny`) is
|
|
1365
|
+
used
|
|
1366
|
+
|
|
1367
|
+
iz0 : int, default: 0
|
|
1368
|
+
index of first slice along z direction, considered for plotting
|
|
1369
|
+
|
|
1370
|
+
iz1 : int, optional
|
|
1371
|
+
1+index of last slice along z direction (`iz0 < iz1`), considered for
|
|
1372
|
+
plotting; by default: number of cells in z direction (`iz1=im.nz`) is
|
|
1373
|
+
used
|
|
1374
|
+
|
|
1375
|
+
iv : int, default: 0
|
|
1376
|
+
index of the variable to be displayed
|
|
1377
|
+
|
|
1378
|
+
cmap : colormap, default: `geone.customcolors.cmap_def`
|
|
1379
|
+
color map (can be a string, in this case the color map is obtained by
|
|
1380
|
+
`matplotlib.pyplot.get_cmap(cmap)`)
|
|
1381
|
+
|
|
1382
|
+
cmin : float, optional
|
|
1383
|
+
used only if `categ=False`:
|
|
1384
|
+
minimal value to be displayed; by default: minimal value of the displayed
|
|
1385
|
+
variable is used for `cmin`
|
|
1386
|
+
|
|
1387
|
+
cmax : float, optional
|
|
1388
|
+
used only if `categ=False`:
|
|
1389
|
+
maximal value to be displayed; by default: maximal value of the displayed
|
|
1390
|
+
variable is used for `cmax`
|
|
1391
|
+
|
|
1392
|
+
set_out_values_to_nan : bool, default: True
|
|
1393
|
+
indicates if values out of the range `[cmin, cmax]` is set to `numpy.nan`
|
|
1394
|
+
before plotting
|
|
1395
|
+
|
|
1396
|
+
show_scalar_bar : bool, default: True
|
|
1397
|
+
indicates if scalar bar (color bar) is displayed
|
|
1398
|
+
|
|
1399
|
+
show_outline : bool, default: True
|
|
1400
|
+
indicates if outline (around the image) is displayed
|
|
1401
|
+
|
|
1402
|
+
show_bounds : bool, default: False
|
|
1403
|
+
indicates if bounds are displayed (box with graduation)
|
|
1404
|
+
|
|
1405
|
+
show_axes : bool, default: True
|
|
1406
|
+
indicates if axes are displayed
|
|
1407
|
+
|
|
1408
|
+
text : str, optional
|
|
1409
|
+
text (title) to be displayed on the figure
|
|
1410
|
+
|
|
1411
|
+
scalar_bar_annotations : dict, optional
|
|
1412
|
+
annotation (ticks) on the scalar bar (color bar), used if
|
|
1413
|
+
`show_scalar_bar=True`
|
|
1414
|
+
|
|
1415
|
+
scalar_bar_kwargs : dict, optional
|
|
1416
|
+
keyword arguments passed to function `plotter.add_scalar_bar`
|
|
1417
|
+
(can be useful for customization, used if `show_scalar_bar=True`)
|
|
1418
|
+
note: in subplots (multi-sub-window), key 'title' should be distinct for
|
|
1419
|
+
each subplot
|
|
1420
|
+
|
|
1421
|
+
outline_kwargs : dict, optional
|
|
1422
|
+
keyword arguments passed to function `plotter.add_mesh`
|
|
1423
|
+
(can be useful for customization, used if `show_outline=True`)
|
|
1424
|
+
|
|
1425
|
+
bounds_kwargs : dict, optional
|
|
1426
|
+
keyword arguments passed to function `plotter.show_bounds`
|
|
1427
|
+
(can be useful for customization, used if `show_bounds=True`)
|
|
1428
|
+
|
|
1429
|
+
axes_kwargs : dict, optional
|
|
1430
|
+
keyword arguments passed to function `plotter.add_axes`
|
|
1431
|
+
(can be useful for customization, used if `show_axes=True`)
|
|
1432
|
+
|
|
1433
|
+
text_kwargs : dict, optional
|
|
1434
|
+
keyword arguments passed to function `plotter.add_text`
|
|
1435
|
+
(can be useful for customization, used if `text` is not `None`)
|
|
1436
|
+
|
|
1437
|
+
background_color : color, optional
|
|
1438
|
+
background color (3-tuple (RGB code), 4-tuple (RGBA code) or str)
|
|
1439
|
+
|
|
1440
|
+
foreground_color : color, optional
|
|
1441
|
+
foreground color (3-tuple (RGB code), 4-tuple (RGBA code) or str)
|
|
1442
|
+
|
|
1443
|
+
cpos : sequence[sequence[float]], optional
|
|
1444
|
+
camera position (unsused if `plotter=None`);
|
|
1445
|
+
`cpos` = [camera_location, focus_point, viewup_vector], with
|
|
1446
|
+
|
|
1447
|
+
- camera_location: (tuple of length 3) camera location ("eye")
|
|
1448
|
+
- focus_point : (tuple of length 3) focus point
|
|
1449
|
+
- viewup_vector : (tuple of length 3) viewup vector (vector \
|
|
1450
|
+
attached to the "head" and pointed to the "sky")
|
|
1451
|
+
|
|
1452
|
+
note: in principle, (focus_point - camera_location) is orthogonal to
|
|
1453
|
+
viewup_vector
|
|
1454
|
+
|
|
1455
|
+
logger : :class:`logging.Logger`, optional
|
|
1456
|
+
logger (see package `logging`)
|
|
1457
|
+
if specified, messages are written via `logger` (no print)
|
|
1458
|
+
|
|
1459
|
+
kwargs : dict
|
|
1460
|
+
additional keyword arguments passed to `plotter.add_volume`
|
|
1461
|
+
when plotting the variable, such as
|
|
1462
|
+
|
|
1463
|
+
- opacity (float, or str) : \
|
|
1464
|
+
opacity for colors; \
|
|
1465
|
+
default: 'linear', (set 'linear_r' to invert opacity)
|
|
1466
|
+
- show_edges (bool) : \
|
|
1467
|
+
indicates if edges of the grid are displayed
|
|
1468
|
+
- edge_color (color) : \
|
|
1469
|
+
color (3-tuple (RGB code), 4-tuple (RGBA code) or str) for edges \
|
|
1470
|
+
(used if `show_edges=True`)
|
|
1471
|
+
- line_width (float) \
|
|
1472
|
+
line width for edges (used if `show_edges=True`)
|
|
1473
|
+
- etc.
|
|
1474
|
+
|
|
1475
|
+
Notes
|
|
1476
|
+
-----
|
|
1477
|
+
- 'scalar bar', and 'axes' may be not displayed in multiple-plot, bug ?
|
|
1478
|
+
"""
|
|
1479
|
+
fname = 'drawImage3D_volume'
|
|
1480
|
+
|
|
1481
|
+
# Check iv
|
|
1482
|
+
if iv < 0:
|
|
1483
|
+
iv = im.nv + iv
|
|
1484
|
+
|
|
1485
|
+
if iv < 0 or iv >= im.nv:
|
|
1486
|
+
err_msg = f'{fname}: invalid `iv` index'
|
|
1487
|
+
if logger: logger.error(err_msg)
|
|
1488
|
+
raise Imgplot3dError(err_msg)
|
|
1489
|
+
|
|
1490
|
+
# Set indices to be plotted
|
|
1491
|
+
if ix1 is None:
|
|
1492
|
+
ix1 = im.nx
|
|
1493
|
+
|
|
1494
|
+
if iy1 is None:
|
|
1495
|
+
iy1 = im.ny
|
|
1496
|
+
|
|
1497
|
+
if iz1 is None:
|
|
1498
|
+
iz1 = im.nz
|
|
1499
|
+
|
|
1500
|
+
if ix0 >= ix1 or ix0 < 0 or ix1 > im.nx:
|
|
1501
|
+
err_msg = f'{fname}: invalid indices along x axis'
|
|
1502
|
+
if logger: logger.error(err_msg)
|
|
1503
|
+
raise Imgplot3dError(err_msg)
|
|
1504
|
+
|
|
1505
|
+
if iy0 >= iy1 or iy0 < 0 or iy1 > im.ny:
|
|
1506
|
+
err_msg = f'{fname}: invalid indices along y axis'
|
|
1507
|
+
if logger: logger.error(err_msg)
|
|
1508
|
+
raise Imgplot3dError(err_msg)
|
|
1509
|
+
|
|
1510
|
+
if iz0 >= iz1 or iz0 < 0 or iz1 > im.nz:
|
|
1511
|
+
err_msg = f'{fname}: invalid indices along z axis'
|
|
1512
|
+
if logger: logger.error(err_msg)
|
|
1513
|
+
raise Imgplot3dError(err_msg)
|
|
1514
|
+
|
|
1515
|
+
# Get the color map
|
|
1516
|
+
if isinstance(cmap, str):
|
|
1517
|
+
try:
|
|
1518
|
+
cmap = plt.get_cmap(cmap)
|
|
1519
|
+
except:
|
|
1520
|
+
err_msg = f'{fname}: invalid `cmap` string'
|
|
1521
|
+
if logger: logger.error(err_msg)
|
|
1522
|
+
raise Imgplot3dError(err_msg)
|
|
1523
|
+
|
|
1524
|
+
# Initialization of dictionary (do not use {} as default argument, it is not re-initialized...)
|
|
1525
|
+
if scalar_bar_annotations is None:
|
|
1526
|
+
scalar_bar_annotations = {}
|
|
1527
|
+
|
|
1528
|
+
if scalar_bar_kwargs is None:
|
|
1529
|
+
scalar_bar_kwargs = {}
|
|
1530
|
+
|
|
1531
|
+
if outline_kwargs is None:
|
|
1532
|
+
outline_kwargs = {}
|
|
1533
|
+
|
|
1534
|
+
if bounds_kwargs is None:
|
|
1535
|
+
bounds_kwargs = {}
|
|
1536
|
+
|
|
1537
|
+
if axes_kwargs is None:
|
|
1538
|
+
axes_kwargs = {}
|
|
1539
|
+
|
|
1540
|
+
if text_kwargs is None:
|
|
1541
|
+
text_kwargs = {}
|
|
1542
|
+
|
|
1543
|
+
# Extract what to be plotted
|
|
1544
|
+
# zz = np.array(im.val[iv][iz0:iz1, iy0:iy1, ix0:ix1]) # np.array() to get a copy
|
|
1545
|
+
zz = im.val[iv][iz0:iz1, iy0:iy1, ix0:ix1].flatten() # .flatten() provides a copy
|
|
1546
|
+
|
|
1547
|
+
# Set cmin and cmax if not specified
|
|
1548
|
+
if cmin is None:
|
|
1549
|
+
cmin = np.nanmin(zz)
|
|
1550
|
+
|
|
1551
|
+
if cmax is None:
|
|
1552
|
+
cmax = np.nanmax(zz)
|
|
1553
|
+
|
|
1554
|
+
if set_out_values_to_nan:
|
|
1555
|
+
np.putmask(zz, np.any((np.isnan(zz), zz < cmin, zz > cmax), axis=0), np.nan)
|
|
1556
|
+
|
|
1557
|
+
# Set pyvista ImageData (previously: UniformGrid)
|
|
1558
|
+
xmin = im.ox + ix0 * im.sx
|
|
1559
|
+
xmax = im.ox + ix1 * im.sx
|
|
1560
|
+
xdim = ix1 - ix0 + 1
|
|
1561
|
+
|
|
1562
|
+
ymin = im.oy + iy0 * im.sy
|
|
1563
|
+
ymay = im.oy + iy1 * im.sy
|
|
1564
|
+
ydim = iy1 - iy0 + 1
|
|
1565
|
+
|
|
1566
|
+
zmin = im.oz + iz0 * im.sz
|
|
1567
|
+
zmaz = im.oz + iz1 * im.sz
|
|
1568
|
+
zdim = iz1 - iz0 + 1
|
|
1569
|
+
|
|
1570
|
+
# pg = pv.UniformGrid(dims=(xdim, ydim, zdim), spacing=(im.sx, im.sy, im.sz), origin=(xmin, ymin, zmin))
|
|
1571
|
+
# pg = pv.UniformGrid(dimensions=(xdim, ydim, zdim), spacing=(im.sx, im.sy, im.sz), origin=(xmin, ymin, zmin))
|
|
1572
|
+
pg = pv.ImageData(dimensions=(xdim, ydim, zdim), spacing=(im.sx, im.sy, im.sz), origin=(xmin, ymin, zmin))
|
|
1573
|
+
|
|
1574
|
+
pg.cell_data[im.varname[iv]] = zz #.flatten()
|
|
1575
|
+
|
|
1576
|
+
if plotter is not None:
|
|
1577
|
+
pp = plotter
|
|
1578
|
+
else:
|
|
1579
|
+
pp = pv.Plotter()
|
|
1580
|
+
|
|
1581
|
+
# pp.add_volume(pg.ctp(), cmap=cmap, clim=(cmin, cmax), annotations=scalar_bar_annotations, show_scalar_bar=show_scalar_bar, scalar_bar_args=scalar_bar_kwargs)
|
|
1582
|
+
# pp.add_volume(pg.ctp(), cmap=cmap, clim=(cmin, cmax), opacity_unit_distance=0.1, opacity=opacity, annotations=scalar_bar_annotations, show_scalar_bar=False)
|
|
1583
|
+
pp.add_volume(pg.ctp(), cmap=cmap, clim=(cmin, cmax), annotations=scalar_bar_annotations, show_scalar_bar=False, **kwargs)
|
|
1584
|
+
|
|
1585
|
+
if background_color is not None:
|
|
1586
|
+
pp.background_color = background_color
|
|
1587
|
+
|
|
1588
|
+
if foreground_color is not None:
|
|
1589
|
+
for d in [scalar_bar_kwargs, outline_kwargs, bounds_kwargs, axes_kwargs, text_kwargs]:
|
|
1590
|
+
if 'color' not in d.keys():
|
|
1591
|
+
d['color'] = foreground_color
|
|
1592
|
+
|
|
1593
|
+
if show_scalar_bar:
|
|
1594
|
+
# pp.add_mesh(pg, cmap=cmap, clim=(cmin, cmax), opacity=0., nan_opacity=0., annotations=scalar_bar_annotations, show_scalar_bar=False)
|
|
1595
|
+
if 'title' not in scalar_bar_kwargs.keys():
|
|
1596
|
+
scalar_bar_kwargs['title'] = im.varname[iv]
|
|
1597
|
+
pp.add_scalar_bar(**scalar_bar_kwargs)
|
|
1598
|
+
|
|
1599
|
+
if show_outline:
|
|
1600
|
+
pp.add_mesh(pg.outline(), **outline_kwargs)
|
|
1601
|
+
|
|
1602
|
+
if show_bounds:
|
|
1603
|
+
pp.show_bounds(**bounds_kwargs)
|
|
1604
|
+
|
|
1605
|
+
if show_axes:
|
|
1606
|
+
pp.add_axes(**axes_kwargs)
|
|
1607
|
+
|
|
1608
|
+
if text is not None:
|
|
1609
|
+
pp.add_text(text, **text_kwargs)
|
|
1610
|
+
|
|
1611
|
+
if plotter is None:
|
|
1612
|
+
pp.show(cpos=cpos)
|
|
1613
|
+
# ----------------------------------------------------------------------------
|
|
1614
|
+
|
|
1615
|
+
# From: https://docs.pyvista.org/plotting/plotting.html?highlight=add_mesh#pyvista.BasePlotter.add_mesh
|
|
1616
|
+
# add_scalar_bar(title=None, n_labels=5, italic=False, bold=False, title_font_size=None, label_font_size=None, color=None, font_family=None, shadow=False, mapper=None, width=None, height=None, position_x=None, position_y=None, vertical=None, interactive=None, fmt=None, use_opacity=True, outline=False, nan_annotation=False, below_label=None, above_label=None, background_color=None, n_colors=None, fill=False, render=True)
|
|
1617
|
+
#
|
|
1618
|
+
# Create scalar bar using the ranges as set by the last input mesh.
|
|
1619
|
+
#
|
|
1620
|
+
# Parameters
|
|
1621
|
+
#
|
|
1622
|
+
# title (string, optional) – Title of the scalar bar. Default None
|
|
1623
|
+
#
|
|
1624
|
+
# n_labels (int, optional) – Number of labels to use for the scalar bar.
|
|
1625
|
+
#
|
|
1626
|
+
# italic (bool, optional) – Italicises title and bar labels. Default False.
|
|
1627
|
+
#
|
|
1628
|
+
# bold (bool, optional) – Bolds title and bar labels. Default True
|
|
1629
|
+
#
|
|
1630
|
+
# title_font_size (float, optional) – Sets the size of the title font. Defaults to None and is sized automatically.
|
|
1631
|
+
#
|
|
1632
|
+
# label_font_size (float, optional) – Sets the size of the title font. Defaults to None and is sized automatically.
|
|
1633
|
+
#
|
|
1634
|
+
# color (string or 3 item list, optional, defaults to white) –
|
|
1635
|
+
#
|
|
1636
|
+
# Either a string, rgb list, or hex color string. For example:
|
|
1637
|
+
#
|
|
1638
|
+
# color=’white’ color=’w’ color=[1, 1, 1] color=’#FFFFFF’
|
|
1639
|
+
#
|
|
1640
|
+
# font_family (string, optional) – Font family. Must be either courier, times, or arial.
|
|
1641
|
+
#
|
|
1642
|
+
# shadow (bool, optional) – Adds a black shadow to the text. Defaults to False
|
|
1643
|
+
#
|
|
1644
|
+
# width (float, optional) – The percentage (0 to 1) width of the window for the colorbar
|
|
1645
|
+
#
|
|
1646
|
+
# height (float, optional) – The percentage (0 to 1) height of the window for the colorbar
|
|
1647
|
+
#
|
|
1648
|
+
# position_x (float, optional) – The percentage (0 to 1) along the windows’s horizontal direction to place the bottom left corner of the colorbar
|
|
1649
|
+
#
|
|
1650
|
+
# position_y (float, optional) – The percentage (0 to 1) along the windows’s vertical direction to place the bottom left corner of the colorbar
|
|
1651
|
+
#
|
|
1652
|
+
# interactive (bool, optional) – Use a widget to control the size and location of the scalar bar.
|
|
1653
|
+
#
|
|
1654
|
+
# use_opacity (bool, optional) – Optionally display the opacity mapping on the scalar bar
|
|
1655
|
+
#
|
|
1656
|
+
# outline (bool, optional) – Optionally outline the scalar bar to make opacity mappings more obvious.
|
|
1657
|
+
#
|
|
1658
|
+
# nan_annotation (bool, optional) – Annotate the NaN color
|
|
1659
|
+
#
|
|
1660
|
+
# below_label (str, optional) – String annotation for values below the scalars range
|
|
1661
|
+
#
|
|
1662
|
+
# above_label (str, optional) – String annotation for values above the scalars range
|
|
1663
|
+
#
|
|
1664
|
+
# background_color (array, optional) – The color used for the background in RGB format.
|
|
1665
|
+
#
|
|
1666
|
+
# n_colors (int, optional) – The maximum number of color displayed in the scalar bar.
|
|
1667
|
+
#
|
|
1668
|
+
# fill (bool) – Draw a filled box behind the scalar bar with the background_color
|
|
1669
|
+
#
|
|
1670
|
+
# render (bool, optional) – Force a render when True. Default True.
|
|
1671
|
+
#
|
|
1672
|
+
# Notes
|
|
1673
|
+
#
|
|
1674
|
+
# Setting title_font_size, or label_font_size disables automatic font sizing for both the title and label.
|
|
1675
|
+
|
|
1676
|
+
if __name__ == "__main__":
|
|
1677
|
+
print("Module 'geone.imgplot3d' example:")
|
|
1678
|
+
|
|
1679
|
+
# Example with a 3D gaussian random field
|
|
1680
|
+
|
|
1681
|
+
import geone.covModel as gcm
|
|
1682
|
+
from geone import grf, img
|
|
1683
|
+
|
|
1684
|
+
# Define grid
|
|
1685
|
+
nx, ny, nz = 85, 56, 34
|
|
1686
|
+
dx, dy, dz = 1., 1., 1.
|
|
1687
|
+
ox, oy, oz = 0., 0., 0.
|
|
1688
|
+
|
|
1689
|
+
dimension = [nx, ny, nz]
|
|
1690
|
+
spacing = [dx, dy, dy]
|
|
1691
|
+
origin = [ox, oy, oz]
|
|
1692
|
+
|
|
1693
|
+
# Define covariance model
|
|
1694
|
+
cov_model = gcm.CovModel3D(elem=[
|
|
1695
|
+
('gaussian', {'w':8.9, 'r':[40, 20, 10]}), # elementary contribution
|
|
1696
|
+
('nugget', {'w':0.1}) # elementary contribution
|
|
1697
|
+
], alpha=-30, beta=-45, gamma=20, name='')
|
|
1698
|
+
|
|
1699
|
+
# Set seed
|
|
1700
|
+
np.random.seed(123)
|
|
1701
|
+
|
|
1702
|
+
# Generate GRF
|
|
1703
|
+
v = grf.grf3D(cov_model, (nx, ny, nz), (dx, dy, dz), (ox, oy, oz))
|
|
1704
|
+
im = img.Img(nx, ny, nz, dx, dy, dz, ox, oy, oz, nv=1, val=v)
|
|
1705
|
+
|
|
1706
|
+
# ===== Ex1 =====
|
|
1707
|
+
# Simple plot
|
|
1708
|
+
# ------
|
|
1709
|
+
drawImage3D_volume(im, text='Ex1: volume (cont.)')
|
|
1710
|
+
|
|
1711
|
+
# # Equivalent:
|
|
1712
|
+
# pp = pv.Plotter()
|
|
1713
|
+
# drawImage3D_volume(im, text='Ex1: volume (cont.)')
|
|
1714
|
+
# pp.show()
|
|
1715
|
+
|
|
1716
|
+
# # For saving screenshot (png)
|
|
1717
|
+
# # Note: axes will not be displayed in off screen mode
|
|
1718
|
+
# pp = pv.Plotter(off_screen=True)
|
|
1719
|
+
# drawImage3D_volume(im, plotter=pp)
|
|
1720
|
+
# pp.show(screenshot='test.png')
|
|
1721
|
+
|
|
1722
|
+
# ===== Ex2 =====
|
|
1723
|
+
# Multiple plot
|
|
1724
|
+
# ------
|
|
1725
|
+
# Note: scalar bar is not displayed in all plots (even if show_scalar_bar is True) when
|
|
1726
|
+
# same title for scalar bar is used (see Ex4)
|
|
1727
|
+
pp = pv.Plotter(shape=(2,2))
|
|
1728
|
+
|
|
1729
|
+
pp.subplot(0,0)
|
|
1730
|
+
drawImage3D_surface(im, plotter=pp, text='Ex2: surface (cont.)' )
|
|
1731
|
+
|
|
1732
|
+
pp.subplot(0,1)
|
|
1733
|
+
drawImage3D_volume(im, plotter=pp, text='Ex2: volume (cont.)')
|
|
1734
|
+
|
|
1735
|
+
cx, cy, cz = im.ox+0.5*im.nx*im.sx, im.oy+0.5*im.ny*im.sy, im.oz+0.5*im.nz*im.sz # center of image
|
|
1736
|
+
pp.subplot(1,0)
|
|
1737
|
+
drawImage3D_slice(im, plotter=pp,
|
|
1738
|
+
slice_normal_x=cx,
|
|
1739
|
+
slice_normal_y=cy,
|
|
1740
|
+
slice_normal_z=cz,
|
|
1741
|
+
text='Ex2: slice (cont.)')
|
|
1742
|
+
|
|
1743
|
+
pp.subplot(1,1)
|
|
1744
|
+
drawImage3D_slice(im, plotter=pp,
|
|
1745
|
+
slice_normal_custom=[[(1, 1, 0), (cx, cy, cz)], [(1, -1, 0), (cx, cy, cz)]],
|
|
1746
|
+
text='Ex2: slice (cont.)')
|
|
1747
|
+
|
|
1748
|
+
pp.link_views()
|
|
1749
|
+
pp.show(cpos=(1,2,.5))
|
|
1750
|
+
|
|
1751
|
+
# ===== Ex3 =====
|
|
1752
|
+
# Multiple plot
|
|
1753
|
+
# ------
|
|
1754
|
+
# Note: scalar bar is not displayed in all plots (even if show_scalar_bar is True) when
|
|
1755
|
+
# same title for scalar bar is used (see Ex4)
|
|
1756
|
+
pp = pv.Plotter(shape=(1,3))
|
|
1757
|
+
|
|
1758
|
+
pp.subplot(0,0)
|
|
1759
|
+
drawImage3D_volume(im, plotter=pp, cmin=2, cmax=4, text='Ex3: volume - cmin / cmax')
|
|
1760
|
+
|
|
1761
|
+
pp.subplot(0,1)
|
|
1762
|
+
drawImage3D_volume(im, plotter=pp, cmin=2, cmax=4, set_out_values_to_nan=False, text='Ex3: volume - cmin / cmax - set_out_values_to_nan=False')
|
|
1763
|
+
|
|
1764
|
+
pp.subplot(0,2)
|
|
1765
|
+
drawImage3D_surface(im, plotter=pp, cmin=2, cmax=4, text='Ex3: surface - cmin / cmax')
|
|
1766
|
+
|
|
1767
|
+
pp.link_views()
|
|
1768
|
+
pp.show(cpos=(1,2,.5))
|
|
1769
|
+
|
|
1770
|
+
# Categorize image...
|
|
1771
|
+
# -------------------
|
|
1772
|
+
v = im.val.reshape(-1)
|
|
1773
|
+
newv = np.zeros(im.nxyz())
|
|
1774
|
+
for t in [1., 2., 3., 4.]:
|
|
1775
|
+
np.putmask(newv, np.all((np.abs(v) > t, np.abs(v) <= t+1), axis=0), t)
|
|
1776
|
+
np.putmask(newv, np.abs(v) > 5., 10.)
|
|
1777
|
+
# -> newv takes values 0, 1, 3, 4, 10
|
|
1778
|
+
im.set_var(newv, 'categ', 0) # insert variable in image im
|
|
1779
|
+
|
|
1780
|
+
# ===== Ex4 =====
|
|
1781
|
+
pp = pv.Plotter(shape=(2,3))
|
|
1782
|
+
|
|
1783
|
+
pp.subplot(0,0)
|
|
1784
|
+
drawImage3D_volume(im, plotter=pp,
|
|
1785
|
+
scalar_bar_kwargs={'title':''}, # distinct title in each subplot for correct display!
|
|
1786
|
+
text='Ex4: volume (categ. var.)')
|
|
1787
|
+
|
|
1788
|
+
pp.subplot(0,1)
|
|
1789
|
+
drawImage3D_surface(im, plotter=pp, categ=False,
|
|
1790
|
+
scalar_bar_kwargs={'title':' '}, # distinct title in each subplot for correct display!
|
|
1791
|
+
text='Ex4: surface - categ=False')
|
|
1792
|
+
|
|
1793
|
+
cx, cy, cz = im.ox+0.5*im.nx*im.sx, im.oy+0.5*im.ny*im.sy, im.oz+0.5*im.nz*im.sz # center of image
|
|
1794
|
+
pp.subplot(0,2)
|
|
1795
|
+
drawImage3D_slice(im, plotter=pp, categ=False,
|
|
1796
|
+
slice_normal_x=cx,
|
|
1797
|
+
slice_normal_y=cy,
|
|
1798
|
+
slice_normal_z=cz,
|
|
1799
|
+
scalar_bar_kwargs={'title':' '}, # distinct title in each subplot for correct display!
|
|
1800
|
+
text='Ex4: slice - categ=False')
|
|
1801
|
+
|
|
1802
|
+
pp.subplot(1,1)
|
|
1803
|
+
drawImage3D_surface(im, plotter=pp, categ=True,
|
|
1804
|
+
scalar_bar_kwargs={'title':' '}, # distinct title in each subplot for correct display!
|
|
1805
|
+
text='Ex4: surface - categ=True')
|
|
1806
|
+
|
|
1807
|
+
cx, cy, cz = im.ox+0.5*im.nx*im.sx, im.oy+0.5*im.ny*im.sy, im.oz+0.5*im.nz*im.sz # center of image
|
|
1808
|
+
pp.subplot(1,2)
|
|
1809
|
+
drawImage3D_slice(im, plotter=pp, categ=True,
|
|
1810
|
+
slice_normal_x=cx,
|
|
1811
|
+
slice_normal_y=cy,
|
|
1812
|
+
slice_normal_z=cz,
|
|
1813
|
+
scalar_bar_kwargs={'title':' '}, # distinct title in each subplot for correct display!
|
|
1814
|
+
text='Ex4: slice - categ=True')
|
|
1815
|
+
|
|
1816
|
+
pp.link_views()
|
|
1817
|
+
pp.show(cpos=(1,2,.5))
|
|
1818
|
+
|
|
1819
|
+
# Using some options
|
|
1820
|
+
# -------------------
|
|
1821
|
+
cols=['purple', 'blue', 'cyan', 'yellow', 'red', 'pink']
|
|
1822
|
+
|
|
1823
|
+
# ===== Ex5 =====
|
|
1824
|
+
# Multiple plot
|
|
1825
|
+
pp = pv.Plotter(shape=(2,3))
|
|
1826
|
+
|
|
1827
|
+
pp.subplot(0,0)
|
|
1828
|
+
drawImage3D_surface(im, plotter=pp, categ=True, categVal=[0, 1, 2, 3, 4, 10], categCol=cols,
|
|
1829
|
+
categActive=[True, False, False, False, False, False],
|
|
1830
|
+
scalar_bar_kwargs={'title':''}, # distinct title in each subplot for correct display!
|
|
1831
|
+
text='Ex5: surface - categ=True\n - active categ "0"')
|
|
1832
|
+
|
|
1833
|
+
pp.subplot(0,1)
|
|
1834
|
+
drawImage3D_surface(im, plotter=pp, categ=True, categVal=[0, 1, 2, 3, 4, 10], categCol=cols,
|
|
1835
|
+
categActive=[False, True, False, False, False, False],
|
|
1836
|
+
scalar_bar_kwargs={'title':' '}, # distinct title in each subplot for correct display!
|
|
1837
|
+
text='Ex5: surface - categ=True\n - active categ "1"')
|
|
1838
|
+
|
|
1839
|
+
pp.subplot(0,2)
|
|
1840
|
+
drawImage3D_surface(im, plotter=pp, categ=True, categVal=[0, 1, 2, 3, 4, 10], categCol=cols,
|
|
1841
|
+
categActive=[False, False, True, False, False, False],
|
|
1842
|
+
scalar_bar_kwargs={'title':' '}, # distinct title in each subplot for correct display!
|
|
1843
|
+
text='Ex5: surface - categ=True\n - active categ "2"')
|
|
1844
|
+
|
|
1845
|
+
pp.subplot(1,0)
|
|
1846
|
+
drawImage3D_surface(im, plotter=pp, categ=True, categVal=[0, 1, 2, 3, 4, 10], categCol=cols,
|
|
1847
|
+
categActive=[False, False, False, True, False, False],
|
|
1848
|
+
scalar_bar_kwargs={'title':' '}, # distinct title in each subplot for correct display!
|
|
1849
|
+
text='Ex5: surface - categ=True\n - active categ "3"')
|
|
1850
|
+
|
|
1851
|
+
pp.subplot(1,1)
|
|
1852
|
+
drawImage3D_surface(im, plotter=pp, categ=True, categVal=[0, 1, 2, 3, 4, 10], categCol=cols,
|
|
1853
|
+
categActive=[False, False, False, False, True, False],
|
|
1854
|
+
scalar_bar_kwargs={'title':' '}, # distinct title in each subplot for correct display!
|
|
1855
|
+
text='Ex5: surface - categ=True\n - active categ "4"')
|
|
1856
|
+
|
|
1857
|
+
pp.subplot(1,2)
|
|
1858
|
+
drawImage3D_surface(im, plotter=pp, categ=True, categVal=[0, 1, 2, 3, 4, 10], categCol=cols,
|
|
1859
|
+
categActive=[False, False, False, False, False, True],
|
|
1860
|
+
scalar_bar_kwargs={'title':' '}, # distinct title in each subplot for correct display!
|
|
1861
|
+
text='Ex5: surface - categ=True\n - active categ "10"')
|
|
1862
|
+
|
|
1863
|
+
pp.link_views()
|
|
1864
|
+
pp.show(cpos=(1,2,.5))
|
|
1865
|
+
|
|
1866
|
+
# ===== Ex6 =====
|
|
1867
|
+
# activate only some categories
|
|
1868
|
+
drawImage3D_surface(im, categ=True, categVal=[0, 1, 2, 3, 4, 10], categCol=cols, categActive=[True, False, False, True, False, True],
|
|
1869
|
+
text='Ex6: surface - categ=True - active some categ.')
|
|
1870
|
+
|
|
1871
|
+
# ===== Ex7 =====
|
|
1872
|
+
# do not show outline
|
|
1873
|
+
drawImage3D_surface(im, categ=True, categVal=[0, 1, 2, 3, 4, 10], categCol=cols, categActive=[True, False, False, True, False, True],
|
|
1874
|
+
show_outline=False, text='Ex7: no outline')
|
|
1875
|
+
|
|
1876
|
+
# ===== Ex8 =====
|
|
1877
|
+
# enlarge outline
|
|
1878
|
+
drawImage3D_surface(im, categ=True, categVal=[0, 1, 2, 3, 4, 10], categCol=cols, categActive=[True, False, False, True, False, True],
|
|
1879
|
+
show_outline=True, outline_kwargs={'line_width':5}, text='Ex8: thick outline')
|
|
1880
|
+
|
|
1881
|
+
# ===== Ex9 =====
|
|
1882
|
+
# show bounds
|
|
1883
|
+
drawImage3D_surface(im, categ=True, categVal=[0, 1, 2, 3, 4, 10], categCol=cols, categActive=[True, False, False, True, False, True],
|
|
1884
|
+
show_bounds=True, text='Ex9: show bounds')
|
|
1885
|
+
|
|
1886
|
+
# ===== Ex10 =====
|
|
1887
|
+
# show bounds with grid
|
|
1888
|
+
drawImage3D_surface(im, categ=True, categVal=[0, 1, 2, 3, 4, 10], categCol=cols, categActive=[True, False, False, True, False, True],
|
|
1889
|
+
show_bounds=True, bounds_kwargs={'grid':True}, text='Ex10: bounds and grid')
|
|
1890
|
+
|
|
1891
|
+
# ===== Ex11 =====
|
|
1892
|
+
# customize scalar bar
|
|
1893
|
+
drawImage3D_surface(im, categ=True, categVal=[0, 1, 2, 3, 4, 10], categCol=cols, categActive=[True, False, False, True, False, True],
|
|
1894
|
+
scalar_bar_annotations={0.5:'A', 1.5:'B', 2.5:'C', 3.5:'D', 4.5:'E', 5.5:'high'}, scalar_bar_kwargs={'vertical':True, 'title_font_size':24, 'label_font_size':10},
|
|
1895
|
+
text='Ex11: custom scalar bar')
|
|
1896
|
+
|
|
1897
|
+
# ===== Ex12 =====
|
|
1898
|
+
# scalar bar: interactive position...
|
|
1899
|
+
drawImage3D_surface(im, categ=True, categVal=[0, 1, 2, 3, 4, 10], categCol=cols, categActive=[True, False, False, True, False, True],
|
|
1900
|
+
scalar_bar_kwargs={'interactive':True}, text='Ex12: interactive scalar bar')
|
|
1901
|
+
|
|
1902
|
+
# ===== Ex13 =====
|
|
1903
|
+
# customize title
|
|
1904
|
+
drawImage3D_surface(im, categ=True, categVal=[0, 1, 2, 3, 4, 10], categCol=cols, categActive=[True, False, False, True, False, True],
|
|
1905
|
+
text='Ex13: custom title', text_kwargs={'font_size':12, 'position':'upper_right'})
|
|
1906
|
+
|
|
1907
|
+
# ===== Ex14 =====
|
|
1908
|
+
# customize axes
|
|
1909
|
+
drawImage3D_surface(im, categ=True, categVal=[0, 1, 2, 3, 4, 10], categCol=cols, categActive=[True, False, False, True, False, True],
|
|
1910
|
+
axes_kwargs={'x_color':'pink', 'zlabel':'depth'}, text='Ex14: custom axes')
|
|
1911
|
+
|
|
1912
|
+
# ===== Ex15 =====
|
|
1913
|
+
# changing background / foreground colors
|
|
1914
|
+
drawImage3D_surface(im, categ=True, categVal=[0, 1, 2, 3, 4, 10], categCol=cols, categActive=[True, False, False, True, False, True],
|
|
1915
|
+
background_color=(0.9, 0.9, 0.9), foreground_color='k', text='Ex15: background/foreground colors')
|
|
1916
|
+
|
|
1917
|
+
# ===== Ex16 =====
|
|
1918
|
+
drawImage3D_surface(im, cmin=2, cmax=4, text='Ex16: surface (categ. var) - categ=False - cmin / cmax')
|