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