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/imgplot.py ADDED
@@ -0,0 +1,1367 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ # -------------------------------------------------------------------------
5
+ # Python module: 'imgplot.py'
6
+ # author: Julien Straubhaar
7
+ # date: dec-2017
8
+ # -------------------------------------------------------------------------
9
+
10
+ """
11
+ Module for custom plots of images (class :class:`geone.img.Img`) in 2D.
12
+ """
13
+
14
+ from geone import img
15
+ from geone.img import Img
16
+ from geone import customcolors as ccol
17
+ from geone.customcolors import add_colorbar
18
+
19
+ import numpy as np
20
+ import matplotlib.pyplot as plt
21
+
22
+ from matplotlib import rcParams as mpl_rcParams
23
+ import matplotlib.colors as mcolors
24
+
25
+ # ----------------------------------------------------------------------------
26
+ def drawImage2D(
27
+ im, ix=None, iy=None, iz=None, iv=None,
28
+ plot_empty_grid=False,
29
+ cmap=ccol.cmap_def,
30
+ alpha=None,
31
+ excludedVal=None,
32
+ categ=False, categVal=None,
33
+ categCol=None, categColCycle=False, categColbad=ccol.cbad_def,
34
+ vmin=None, vmax=None,
35
+ contourf=False,
36
+ contour=False,
37
+ contour_clabel=False,
38
+ levels=None,
39
+ contourf_kwargs=None,
40
+ contour_kwargs={'linestyles':'solid', 'colors':'gray'},
41
+ contour_clabel_kwargs={'inline':1},
42
+ interpolation='none',
43
+ aspect='equal',
44
+ frame=True, xaxis=True, yaxis=True,
45
+ title=None,
46
+ xlabel=None, xticks=None, xticklabels=None, xticklabels_max_decimal=None,
47
+ ylabel=None, yticks=None, yticklabels=None, yticklabels_max_decimal=None,
48
+ clabel=None, cticks=None, cticklabels=None, cticklabels_max_decimal=None,
49
+ colorbar_extend='neither',
50
+ colorbar_aspect=20, colorbar_pad_fraction=1.0,
51
+ showColorbar=True, removeColorbar=False, showColorbarOnly=0,
52
+ **kwargs):
53
+ # animated : bool, default: False
54
+ # keyword argument passed to `matplotlib.pyplot.imshow` for animation...
55
+ """
56
+ Displays a 2D image (or a slice of a 3D image).
57
+
58
+ Parameters
59
+ ----------
60
+ im : :class:`geone.img.Img`
61
+ image
62
+
63
+ ix : int, optional
64
+ grid index along x axis of the yz-slice to be displayed;
65
+ by default (`None`): no slice along x axis
66
+
67
+ iy : int, optional
68
+ grid index along y axis of the xz-slice to be displayed;
69
+ by default (`None`): no slice along y axis
70
+
71
+ iz : int, optional
72
+ grid index along z axis of the xy-slice to be displayed;
73
+ by default (`None`): no slice along z axis, but
74
+ if `ix=None`, `iy=None`, `iz=None`, then `iz=0` is used
75
+
76
+ iv : int, optional
77
+ index of the variable to be displayed;
78
+ by default (`None`): the variable of index 0 (`iv=0` is used) if
79
+ the image has at leas one variable (`im.nv > 0`), otherwise, an "empty"
80
+ grid is displayed (a "fake" variable with `numpy.nan` (missing value)
81
+ over the entire grid is considered)
82
+
83
+ plot_empty_grid : bool, default: False
84
+ if `True`: an "empty" grid is displayed (a "fake" variable with `numpy.nan`
85
+ (missing value) over the entire grid is considered), and `iv` is ignored
86
+
87
+ cmap : colormap
88
+ color map (can be a string, in this case the color map is obtained by
89
+ `matplotlib.pyplot.get_cmap(cmap)`)
90
+
91
+ alpha : float, optional
92
+ value of the "alpha" channel (for transparency);
93
+ by default (`None`): `alpha=1.0` is used (no transparency)
94
+
95
+ excludedVal : sequence of values, or single value, optional
96
+ values to be excluded from the plot;
97
+ note not used if `categ=True` and `categVal` is not `None`
98
+
99
+ categ : bool, default: False
100
+ indicates if the variable of the image to diplay has to be treated as a
101
+ categorical (discrete) variable (`True`), or continuous variable (`False`)
102
+
103
+ categVal : sequence of values, or single value, optional
104
+ used only if `categ=True`:
105
+ explicit list of the category values to be displayed;
106
+ by default (`None`): the list of all unique values are automatically
107
+ retrieved
108
+
109
+ categCol: sequence of colors, optional
110
+ used only if `categ=True`:
111
+ sequence of colors, (given as 3-tuple (RGB code), 4-tuple (RGBA code) or
112
+ str), used for the category values that will be displayed:
113
+
114
+ - if `categVal` is not `None`: `categCol` must have the same length as \
115
+ `categVal`
116
+ - if `categVal=None`:
117
+ - first colors of `categCol` are used if its length is greater than \
118
+ or equal to the number of displayed category values,
119
+ - otherwise: the colors of `categCol` are used cyclically if \
120
+ `categColCycle=True`, and the colors taken from the color map `cmap` \
121
+ are used if `categColCycle=False`
122
+
123
+ categColCycle : bool, default: False
124
+ used only if `categ=True`:
125
+ indicates if the colors of `categCol` can be used cyclically or not
126
+ (when the number of displayed category values exceeds the length of
127
+ `categCol`)
128
+
129
+ categColbad : color
130
+ used only if `categ=True`:
131
+ color (3-tuple (RGB code), 4-tuple (RGBA code) or str) used for bad
132
+ categorical value
133
+
134
+ vmin : float, optional
135
+ used only if `categ=False`:
136
+ minimal value to be displayed; by default: minimal value of the displayed
137
+ variable is used for `vmin`
138
+
139
+ vmax : float, optional
140
+ used only if `categ=False`:
141
+ maximal value to be displayed; by default: maximal value of the displayed
142
+ variable is used for `vmax`
143
+
144
+ contourf : bool, default: False
145
+ indicates if `matplotlib.pyplot.contourf` is used, i.e. contour map with
146
+ filled area between levels, instead of standard plot
147
+ (`matplotlib.pyplot.imshow`)
148
+
149
+ contour : bool, default: False
150
+ indicates if contour levels are added to the plot (using
151
+ `matplotlib.pyplot.contour`)
152
+
153
+ contour_clabel : bool, default: False
154
+ indicates if labels are added to contour (if `contour=True`)
155
+
156
+ levels : array-like, or int, optional
157
+ keyword argument 'levels' passed to `matplotlib.pyplot.contourf` (if
158
+ `contourf=True`) and/or `matplotlib.pyplot.contour` (if `contour=True`)
159
+
160
+ contourf_kwargs : dict, optional
161
+ keyword arguments passed to `matplotlib.pyplot.contourf` (if
162
+ `contourf=True`); note: the parameters `levels` (see above) is used as
163
+ keyword argument, i.e. it prevails over the key 'levels' in
164
+ `contourf_kwargs` (if given)
165
+
166
+ contour_kwargs : dict
167
+ keyword arguments passed to `matplotlib.pyplot.contour` (if
168
+ `contour=True`); note: the parameters `levels` (see above) is used as
169
+ keyword argument, i.e. it prevails over the key 'levels' in
170
+ `contour_kwargs` (if given)
171
+
172
+ contour_clabel_kwargs : dict
173
+ keyword arguments passed to `matplotlib.pyplot.clabel` (if
174
+ `contour_clabel=True`)
175
+
176
+ interpolation : str, default: 'none'
177
+ keyword argument 'interpolation' to be passed `matplotlib.pyplot.imshow`
178
+
179
+ aspect : str, or scalar, default: 'equal'
180
+ keyword argument 'aspect' to be passed `matplotlib.pyplot.imshow`
181
+
182
+ frame : bool, default: True
183
+ indicates if a frame is drawn around the image
184
+
185
+ xaxis : bool, default: True
186
+ indicates if x axis is visible
187
+
188
+ yaxis : bool, default: True
189
+ indicates if y axis is visible
190
+
191
+ title : str, optional
192
+ title of the figure
193
+
194
+ xlabel : str, optional
195
+ label for x axis
196
+
197
+ ylabel : str, optional
198
+ label for y axis
199
+
200
+ clabel : str, optional
201
+ label for color bar
202
+
203
+ xticks : sequence of values, optional
204
+ values where to place ticks along x axis
205
+
206
+ yticks : sequence of values, optional
207
+ values where to place ticks along y axis
208
+
209
+ cticks : sequence of values, optional
210
+ values where to place ticks along the color bar
211
+
212
+ xticklabels : sequence of strs, optional,
213
+ sequence of strings for ticks along x axis
214
+
215
+ yticklabels : sequence of strs, optional,
216
+ sequence of strings for ticks along y axis
217
+
218
+ cticklabels : sequence of strs, optional,
219
+ sequence of strings for ticks along the color bar
220
+
221
+ xticklabels_max_decimal : int, optional
222
+ maximal number of decimals (fractional part) for tick labels along x axis
223
+
224
+ yticklabels_max_decimal : int, optional
225
+ maximal number of decimals (fractional part) for tick labels along y axis
226
+
227
+ cticklabels_max_decimal : int, optional
228
+ maximal number of decimals (fractional part) for tick labels along the
229
+ color bar
230
+
231
+ colorbar_extend : str {'neither', 'both', 'min', 'max'}, default: 'neither'
232
+ used only if `categ=False`:
233
+ keyword argument 'extend' to be passed to `matplotlib.pyplot.colorbar`
234
+ (or `geone.customcolors.add_colorbar`)
235
+
236
+ colorbar_aspect : float or int, default: 20
237
+ keyword argument 'aspect' to be passed to
238
+ `geone.customcolors.add_colorbar`
239
+
240
+ colorbar_pad_fraction : float or int, default: 1.0
241
+ keyword argument 'pad_fraction' to be passed to
242
+ `geone.customcolors.add_colorbar`
243
+
244
+ showColorbar : bool, default: True
245
+ indicates if the color bar (vertical) is shown
246
+
247
+ removeColorbar : bool, default: False
248
+ if True (and if `showColorbar=True`), then the colorbar is removed;
249
+ note: it can be useful to draw and then remove the color bar for size of
250
+ the plotted image...)
251
+
252
+ showColorbarOnly : int, default: 0
253
+ mode defining how the color bar (vertical) is shown:
254
+
255
+ - `showColorbarOnly=0`: not used / not applied
256
+ - `showColorbarOnly>0`: only the color bar is shown (even if \
257
+ `showColorbar=False` or if `removeColorbar=True`):
258
+ - `showColorbarOnly=1`: the plotted image is "cleared"
259
+ - `showColorbarOnly=2`: an image of same color as the background is \
260
+ drawn onto the plotted image
261
+
262
+ kwargs : dict
263
+ additional keyword arguments : each keyword argument with the key
264
+ 'xxx_<name>' will be passed as keyword argument with the key '<name>' to
265
+ a function related to 'xxx';
266
+ possibilities for 'xxx\_' and related function:
267
+
268
+ .. list-table::
269
+ :widths: 25 45
270
+ :header-rows: 1
271
+
272
+ * - string (prefix)
273
+ - method from `Axes` from `matplotlib`
274
+ * - 'title\_'
275
+ - `ax.set_title()`
276
+ * - 'xlabel\_'
277
+ - `ax.set_xlabel()`
278
+ * - 'xticks\_'
279
+ - `ax.set_xticks()`
280
+ * - 'xticklabels\_'
281
+ - `ax.set_xticklabels()`
282
+ * - 'ylabel\_'
283
+ - `ax.set_ylabel()`
284
+ * - 'yticks\_'
285
+ - `ax.set_yticks()`
286
+ * - 'yticklabels\_'
287
+ - `ax.set_yticklabels()`
288
+ * - 'clabel\_'
289
+ - `cbar.set_label()`
290
+ * - 'cticks\_'
291
+ - `cbar.set_ticks()`
292
+ * - 'cticklabels\_'
293
+ - `cbar.ax.set_yticklabels()`
294
+
295
+ for examples:
296
+
297
+ - 'title_fontsize', '<x|y|c>label_fontsize' (keys)
298
+ - 'title_fontweight', '<x|y|c>label_fontweight' (keys), with \
299
+ possible values: numeric value in 0-1000 or \
300
+ 'ultralight', 'light', 'normal', 'regular', 'book', 'medium', \
301
+ 'roman', 'semibold', 'demibold', 'demi', 'bold', 'heavy', \
302
+ 'extra bold', 'black'
303
+
304
+ Note that
305
+
306
+ - default value for font size is `matplotlib.rcParams['font.size']`
307
+ - default value for font weight is `matplotlib.rcParams['font.weight']`
308
+
309
+ Returns
310
+ -------
311
+ imout : list of `matplotlib` object
312
+ list of plotted objects
313
+ """
314
+ # - 'title\_' fun: ax.set_title()
315
+ # - 'xlabel\_' fun: ax.set_xlabel()
316
+ # - 'xticks\_' fun: ax.set_xticks()
317
+ # - 'xticklabels\_' fun: ax.set_xticklabels()
318
+ # - 'ylabel\_' fun: ax.set_ylabel()
319
+ # - 'yticks\_' fun: ax.set_yticks()
320
+ # - 'yticklabels\_' fun: ax.set_yticklabels()
321
+ # - 'clabel\_' fun: cbar.set_label()
322
+ # - 'cticks\_' fun: cbar.set_ticks()
323
+ # - 'cticklabels\_' fun: cbar.ax.set_yticklabels()
324
+ fname = 'drawImage2D'
325
+
326
+ # Initialization for output
327
+ imout = []
328
+ #ax, cbar = None, None
329
+
330
+ if plot_empty_grid:
331
+ iv = None
332
+ else:
333
+ # Check / set iv
334
+ if iv is None:
335
+ if im.nv > 0:
336
+ iv = 0
337
+ else:
338
+ if iv < 0:
339
+ iv = im.nv + iv
340
+
341
+ if iv < 0 or iv >= im.nv:
342
+ print(f'ERROR ({fname}): invalid `iv` index!')
343
+ return imout
344
+ #return (ax, cbar)
345
+
346
+ # Check slice direction and indices
347
+ n = int(ix is not None) + int(iy is not None) + int(iz is not None)
348
+
349
+ if n == 0:
350
+ sliceDir = 'z'
351
+ iz = 0
352
+
353
+ elif n==1:
354
+ if ix is not None:
355
+ if ix < 0:
356
+ ix = im.nx + ix
357
+
358
+ if ix < 0 or ix >= im.nx:
359
+ print(f'ERROR ({fname}): invalid `ix` index!')
360
+ return imout
361
+ #return (ax, cbar)
362
+
363
+ sliceDir = 'x'
364
+
365
+ elif iy is not None:
366
+ if iy < 0:
367
+ iy = im.ny + iy
368
+
369
+ if iy < 0 or iy >= im.ny:
370
+ print(f'ERROR ({fname}): invalid `iy` index!')
371
+ return imout
372
+ #return (ax, cbar)
373
+
374
+ sliceDir = 'y'
375
+
376
+ else: # iz is not None
377
+ if iz < 0:
378
+ iz = im.nz + iz
379
+
380
+ if iz < 0 or iz >= im.nz:
381
+ print(f'ERROR ({fname}): invalid `iz` index!')
382
+ return imout
383
+ #return (ax, cbar)
384
+
385
+ sliceDir = 'z'
386
+
387
+ else: # n > 1
388
+ print(f'ERROR ({fname}): slice specified in more than one direction!')
389
+ return imout
390
+ #return (ax, cbar)
391
+
392
+ # Extract what to be plotted
393
+ if sliceDir == 'x':
394
+ dim0 = im.ny
395
+ min0 = im.oy
396
+ max0 = im.ymax()
397
+
398
+ dim1 = im.nz
399
+ min1 = im.oz
400
+ max1 = im.zmax()
401
+ if iv is None: # empty image
402
+ zz = np.nan * np.ones((im.nz, im.ny))
403
+ else:
404
+ zz = np.array(im.val[iv, :, :, ix].reshape(dim1, dim0)) # np.array() to get a copy
405
+ # reshape to force 2-dimensional array
406
+
407
+ elif sliceDir == 'y':
408
+ dim0 = im.nx
409
+ min0 = im.ox
410
+ max0 = im.xmax()
411
+
412
+ dim1 = im.nz
413
+ min1 = im.oz
414
+ max1 = im.zmax()
415
+ if iv is None: # empty image
416
+ zz = np.nan * np.ones((im.nz, im.nx))
417
+ else:
418
+ zz = np.array(im.val[iv, :, iy, :].reshape(dim1, dim0)) # np.array() to get a copy
419
+
420
+ else: # sliceDir == 'z'
421
+ dim0 = im.nx
422
+ min0 = im.ox
423
+ max0 = im.xmax()
424
+
425
+ dim1 = im.ny
426
+ min1 = im.oy
427
+ max1 = im.ymax()
428
+ if iv is None: # empty image
429
+ zz = np.nan * np.ones((im.ny, im.nx))
430
+ else:
431
+ zz = np.array(im.val[iv, iz, :, :].reshape(dim1, dim0)) # np.array() to get a copy
432
+
433
+ # Get the color map
434
+ if isinstance(cmap, str):
435
+ try:
436
+ cmap = plt.get_cmap(cmap)
437
+ except:
438
+ print(f'ERROR ({fname}): invalid `cmap` string!')
439
+ return imout
440
+ #return (ax, cbar)
441
+
442
+ if categ:
443
+ # --- Treat categorical variable ---
444
+ if categCol is not None \
445
+ and type(categCol) is not list \
446
+ and type(categCol) is not tuple:
447
+ print(f"ERROR ({fname}): `categCol` must be a list or a tuple (if not None)!")
448
+ return imout
449
+ #return (ax, cbar)
450
+
451
+ # Get array 'dval' of displayed values
452
+ if categVal is not None:
453
+ dval = np.array(categVal).reshape(-1) # force to be an 1d array
454
+
455
+ if len(np.unique(dval)) != len(dval):
456
+ print(f"ERROR ({fname}): `categVal` contains duplicated entries!")
457
+ return imout
458
+ #return (ax, cbar)
459
+
460
+ # Check 'categCol' (if not None)
461
+ if categCol is not None and len(categCol) != len(dval):
462
+ print(f"ERROR ({fname}): length of `categVal` and `categCol` differ!")
463
+ return imout
464
+ #return (ax, cbar)
465
+
466
+ else:
467
+ # Possibly exclude values from zz
468
+ if excludedVal is not None:
469
+ for val in np.array(excludedVal).reshape(-1):
470
+ np.putmask(zz, zz == val, np.nan)
471
+
472
+ # Get the unique value in zz
473
+ dval = np.array([v for v in np.unique(zz).reshape(-1) if ~np.isnan(v)])
474
+
475
+ if not len(dval): # len(dval) == 0
476
+ print("Warning: no value to be drawn!")
477
+
478
+ # Replace dval[i] by i in zz and other values by np.nan
479
+ zz2 = np.array(zz) # copy array
480
+ zz[...] = np.nan # initialize
481
+ for i, v in enumerate(dval):
482
+ zz[zz2 == v] = i
483
+
484
+ del zz2
485
+
486
+ # Set 'colorList': the list of colors to use
487
+ colorList = None
488
+ if categCol is not None:
489
+ if len(categCol) >= len(dval):
490
+ colorList = [categCol[i] for i in range(len(dval))]
491
+ # colorList = [mcolors.ColorConverter().to_rgba(categCol[i]) for i in range(len(dval))]
492
+
493
+ elif categColCycle:
494
+ print("Warning: 'categCol' is used cyclically (too few entries)")
495
+ colorList = [categCol[i%len(categCol)] for i in range(len(dval))]
496
+
497
+ else:
498
+ print("Warning: 'categCol' not used (too few entries)")
499
+
500
+ if colorList is None:
501
+ # Use colors from cmap
502
+ colorList = [cmap(x) for x in np.arange(len(dval)) * 1.0/(len(dval)-1)]
503
+
504
+ # Set the colormap: 'cmap'
505
+ if len(dval) == 1:
506
+ cmap = ccol.custom_cmap([colorList[0], colorList[0]], ncol=2,
507
+ cbad=categColbad, alpha=alpha)
508
+
509
+ else: # len(dval) == len(colorList) > 1
510
+ # cmap = mcolors.ListedColormap(colorList)
511
+ cmap = ccol.custom_cmap(colorList, ncol=len(colorList),
512
+ cbad=categColbad, alpha=alpha)
513
+
514
+ # Set the min and max of the colorbar
515
+ vmin, vmax = -0.5, len(dval) - 0.5
516
+
517
+ # Set colorbar ticks and ticklabels if not given
518
+ if cticks is None:
519
+ cticks = range(len(dval))
520
+
521
+ if cticklabels is None:
522
+ #cticklabels = ['{:.3g}'.format(v) for v in cticks]
523
+ cticklabels = ['{:.3g}'.format(v) for v in dval]
524
+
525
+ # Reset cextend if needed
526
+ colorbar_extend = 'neither'
527
+
528
+ else: # categ == False
529
+ # --- Treat continuous variable ---
530
+ # Possibly exclude values from zz
531
+ if excludedVal is not None:
532
+ for val in np.array(excludedVal).reshape(-1): # force to be an 1d array
533
+ np.putmask(zz, zz == val, np.nan)
534
+
535
+ # Generate "sub-dictionaries" from kwargs
536
+ # For each item 'xxx_<name>':<value> from kwargs (whose the key begins
537
+ # with "xxx_"): the item '<name>':<value> is added to the dictionary
538
+ # xxx_kwargs
539
+ # "xxx_" is replaced by the strings below (in sub_prefix list)
540
+ # These sub-dictionaries are passed (unpacked) as keyword arguments to
541
+ # functions that draw labels, ticklabels, ...
542
+ title_kwargs = {}
543
+
544
+ xlabel_kwargs = {}
545
+ xticks_kwargs = {}
546
+ xticklabels_kwargs = {}
547
+
548
+ ylabel_kwargs = {}
549
+ yticks_kwargs = {}
550
+ yticklabels_kwargs = {}
551
+
552
+ clabel_kwargs = {}
553
+ cticks_kwargs = {}
554
+ cticklabels_kwargs = {}
555
+
556
+ colorbar_kwargs = {'aspect':colorbar_aspect, 'pad_fraction':colorbar_pad_fraction}
557
+
558
+ sub_prefix = ['title_',
559
+ 'xlabel_', 'xticks_', 'xticklabels_',
560
+ 'ylabel_', 'yticks_', 'yticklabels_',
561
+ 'clabel_', 'cticks_', 'cticklabels_',
562
+ 'colorbar_']
563
+ sub_kwargs = [title_kwargs,
564
+ xlabel_kwargs, xticks_kwargs, xticklabels_kwargs,
565
+ ylabel_kwargs, yticks_kwargs, yticklabels_kwargs,
566
+ clabel_kwargs, cticks_kwargs, cticklabels_kwargs,
567
+ colorbar_kwargs] # list of dictionaries
568
+
569
+ for k, v in kwargs.items():
570
+ for i in range(len(sub_kwargs)):
571
+ n = len(sub_prefix[i])
572
+ if k[0:n] == sub_prefix[i]:
573
+ sub_kwargs[i][k[n:]] = v # add item k[n:]:v to dictionary sub_kwargs[i]
574
+
575
+ if showColorbarOnly:
576
+ # Overwrite some parameters if needed
577
+ frame = False
578
+ xaxis = False
579
+ yaxis = False
580
+ title = None
581
+ showColorbar = True
582
+ removeColorbar = False
583
+
584
+ # Get current axis (for plotting)
585
+ ax = plt.gca()
586
+
587
+ # image plot
588
+ im_plot = ax.imshow(zz, cmap=cmap, alpha=alpha, vmin=vmin, vmax=vmax,
589
+ origin='lower', extent=[min0, max0, min1, max1],
590
+ interpolation=interpolation, aspect=aspect) #, animated=animated)
591
+ imout.append(im_plot)
592
+
593
+ if contourf:
594
+ # imshow is still used above to account for 'aspect'
595
+ # Set key word argument 'levels' from the argument 'levels'
596
+ if contourf_kwargs is None:
597
+ contourf_kwargs = {}
598
+ contourf_kwargs['levels'] = levels
599
+ im_contf = ax.contourf(zz, cmap=cmap, alpha=alpha, vmin=vmin, vmax=vmax,
600
+ origin='lower', extent=[min0, max0, min1, max1],
601
+ **contourf_kwargs)
602
+ imout.append(im_contf)
603
+
604
+ if contour:
605
+ # Set key word argument 'levels' from the argument 'levels'
606
+ contour_kwargs['levels']=levels
607
+ im_cont = ax.contour(zz,
608
+ origin='lower', extent=[min0, max0, min1, max1],
609
+ **contour_kwargs)
610
+ imout.append(im_cont)
611
+ if contour_clabel:
612
+ ax.clabel(im_cont, **contour_clabel_kwargs)
613
+
614
+ # title
615
+ if title is not None:
616
+ ax.set_title(title, **title_kwargs)
617
+
618
+ # xlabel, xticks and xticklabels
619
+ if xlabel is not None:
620
+ ax.set_xlabel(xlabel, **xlabel_kwargs)
621
+
622
+ if xticks is not None:
623
+ ax.set_xticks(xticks, **xticks_kwargs)
624
+
625
+ if xticklabels is not None:
626
+ ax.set_xticklabels(xticklabels, **xticklabels_kwargs)
627
+ elif xticklabels_max_decimal is not None:
628
+ s = 10**xticklabels_max_decimal
629
+ labels = [np.round(t*s)/s for t in ax.get_xticks()]
630
+ ax.set_xticklabels(labels, **xticklabels_kwargs)
631
+ elif len(xticklabels_kwargs):
632
+ ax.set_xticklabels(ax.get_xticks(), **xticklabels_kwargs)
633
+ # else... default xticklabels....
634
+
635
+ # ylabel, yticks and yticklabels
636
+ if ylabel is not None:
637
+ ax.set_ylabel(ylabel, **ylabel_kwargs)
638
+
639
+ if yticks is not None:
640
+ ax.set_yticks(yticks, **yticks_kwargs)
641
+
642
+ if yticklabels is not None:
643
+ ax.set_yticklabels(yticklabels, **yticklabels_kwargs)
644
+ elif yticklabels_max_decimal is not None:
645
+ s = 10**yticklabels_max_decimal
646
+ labels = [np.round(t*s)/s for t in ax.get_yticks()]
647
+ ax.set_yticklabels(labels, **yticklabels_kwargs)
648
+ elif len(yticklabels_kwargs):
649
+ ax.set_yticklabels(ax.get_yticks(), **yticklabels_kwargs)
650
+ # else... default yticklabels....
651
+
652
+ # Display or hide: frame, xaxis, yaxis
653
+ if not frame:
654
+ ax.set_frame_on(False)
655
+
656
+ if not xaxis:
657
+ ax.get_xaxis().set_visible(False)
658
+
659
+ if not yaxis:
660
+ ax.get_yaxis().set_visible(False)
661
+
662
+ # Colorbar
663
+ if showColorbar:
664
+ #cbar_kwargs = {'aspect':colorbar_aspect, 'pad_fraction':colorbar_pad_fraction}
665
+ if not contourf:
666
+ colorbar_kwargs['extend']=colorbar_extend
667
+ cbar = add_colorbar(im_plot, **colorbar_kwargs)
668
+
669
+ if clabel is not None:
670
+ cbar.set_label(clabel, **clabel_kwargs)
671
+
672
+ if cticks is not None:
673
+ cbar.set_ticks(cticks, **cticks_kwargs)
674
+
675
+ if cticklabels is not None:
676
+ cbar.ax.set_yticklabels(cticklabels, **cticklabels_kwargs)
677
+ elif cticklabels_max_decimal is not None:
678
+ s = 10**cticklabels_max_decimal
679
+ labels = [np.round(t*s)/s for t in cbar.get_ticks()]
680
+ cbar.ax.set_yticklabels(labels, **cticklabels_kwargs)
681
+ elif len(cticklabels_kwargs):
682
+ cbar.ax.set_yticklabels(cbar.get_ticks(), **cticklabels_kwargs)
683
+ # else... default cticklabels....
684
+
685
+ if removeColorbar:
686
+ # cbar.ax.get_xaxis().set_visible(False)
687
+ # cbar.ax.get_yaxis().set_visible(False)
688
+ # cbar.ax.clear()
689
+ cbar.ax.set_visible(False)
690
+
691
+ if showColorbarOnly:
692
+ if showColorbarOnly == 1:
693
+ ax.clear() # change the size of the colorbar...
694
+ else:
695
+ # Trick: redraw the image in background color...
696
+ zz[...] = 0
697
+ # bg_color = mpl_rcParams['figure.facecolor'] # background color
698
+ bg_color = ax.get_facecolor() # background color
699
+ # bg_color = plt.gcf().get_facecolor() # background color
700
+ ncmap = ccol.custom_cmap([bg_color,bg_color], ncol=2)
701
+ ax.imshow(zz, cmap=ncmap)
702
+ ax.set_frame_on(True)
703
+ for pos in ('bottom', 'top', 'right', 'left'):
704
+ ax.spines[pos].set_color(bg_color)
705
+ ax.spines[pos].set_linewidth(10)
706
+
707
+ return imout
708
+ #return (ax, cbar)
709
+ # ----------------------------------------------------------------------------
710
+
711
+ # ----------------------------------------------------------------------------
712
+ def get_colors_from_values(
713
+ val,
714
+ cmap=ccol.cmap_def,
715
+ alpha=None,
716
+ excludedVal=None,
717
+ categ=False, categVal=None,
718
+ categCol=None, categColCycle=False, categColbad=ccol.cbad_def,
719
+ vmin=None, vmax=None,
720
+ cmin=None, cmax=None):
721
+ """
722
+ Gets the colors for given values, according to color settings as used in function :func:`imgplot.drawImage2D`.
723
+
724
+ Parameters
725
+ ----------
726
+ val : array-like of floats, or float
727
+ values for which the colors have to be retrieved
728
+
729
+ cmap : see function :func:`imgplot.drawImage2D`
730
+
731
+ alpha : see function :func:`imgplot.drawImage2D`
732
+
733
+ excludedVal : see function :func:`imgplot.drawImage2D`
734
+
735
+ categ : see function :func:`imgplot.drawImage2D`
736
+
737
+ categVal : see function :func:`imgplot.drawImage2D`
738
+
739
+ categCol : see function :func:`imgplot.drawImage2D`
740
+
741
+ categColCycle : see function :func:`imgplot.drawImage2D`
742
+
743
+ categColbad : see function :func:`imgplot.drawImage2D`
744
+
745
+ vmin : see function :func:`imgplot.drawImage2D`
746
+
747
+ vmax : see function :func:`imgplot.drawImage2D`
748
+
749
+ cmin : float, optional
750
+ alternative keyword for `vmin` (for compatibility with color settings
751
+ in the functions of the module `geone.imgplot3d`)
752
+
753
+ cmax : float, optional
754
+ alternative keyword for `vmax` (for compatibility with color settings
755
+ in the functions of the module `geone.imgplot3d`)
756
+
757
+ Returns
758
+ -------
759
+ col : 1D array of colors
760
+ colors used for values in `val` according to the given settings
761
+ """
762
+ fname = 'get_colors_from_values'
763
+
764
+ # Check vmin, cmin and vmax, cmax
765
+ if vmin is not None and cmin is not None:
766
+ print(f'ERROR ({fname}): use `vmin` or `cmin` (not both)')
767
+ return None
768
+
769
+ if vmax is not None and cmax is not None:
770
+ print(f'ERROR ({fname}): use `vmax` or `cmax` (not both)')
771
+ return None
772
+
773
+ if vmin is None:
774
+ vmin = cmin
775
+
776
+ if vmax is None:
777
+ vmax = cmax
778
+
779
+ # Copy val in a 1d array
780
+ zz = np.copy(np.atleast_1d(val)).reshape(-1)
781
+
782
+ # --- Code adapted from function drawImage2D - start ----
783
+ # Get the color map
784
+ if isinstance(cmap, str):
785
+ try:
786
+ cmap = plt.get_cmap(cmap)
787
+ except:
788
+ print(f'ERROR ({fname}): invalid `cmap` string!')
789
+ return None
790
+ # return imout
791
+ # #return (ax, cbar)
792
+
793
+ if categ:
794
+ # --- Treat categorical variable ---
795
+ if categCol is not None \
796
+ and type(categCol) is not list \
797
+ and type(categCol) is not tuple:
798
+ print(f"ERROR ({fname}): `categCol` must be a list or a tuple (if not None)!")
799
+ return None
800
+ # return imout
801
+ # #return (ax, cbar)
802
+
803
+ # Get array 'dval' of displayed values
804
+ if categVal is not None:
805
+ dval = np.array(categVal).reshape(-1) # force to be an 1d array
806
+
807
+ if len(np.unique(dval)) != len(dval):
808
+ print(f"ERROR ({fname}): `categVal` contains duplicated entries!")
809
+ return None
810
+ # return imout
811
+ # #return (ax, cbar)
812
+
813
+ # Check 'categCol' (if not None)
814
+ if categCol is not None and len(categCol) != len(dval):
815
+ print(f"ERROR ({fname}): length of `categVal` and `categCol` differ!")
816
+ return None
817
+ # return imout
818
+ # #return (ax, cbar)
819
+
820
+ else:
821
+ # Possibly exclude values from zz
822
+ if excludedVal is not None:
823
+ for val in np.array(excludedVal).reshape(-1):
824
+ np.putmask(zz, zz == val, np.nan)
825
+
826
+ # Get the unique value in zz
827
+ dval = np.array([v for v in np.unique(zz).reshape(-1) if ~np.isnan(v)])
828
+
829
+ if not len(dval): # len(dval) == 0
830
+ print("Warning: no value to be drawn!")
831
+
832
+ # Replace dval[i] by i in zz and other values by np.nan
833
+ zz2 = np.array(zz) # copy array
834
+ zz[...] = np.nan # initialize
835
+ for i, v in enumerate(dval):
836
+ zz[zz2 == v] = i
837
+
838
+ del zz2
839
+
840
+ # Set 'colorList': the list of colors to use
841
+ colorList = None
842
+ if categCol is not None:
843
+ if len(categCol) >= len(dval):
844
+ colorList = [categCol[i] for i in range(len(dval))]
845
+ # colorList = [mcolors.ColorConverter().to_rgba(categCol[i]) for i in range(len(dval))]
846
+
847
+ elif categColCycle:
848
+ print("Warning: 'categCol' is used cyclically (too few entries)")
849
+ colorList = [categCol[i%len(categCol)] for i in range(len(dval))]
850
+
851
+ else:
852
+ print("Warning: 'categCol' not used (too few entries)")
853
+
854
+ if colorList is None:
855
+ # Use colors from cmap
856
+ colorList = [cmap(x) for x in np.arange(len(dval)) * 1.0/(len(dval)-1)]
857
+
858
+ # Set the colormap: 'cmap'
859
+ if len(dval) == 1:
860
+ cmap = ccol.custom_cmap([colorList[0], colorList[0]], ncol=2,
861
+ cbad=categColbad, alpha=alpha)
862
+
863
+ else: # len(dval) == len(colorList) > 1
864
+ # cmap = mcolors.ListedColormap(colorList)
865
+ cmap = ccol.custom_cmap(colorList, ncol=len(colorList),
866
+ cbad=categColbad, alpha=alpha)
867
+
868
+ # Set the min and max of the colorbar
869
+ vmin, vmax = -0.5, len(dval) - 0.5
870
+
871
+ else: # categ == False
872
+ # --- Treat continuous variable ---
873
+ # Possibly exclude values from zz
874
+ if excludedVal is not None:
875
+ for v in np.array(excludedVal).reshape(-1): # force to be an 1d array
876
+ np.putmask(zz, zz == v, np.nan)
877
+
878
+ if np.all(np.isnan(zz)):
879
+ vmin, vmax= 0.0, 1.0 # any values
880
+ else:
881
+ if vmin is None:
882
+ vmin = np.nanmin(zz)
883
+
884
+ if vmax is None:
885
+ vmax = np.nanmax(zz)
886
+
887
+ col = cmap((zz-vmin)/(vmax-vmin))
888
+
889
+ return col
890
+ # ----------------------------------------------------------------------------
891
+
892
+ # ----------------------------------------------------------------------------
893
+ def drawImage2Drgb(im, nancol=(1.0, 0.0, 0.0)):
894
+ """
895
+ Displays a 2D image with 3 or 4 variables interpreted as RGB or RGBA code.
896
+
897
+ Parameters
898
+ ----------
899
+ im : :class:`geone.img.Img`
900
+ input image, with `im.nv=3` or `im.nv=4` variables interpreted as RGB or
901
+ RBGA code (normalized in [0, 1])
902
+
903
+ nancol : color, default: (1.0, 0.0, 0.0)
904
+ color (3-tuple for RGB code, 4-tuple for RGBA code, str) used for missing
905
+ value (`numpy.nan`) in the input image
906
+ """
907
+ fname = 'drawImage2Drgb'
908
+
909
+ # Check image parameters
910
+ if im.nz != 1:
911
+ print(f"ERROR ({fname}): `im.nz` must be 1")
912
+ return None
913
+
914
+ if im.nv != 3 and im.nv != 4:
915
+ print(f"ERROR ({fname}): `im.nv` must be 3 or 4")
916
+ return None
917
+
918
+ vv = im.val.reshape(im.nv, -1).T
919
+
920
+ if vv.shape[1] == 3:
921
+ nancolf = mcolors.to_rgb(nancol)
922
+ else: # vv.shape[1] == 4
923
+ nancolf = mcolors.to_rgba(nancol)
924
+
925
+ ind_isnan = np.any(np.isnan(vv), axis=1)
926
+ vv[ind_isnan, :] = nancolf
927
+
928
+ plt.imshow(vv.reshape(im.ny, im.nx, -1), origin='lower')
929
+ # ----------------------------------------------------------------------------
930
+
931
+ # ----------------------------------------------------------------------------
932
+ def drawGeobodyMap2D(im, iv=0):
933
+ """
934
+ Displays a geobody 2D map, with adapted color bar.
935
+
936
+ Parameters
937
+ ----------
938
+ im : :class:`geone.img.Img`
939
+ input image, with variable of index `iv` interpreted as geobody labels,
940
+ i.e.:
941
+
942
+ - value 0: cell not in the considered medium,
943
+ - value n > 0: cell in the n-th geobody (connected component)
944
+
945
+ iv : int, default: 0
946
+ index of the variable to be displayed
947
+ """
948
+ categ = True
949
+ ngeo = int(im.val[iv].max())
950
+ categVal = [i for i in range(1, ngeo+1)]
951
+ categCol = None
952
+ if ngeo <= 10:
953
+ categCol = plt.get_cmap('tab10').colors[:ngeo]
954
+ cticks = np.arange(ngeo)
955
+ cticklabels = 1 + cticks
956
+ elif ngeo <= 20:
957
+ categCol = plt.get_cmap('tab20').colors[:ngeo]
958
+ cticks = np.arange(ngeo)
959
+ cticklabels = 1 + cticks
960
+ elif ngeo <= 40:
961
+ categCol = plt.get_cmap('tab20b').colors + plt.get_cmap('tab20c').colors[:ngeo-20]
962
+ cticks = np.arange(0,ngeo,5)
963
+ cticklabels = 1 + cticks
964
+ else:
965
+ categ = False
966
+ cticks = None
967
+ cticklabels = None
968
+ drawImage2D(im, iv=iv, excludedVal=0, categ=categ, categVal=categVal, categCol=categCol,
969
+ cticks=cticks, cticklabels=cticklabels)
970
+ # ----------------------------------------------------------------------------
971
+
972
+ # ----------------------------------------------------------------------------
973
+ def writeImage2Dppm(im, filename,
974
+ ix=None, iy=None, iz=None, iv=0,
975
+ cmap=ccol.cmap_def,
976
+ excludedVal=None,
977
+ categ=False, categVal=None, categCol=None,
978
+ vmin=None, vmax=None):
979
+ """
980
+ Writes an image in a file in ppm format.
981
+
982
+ The colors according the to given settings, as defined in the function
983
+ `drawImage2D` are used.
984
+
985
+ Parameters
986
+ ----------
987
+ im : :class:`geone.img.Img`
988
+ input image
989
+
990
+ filename : str
991
+ name of the file
992
+
993
+ ix : see function :func:`imgplot.drawImage2D`
994
+
995
+ iy : see function :func:`imgplot.drawImage2D`
996
+
997
+ iz : see function :func:`imgplot.drawImage2D`
998
+
999
+ iv : see function :func:`imgplot.drawImage2D`
1000
+
1001
+ cmap : see function :func:`imgplot.drawImage2D`
1002
+
1003
+ excludedVal : see function :func:`imgplot.drawImage2D`
1004
+
1005
+ categ : see function :func:`imgplot.drawImage2D`
1006
+
1007
+ categVal : see function :func:`imgplot.drawImage2D`
1008
+
1009
+ categCol : see function :func:`imgplot.drawImage2D`
1010
+
1011
+ vmin : see function :func:`imgplot.drawImage2D`
1012
+
1013
+ vmax : see function :func:`imgplot.drawImage2D`
1014
+ """
1015
+ fname = 'writeImage2Dppm'
1016
+
1017
+ # Check iv
1018
+ if iv < 0:
1019
+ iv = im.nv + iv
1020
+
1021
+ if iv < 0 or iv >= im.nv:
1022
+ print(f'ERROR ({fname}): invalid `iv` index!')
1023
+ return None
1024
+
1025
+ # Check slice direction and indices
1026
+ n = int(ix is not None) + int(iy is not None) + int(iz is not None)
1027
+
1028
+ if n == 0:
1029
+ sliceDir = 'z'
1030
+ iz = 0
1031
+
1032
+ elif n==1:
1033
+ if ix is not None:
1034
+ if ix < 0:
1035
+ ix = im.nx + ix
1036
+
1037
+ if ix < 0 or ix >= im.nx:
1038
+ print(f'ERROR ({fname}): invalid `ix` index!')
1039
+ return None
1040
+
1041
+ sliceDir = 'x'
1042
+
1043
+ elif iy is not None:
1044
+ if iy < 0:
1045
+ iy = im.ny + iy
1046
+
1047
+ if iy < 0 or iy >= im.ny:
1048
+ print(f'ERROR ({fname}): invalid `iy` index!')
1049
+ return None
1050
+
1051
+ sliceDir = 'y'
1052
+
1053
+ else: # iz is not None
1054
+ if iz < 0:
1055
+ iz = im.nz + iz
1056
+
1057
+ if iz < 0 or iz >= im.nz:
1058
+ print(f'ERROR ({fname}): invalid `iz` index!')
1059
+ return None
1060
+
1061
+ sliceDir = 'z'
1062
+
1063
+ else: # n > 1
1064
+ print(f'ERROR ({fname}): slice specified in more than one direction!')
1065
+ return None
1066
+
1067
+ # Extract what to be plotted
1068
+ if sliceDir == 'x':
1069
+ dim0 = im.ny
1070
+ min0 = im.oy
1071
+ max0 = im.ymax()
1072
+
1073
+ dim1 = im.nz
1074
+ min1 = im.oz
1075
+ max1 = im.zmax()
1076
+ zz = np.array(im.val[iv, :, :, ix].reshape(dim1, dim0)) # np.array() to get a copy
1077
+ # reshape to force 2-dimensional array
1078
+
1079
+ elif sliceDir == 'y':
1080
+ dim0 = im.nx
1081
+ min0 = im.ox
1082
+ max0 = im.xmax()
1083
+
1084
+ dim1 = im.nz
1085
+ min1 = im.oz
1086
+ max1 = im.zmax()
1087
+ zz = np.array(im.val[iv, :, iy, :].reshape(dim1, dim0)) # np.array() to get a copy
1088
+
1089
+ else: # sliceDir == 'z'
1090
+ dim0 = im.nx
1091
+ min0 = im.ox
1092
+ max0 = im.xmax()
1093
+
1094
+ dim1 = im.ny
1095
+ min1 = im.oy
1096
+ max1 = im.ymax()
1097
+ zz = np.array(im.val[iv, iz, :, :].reshape(dim1, dim0)) # np.array() to get a copy
1098
+
1099
+ if categ:
1100
+ # --- Treat categorical variable ---
1101
+ if categCol is not None \
1102
+ and type(categCol) is not list \
1103
+ and type(categCol) is not tuple:
1104
+ print(f"ERROR ({fname}): `categCol` must be a list or a tuple (if not None)!")
1105
+ return None
1106
+
1107
+ # Get array 'dval' of displayed values
1108
+ if categVal is not None:
1109
+ dval = np.array(categVal).reshape(-1) # force to be an 1d array
1110
+
1111
+ if len(np.unique(dval)) != len(dval):
1112
+ print(f"ERROR ({fname}): `categVal` contains duplicated entries!")
1113
+ return None
1114
+
1115
+ # Check 'categCol' (if not None)
1116
+ if categCol is not None and len(categCol) != len(dval):
1117
+ print(f"ERROR ({fname}): length of `categVal` and `categCol` differ!")
1118
+ return None
1119
+
1120
+ else:
1121
+ # Possibly exclude values from zz
1122
+ if excludedVal is not None:
1123
+ for val in np.array(excludedVal).reshape(-1):
1124
+ np.putmask(zz, zz == val, np.nan)
1125
+
1126
+ # Get the unique value in zz
1127
+ dval = np.array([v for v in np.unique(zz).reshape(-1) if ~np.isnan(v)])
1128
+
1129
+ if not len(dval): # len(dval) == 0
1130
+ print(f'ERROR ({fname}): no value to be drawn!')
1131
+
1132
+ # Replace dval[i] by i in zz and other values by np.nan
1133
+ zz2 = np.array(zz) # copy array
1134
+ zz[...] = np.nan # initialize
1135
+ for i, v in enumerate(dval):
1136
+ zz[zz2 == v] = i
1137
+
1138
+ del zz2
1139
+
1140
+ # Set 'colorList': the list of colors to use
1141
+ colorList = None
1142
+ if categCol is not None:
1143
+ if len(categCol) >= len(dval):
1144
+ colorList = [categCol[i] for i in range(len(dval))]
1145
+
1146
+ else:
1147
+ print("Warning: 'categCol' not used (too few entries)")
1148
+
1149
+ if colorList is None:
1150
+ # Use colors from cmap
1151
+ colorList = [cmap(0)]
1152
+ if len(dval) > 1:
1153
+ t = 1./(len(dval)-1)
1154
+ for i in range(1,len(dval)):
1155
+ colorList.append(cmap(i*t))
1156
+
1157
+ # Set the colormap: 'cmap'
1158
+ if len(dval) == 1:
1159
+ cmap = ccol.custom_cmap([colorList[0], colorList[0]], ncol=2,
1160
+ cbad=ccol.cbad_def)
1161
+
1162
+ else: # len(dval) == len(colorList) > 1
1163
+ # cmap = mcolors.ListedColormap(colorList)
1164
+ cmap = ccol.custom_cmap(colorList, ncol=len(colorList),
1165
+ cbad=ccol.cbad_def)
1166
+
1167
+ # Set the min and max of the colorbar
1168
+ vmin, vmax = -0.5, len(dval) - 0.5
1169
+
1170
+ # Set colorbar ticks and ticklabels if not given
1171
+ if cticks is None:
1172
+ cticks = range(len(dval))
1173
+
1174
+ if cticklabels is None:
1175
+ cticklabels = ['{:.3g}'.format(v) for v in dval]
1176
+
1177
+ # Reset cextend if needed
1178
+ colorbar_extend = 'neither'
1179
+
1180
+ else: # categ == False
1181
+ # --- Treat continuous variable ---
1182
+ # Possibly exclude values from zz
1183
+ if excludedVal is not None:
1184
+ for val in np.array(excludedVal).reshape(-1): # force to be an 1d array
1185
+ np.putmask(zz, zz == val, np.nan)
1186
+
1187
+ # Get dimension of zz, flip zz vertically, then reshape as a list of value
1188
+ ny = zz.shape[0]
1189
+ nx = zz.shape[1]
1190
+ zz = zz[list(reversed(range(zz.shape[0]))),:].reshape(-1)
1191
+
1192
+ # Set vmin and vmax (if needed)
1193
+ if vmin is None:
1194
+ vmin = np.nanmin(zz)
1195
+
1196
+ if vmax is None:
1197
+ vmax = np.nanmax(zz)
1198
+
1199
+ # Get indices of bad, under, over value
1200
+ ind_bad = np.isnan(zz)
1201
+ ind_under = np.all((~ind_bad, zz < vmin),0)
1202
+ ind_over = np.all((~ind_bad, zz > vmax),0)
1203
+
1204
+ # Normalize value according to colorbar
1205
+ zz = np.maximum(np.minimum((zz-vmin)/(vmax-vmin), 1.0), 0.0)
1206
+
1207
+ # Get rgba code at each pixel
1208
+ rgba_arr = np.asarray([cmap(v) for v in zz])
1209
+
1210
+ if cmap._rgba_bad is not None:
1211
+ rgba_arr[ind_bad] = cmap._rgba_bad
1212
+
1213
+ if cmap._rgba_under is not None:
1214
+ rgba_arr[ind_under] = cmap._rgba_under
1215
+
1216
+ if cmap._rgba_over is not None:
1217
+ rgba_arr[ind_over] = cmap._rgba_over
1218
+
1219
+ # Convert rgb from 0-1 to integer in 0-255 (ignored a chanel)
1220
+ zz = np.round(rgba_arr[:,0:3]*255)
1221
+
1222
+ # Write file (ppm)
1223
+ shead = ("P3\n"
1224
+ "# CREATED BY PYTHON3-CODE\n"
1225
+ "{0} {1}\n"
1226
+ "255\n").format(nx, ny) # header of ppm file
1227
+ # Open the file in write binary mode
1228
+ with open(filename,'wb') as ff:
1229
+ ff.write(shead.encode()) # write header
1230
+ # Write rgb values
1231
+ np.savetxt(ff, zz.reshape(-1), fmt='%d')
1232
+ # ----------------------------------------------------------------------------
1233
+
1234
+ if __name__ == "__main__":
1235
+ print("Module 'geone.imgplot' example:")
1236
+ import matplotlib.pyplot as plt
1237
+
1238
+ # Set image with 50 variables
1239
+ # ---------------------------
1240
+ # Set domain and number of cell in each direction
1241
+ xmin, xmax = -2, 2
1242
+ ymin, ymax = -1, 2
1243
+ nx, ny = 200, 150
1244
+
1245
+ # Set the cell size
1246
+ sx, sy = (xmax-xmin)/nx, (ymax-ymin)/ny
1247
+
1248
+ # Set the meshgrid
1249
+ x, y = xmin + 0.5 * sx + sx * np.arange(nx), ymin + 0.5 * sy + sy * np.arange(ny)
1250
+ # # equivalently:
1251
+ # x, y = np.arange(xmin+sx/2, xmax, sx), np.arange(ymin+sy/2, ymax ,sy)
1252
+ # x, y = np.linspace(xmin+sx/2, xmax-sx/2, nx), np.linspace(ymin+sy/2, ymax-sy/2, ny)
1253
+ xx,yy = np.meshgrid(x, y)
1254
+
1255
+ # function values
1256
+ zz = xx**2 + yy**2 - 2
1257
+
1258
+ # Set some values to nan
1259
+ zz[np.where(zz < -1.7)] = np.nan
1260
+
1261
+ # set image, where each variable consists in
1262
+ # the function values 'zz' + a gaussian noise
1263
+ nv = 50
1264
+ im = Img(nx=nx, ny=ny, nz=1, nv=nv,
1265
+ sx=sx, sy=sy, sz=1.0,
1266
+ ox=xmin, oy=ymin, oz=0.0)
1267
+
1268
+ for i in range(nv):
1269
+ im.set_var(ind=i, val=zz.reshape(-1)+np.random.normal(size=im.nxy()))
1270
+
1271
+ # Compute the mean and standard deviation
1272
+ # ---------------------------------------
1273
+ imMean = img.imageContStat(im,op='mean')
1274
+ imStd = img.imageContStat(im,op='std',ddof=1)
1275
+
1276
+ # Draw images
1277
+ # -----------
1278
+ # Set min and max value to be displayed
1279
+ vmin, vmax = -1.0, 3.0
1280
+
1281
+ fig, ax = plt.subplots(2,2,figsize=(12,10))
1282
+ plt.subplot(2,2,1)
1283
+ drawImage2D(im, iv=0, vmin=vmin, vmax=vmax, title='1-st real',
1284
+ colorbar_extend='both')
1285
+
1286
+ plt.subplot(2,2,2)
1287
+ drawImage2D(im, iv=1, vmin=vmin, vmax=vmax, title='2-nd real',
1288
+ colorbar_extend='both')
1289
+
1290
+ plt.subplot(2,2,3)
1291
+ drawImage2D(imMean, vmin=vmin, vmax=vmax,
1292
+ title='Mean over {} real'.format(nv),
1293
+ colorbar_extend='both')
1294
+
1295
+ plt.subplot(2,2,4)
1296
+ drawImage2D(imStd, title='Std over {} real'.format(nv))
1297
+
1298
+ # plt.tight_layout()
1299
+
1300
+ # fig.show()
1301
+ plt.show()
1302
+
1303
+ # Copy im and categorize
1304
+ # ----------------------
1305
+ imCat = img.copyImg(im)
1306
+ bins = np.array([-np.inf, -1., 0., 1., np.inf])
1307
+ # set category j to a value v of image im as follows:
1308
+ # bins[j-1] <= v < bins[j], for j=1,...,bins.size-1
1309
+ # and j=np.nan if v is np.nan
1310
+
1311
+ defInd = np.array(~np.isnan(im.val)).reshape(-1) # defined indices
1312
+
1313
+ imCat.val[...] = np.nan # initialization
1314
+
1315
+ for j in range(1, bins.size):
1316
+ # Set category j
1317
+ imCat.val[np.all((defInd,
1318
+ np.asarray(im.val >= bins[j-1]).reshape(-1),
1319
+ np.asarray(im.val < bins[j]).reshape(-1)),
1320
+ 0).reshape(imCat.val.shape)] = j
1321
+
1322
+ categ = list(range(1, bins.size))
1323
+ imCatProp = img.imageCategProp(imCat,categ)
1324
+
1325
+ # Draw images
1326
+ # -----------
1327
+ # Set background color to "white"
1328
+ mpl_rcParams['figure.facecolor'] = 'white'
1329
+
1330
+ fig, ax = plt.subplots(2,3,figsize=(18,10))
1331
+ plt.subplot(2,3,1)
1332
+ drawImage2D(imCat, iv=0, categ=True, title='1-st real')
1333
+
1334
+ plt.subplot(2,3,2)
1335
+ # drawImage2D(imCat, iv=1, categ=True, title='2-nd real')
1336
+ drawImage2D(imCat, iv=1, categ=True, title='2-nd real',
1337
+ title_fontsize=18, title_fontweight='bold',
1338
+ xlabel="x-axis", xlabel_fontsize=8,
1339
+ xticks=[-2,0,2], xticklabels_fontsize=8,
1340
+ clabel="facies",clabel_fontsize=16,clabel_rotation=90,
1341
+ cticklabels=['A','B','C','D'],cticklabels_fontsize=8)
1342
+
1343
+ plt.subplot(2,3,3)
1344
+ drawImage2D(imCatProp, iv=0, vmin=0, vmax=0.7, colorbar_extend='max',
1345
+ title='Prop. of "{}" over {} real'.format(categ[0], nv))
1346
+
1347
+ plt.subplot(2,3,4)
1348
+ drawImage2D(imCatProp, iv=1, vmin=0, vmax=0.7, colorbar_extend='max',
1349
+ title='Prop. of "{}" over {} real'.format(categ[1], nv))
1350
+
1351
+ plt.subplot(2,3,5)
1352
+ drawImage2D(imCatProp, iv=2, vmin=0, vmax=0.7, colorbar_extend='max',
1353
+ title='Prop. of "{}" over {} real'.format(categ[2], nv))
1354
+
1355
+ plt.subplot(2,3,6)
1356
+ drawImage2D(imCatProp, iv=3, vmin=0, vmax=0.7, colorbar_extend='max',
1357
+ title='Prop. of "{}" over {} real'.format(categ[3], nv),
1358
+ cticks=np.arange(0,.8,.1), cticklabels=['{:4.2f}'.format(i) for i in np.arange(0,.8,.1)],
1359
+ cticklabels_fontweight='bold')
1360
+
1361
+ plt.suptitle('Categorized images...')
1362
+ # plt.tight_layout()
1363
+
1364
+ # fig.show()
1365
+ plt.show()
1366
+
1367
+ a = input("Press enter to continue...")