kplot 1.0.6__tar.gz → 1.1.0__tar.gz

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.
Files changed (63) hide show
  1. {kplot-1.0.6 → kplot-1.1.0}/PKG-INFO +2 -2
  2. {kplot-1.0.6 → kplot-1.1.0}/pyproject.toml +2 -2
  3. kplot-1.1.0/src/kplot/axes.py +63 -0
  4. kplot-1.1.0/src/kplot/cmaps.py +98 -0
  5. {kplot-1.0.6 → kplot-1.1.0}/src/kplot/hist.py +3 -7
  6. kplot-1.1.0/src/kplot/image.py +375 -0
  7. kplot-1.1.0/src/kplot/movie.py +173 -0
  8. {kplot-1.0.6 → kplot-1.1.0}/src/kplot/plot.py +28 -16
  9. {kplot-1.0.6 → kplot-1.1.0}/src/kplot/threeD/slider_images.py +20 -2
  10. {kplot-1.0.6 → kplot-1.1.0}/src/kplot/utils.py +20 -4
  11. kplot-1.0.6/src/kplot/.git/COMMIT_EDITMSG +0 -1
  12. kplot-1.0.6/src/kplot/.git/HEAD +0 -1
  13. kplot-1.0.6/src/kplot/.git/config +0 -13
  14. kplot-1.0.6/src/kplot/.git/description +0 -1
  15. kplot-1.0.6/src/kplot/.git/hooks/applypatch-msg.sample +0 -15
  16. kplot-1.0.6/src/kplot/.git/hooks/commit-msg.sample +0 -24
  17. kplot-1.0.6/src/kplot/.git/hooks/fsmonitor-watchman.sample +0 -174
  18. kplot-1.0.6/src/kplot/.git/hooks/post-update.sample +0 -8
  19. kplot-1.0.6/src/kplot/.git/hooks/pre-applypatch.sample +0 -14
  20. kplot-1.0.6/src/kplot/.git/hooks/pre-commit.sample +0 -49
  21. kplot-1.0.6/src/kplot/.git/hooks/pre-merge-commit.sample +0 -13
  22. kplot-1.0.6/src/kplot/.git/hooks/pre-push.sample +0 -53
  23. kplot-1.0.6/src/kplot/.git/hooks/pre-rebase.sample +0 -169
  24. kplot-1.0.6/src/kplot/.git/hooks/pre-receive.sample +0 -24
  25. kplot-1.0.6/src/kplot/.git/hooks/prepare-commit-msg.sample +0 -42
  26. kplot-1.0.6/src/kplot/.git/hooks/push-to-checkout.sample +0 -78
  27. kplot-1.0.6/src/kplot/.git/hooks/sendemail-validate.sample +0 -77
  28. kplot-1.0.6/src/kplot/.git/hooks/update.sample +0 -128
  29. kplot-1.0.6/src/kplot/.git/index +0 -0
  30. kplot-1.0.6/src/kplot/.git/info/exclude +0 -6
  31. kplot-1.0.6/src/kplot/.git/logs/HEAD +0 -1
  32. kplot-1.0.6/src/kplot/.git/logs/refs/heads/master +0 -1
  33. kplot-1.0.6/src/kplot/.git/logs/refs/remotes/origin/master +0 -1
  34. kplot-1.0.6/src/kplot/.git/objects/07/58a12cf558f1c1052d5d85fc11183eaa8c5a0e +0 -1
  35. kplot-1.0.6/src/kplot/.git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 +0 -0
  36. kplot-1.0.6/src/kplot/.git/objects/4b/ec2657a7dbd45dbe73e37138d116f523faa959 +0 -0
  37. kplot-1.0.6/src/kplot/.git/objects/4f/5409d618f7e38d537b978d5f41a3631b32b711 +0 -0
  38. kplot-1.0.6/src/kplot/.git/objects/51/3c1f584825f2ef997c0b00889ad1e6bb8bc8b1 +0 -0
  39. kplot-1.0.6/src/kplot/.git/objects/53/95a83111291f805f440b8fed0dad2591e90c4b +0 -2
  40. kplot-1.0.6/src/kplot/.git/objects/66/594a4f54fe76d07b6cc028ea590f8cfa3d43f6 +0 -0
  41. kplot-1.0.6/src/kplot/.git/objects/73/77c78423a1b6d14a6d87a8748bf57f229ce10f +0 -0
  42. kplot-1.0.6/src/kplot/.git/objects/7e/836e7020b269a8d4f8fd1add726cf6c32ffa61 +0 -0
  43. kplot-1.0.6/src/kplot/.git/objects/b0/df8909fba782cf7f1bda3436475eca0f3246d4 +0 -0
  44. kplot-1.0.6/src/kplot/.git/objects/b2/98659e19d2219f0191793d2d22f41e58f70a32 +0 -0
  45. kplot-1.0.6/src/kplot/.git/objects/d5/8e4034057f2585ea9dd383bfca171cbe9d6842 +0 -0
  46. kplot-1.0.6/src/kplot/.git/objects/dd/527a8b9e974453467c66d8f54053eb0f220435 +0 -0
  47. kplot-1.0.6/src/kplot/.git/objects/dd/ab85719ab389187ded217518e30e0f1b6d6bf9 +0 -0
  48. kplot-1.0.6/src/kplot/.git/objects/e2/c2413a8beac9a9fb63279a8c01bcc0d63db27a +0 -0
  49. kplot-1.0.6/src/kplot/.git/objects/e4/6a79e332a3be7fe4514bab28f14acae9f4f3bd +0 -0
  50. kplot-1.0.6/src/kplot/.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 +0 -0
  51. kplot-1.0.6/src/kplot/.git/objects/fe/004a1dce30bd84414e6ce55f7ff17dd098e37c +0 -0
  52. kplot-1.0.6/src/kplot/.git/refs/heads/master +0 -1
  53. kplot-1.0.6/src/kplot/.git/refs/remotes/origin/master +0 -1
  54. kplot-1.0.6/src/kplot/axes.py +0 -45
  55. kplot-1.0.6/src/kplot/cmaps.py +0 -10
  56. kplot-1.0.6/src/kplot/image.py +0 -214
  57. kplot-1.0.6/src/kplot/movie.py +0 -41
  58. kplot-1.0.6/src/kplot/py.typed +0 -0
  59. {kplot-1.0.6 → kplot-1.1.0}/README.md +0 -0
  60. {kplot-1.0.6 → kplot-1.1.0}/src/kplot/__init__.py +0 -0
  61. {kplot-1.0.6 → kplot-1.1.0}/src/kplot/styles/example.mpl +0 -0
  62. {kplot-1.0.6 → kplot-1.1.0}/src/kplot/styles/publication.mpl +0 -0
  63. {kplot-1.0.6 → kplot-1.1.0}/src/kplot/threeD/__init__.py +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: kplot
3
- Version: 1.0.6
4
- Summary: Add your description here
3
+ Version: 1.1.0
4
+ Summary: a matplotlib wrapper for keyan :-)
5
5
  Requires-Dist: matplotlib>=3.10.8
6
6
  Requires-Dist: numpy>=2.4.2
7
7
  Requires-Python: >=3.11
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "kplot"
3
- version = "1.0.6"
4
- description = "Add your description here"
3
+ version = "1.1.0"
4
+ description = "a matplotlib wrapper for keyan :-)"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
7
7
  dependencies = [
@@ -0,0 +1,63 @@
1
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
2
+ # >-|===|> Imports <|===|-<
3
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
4
+ from kplot.utils import column_width, two_column_width
5
+
6
+ from matplotlib.pyplot import subplots as matplotlib_subplots
7
+ from matplotlib.pyplot import subplot_mosaic
8
+ from matplotlib.figure import Figure
9
+ from matplotlib.axes import Axes
10
+ import numpy as np
11
+
12
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
13
+ # >-|===|> Types <|===|-<
14
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
15
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
16
+ # >-|===|> Definitions <|===|-<
17
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
18
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
19
+ # >-|===|> Functions <|===|-<
20
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
21
+ def decode_subplots_args(*args, **kwargs):
22
+ if 'figsize' in kwargs: kwargs['figsize'] = tuple(
23
+ column_width if kwargs['figsize'][i]==1 else two_column_width if kwargs['figsize'][i]==2 else kwargs['figsize'][i]
24
+ for i in range(2))
25
+ match args:
26
+ case (int(), int())|((int(), int())):
27
+ return matplotlib_subplots(*args, **kwargs)
28
+ case (str(),):
29
+ return subplot_mosaic(*args, **kwargs)
30
+ case ():
31
+ return matplotlib_subplots(**kwargs)
32
+ def subplots(*args, **kwargs): return decode_subplots_args(*args, **kwargs)
33
+ def access_subplots(
34
+ fig: None|Figure = None,
35
+ axes: None|Axes|list = None,
36
+ figsize: None|tuple = None
37
+ ) -> tuple[Figure, list]:
38
+ match fig, axes:
39
+ # if given nothing
40
+ case None, None:
41
+ fig, axes = subplots(figsize=figsize)
42
+ axes = [axes]
43
+ case Figure(), None:
44
+ axes = fig.get_axes()
45
+ axes = axes if len(axes) > 0 else [fig.add_subplot(111)]
46
+ case None, Axes():
47
+ fig = axes.get_figure()
48
+ axes = [axes]
49
+ case None, list()|np.ndarray():
50
+ axes = axes.flatten()
51
+ fig = axes[0].get_figure()
52
+ case Figure(), Axes():
53
+ axes = [axes]
54
+ case Figure(), list()|np.ndarray():
55
+ return fig, axes
56
+ return fig, axes
57
+
58
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
59
+ # >-|===|> Decorators <|===|-<
60
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
61
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
62
+ # >-|===|> Classes <|===|-<
63
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
@@ -0,0 +1,98 @@
1
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
2
+ # >-|===|> Imports <|===|-<
3
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
4
+ from kbasic.array import tile
5
+ from functools import wraps
6
+ from glob import glob
7
+ import matplotlib.pyplot as plt
8
+ from matplotlib.pyplot import cm
9
+ from matplotlib.colors import Colormap, LinearSegmentedColormap, ListedColormap, hex2color, Normalize, LogNorm, FuncNorm, AsinhNorm, PowerNorm, SymLogNorm, BoundaryNorm, CenteredNorm, TwoSlopeNorm
10
+ from mpl_toolkits.axes_grid1 import make_axes_locatable
11
+ import numpy as np
12
+
13
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
14
+ # >-|===|> Types <|===|-<
15
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
16
+ class Norm:
17
+ types: list = [Normalize, LogNorm, FuncNorm, AsinhNorm, PowerNorm, SymLogNorm, BoundaryNorm, CenteredNorm, TwoSlopeNorm]
18
+ class Cmap:
19
+ types: list = [Colormap, ListedColormap, LinearSegmentedColormap]
20
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
21
+ # >-|===|> Definitions <|===|-<
22
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
23
+ pink = "#E34F68"
24
+ lightpink = "#E39FAA"
25
+ blue = "#7350E6"
26
+ lightblue = "#AE9FE3"
27
+ shadow = "#B8B7B8"
28
+ manoaskies = LinearSegmentedColormap.from_list("manoaskies", [pink, blue])
29
+ manoaskies_centered = LinearSegmentedColormap.from_list("manoaskies_centered", [lightpink, pink, "#000000", blue, lightblue])
30
+ manoaskies_background_blue = "#0C0524"
31
+ pink2grey = LinearSegmentedColormap.from_list("p2g", [pink, shadow])
32
+ grey2black = LinearSegmentedColormap.from_list("g2b", [shadow, "#000000"])
33
+ colors_list = np.zeros((256, 4))
34
+ colors_list[:128] = list(hex2color(manoaskies_background_blue))+[1]
35
+ for i in range(28): colors_list[128+i] = pink2grey(i/28)
36
+ for i in range(100): colors_list[156+i] = grey2black(i/150)
37
+ manoaskies_beauty = ListedColormap(colors_list)
38
+ default_cmap = cm.plasma
39
+
40
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
41
+ # >-|===|> Functions <|===|-<
42
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
43
+ # # Ploting utils
44
+ def auto_norm(
45
+ norm: str,
46
+ frames: np.ndarray,
47
+ linear_threshold: float|None = None,
48
+ center: float|None = None,
49
+ saturate: float|None = None
50
+ ) -> Norm:
51
+ """A function to create a matplotlib normalization given a set images.
52
+
53
+ Args:
54
+ norm (str): what type of scale to use, e.g. lognorm or centerednorm
55
+ frames (np.ndarray): the images to base the normalization on
56
+ linear_threshold (float | None, optional): for symlognorm. Defaults to None.
57
+ center (float | None, optional): for centered normalizations. Defaults to None.
58
+ saturate (float | None, optional): the level at which to saturate the norm, e.g. if saturate=0.01 then the
59
+ max is the 99th percentile. Defaults to None.
60
+
61
+ Returns:
62
+ matplotlib normalization
63
+ """
64
+ frames = frames[(-np.inf < frames)&(frames < np.inf)]
65
+ # set min/max IF saturate is None or IF saturate is a tuple ELSE assume its a float
66
+ low = np.nanmin(frames) if saturate is None else np.nanquantile(frames, 1-saturate[0]) if isinstance(saturate, tuple) else np.nanquantile(frames, 1-saturate)
67
+ high = np.nanmax(frames) if saturate is None else np.nanquantile(frames, 0+saturate[1]) if isinstance(saturate, tuple) else np.nanquantile(frames, 0+saturate)
68
+ match norm.lower():
69
+ case "lognorm"|"log":
70
+ if low < 0: raise ValueError(f"minimum is {low}, LogNorm only takes positive values")
71
+ if low==0: low=np.nanmin(frames[frames!=0])
72
+ return LogNorm(vmin=low, vmax=high)
73
+ case "symlognorm"|"symlog"|"sym":
74
+ sig = np.nanstd(frames)
75
+ mu = np.nanmean(frames)
76
+ if np.abs(mu)-sig > 0: raise TypeError("SymLogNorm is only designed for stuff close to zero!")
77
+ return SymLogNorm(sig if linear_threshold is None else linear_threshold, vmin=low, vmax=high)
78
+ case n if n in ["centerednorm", "twoslope", "twoslopenorm"]:
79
+ sig = np.nanstd(frames)
80
+ mu = np.nanmean(frames)
81
+ # for the center use center if give otherwise use 0 if mean is small, else use mean
82
+ vcenter = center if not center is None else 0 if np.abs(mu)-sig > 0 else mu
83
+ return TwoSlopeNorm(vmin=low, vcenter=vcenter, vmax=high)
84
+ case _: return Normalize(vmin=low, vmax=high)
85
+ def align_algorithm(x: list|np.ndarray, mode: str):
86
+ match mode:
87
+ case 'mid'|'m'|'center'|'c':
88
+ return [(x[i] + x[i+1])/2 for i in range(len(x)-1)]
89
+ case 'logmid'|'lm':
90
+ return [np.log10((10**x[i] + 10**x[i+1])/ 2) for i in range(len(x)-1)]
91
+ case 'left'|'l':
92
+ return x[:-1]
93
+ case 'right'|'r':
94
+ return x[1:]
95
+
96
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
97
+ # >-|===|> Decorators <|===|-<
98
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
@@ -5,11 +5,9 @@
5
5
  from kplot.utils import alias_kwarg, column_width
6
6
  from kplot.axes import access_subplots
7
7
  from kplot.utils import alias_kwarg, column_width, parse_multiax_params
8
-
8
+ from kplot.cmaps import Cmap
9
9
  import numpy as np
10
10
  import matplotlib.pyplot as plt
11
- from matplotlib.colors import ListedColormap, LinearSegmentedColormap, Colormap
12
- CmapTypes = [ListedColormap, LinearSegmentedColormap, Colormap]
13
11
 
14
12
  # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
15
13
  # >-|===|> Definitions <|===|-<
@@ -21,8 +19,6 @@ CmapTypes = [ListedColormap, LinearSegmentedColormap, Colormap]
21
19
  def decode_hist_src(src) -> tuple:
22
20
  match src:
23
21
  case np.ndarray()|list(): return src
24
-
25
-
26
22
  def hist(
27
23
  *src,
28
24
  #figure setup
@@ -32,8 +28,8 @@ def hist(
32
28
  show: bool = False,
33
29
  close: bool = False,
34
30
  #line formating
35
- color: str|Colormap|list[int] = "black",
36
- cmap: str|Colormap = plt.cm.plasma,
31
+ color: str|Cmap|list[int] = "black",
32
+ cmap: str|Cmap = plt.cm.plasma,
37
33
  linewidth: int = None, lw: int = None,
38
34
  linestyle: str = None, ls: str = None,
39
35
  #plot formating
@@ -0,0 +1,375 @@
1
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
2
+ # >-|===|> Imports <|===|-<
3
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
4
+ from kplot.axes import access_subplots
5
+ from kplot.utils import alias_kwarg, parse_multiax_params, column_width, two_column_width
6
+ from kplot.cmaps import Norm, Cmap
7
+ import numpy as np
8
+ import matplotlib.pyplot as plt
9
+ from mpl_toolkits.axes_grid1 import make_axes_locatable
10
+ from matplotlib.colors import ListedColormap, LinearSegmentedColormap, Colormap
11
+
12
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
13
+ # >-|===|> Types <|===|-<
14
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
15
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
16
+ # >-|===|> Definitions <|===|-<
17
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
18
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
19
+ # >-|===|> Functions <|===|-<
20
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
21
+ def decode_image_src(src) -> np.ndarray:
22
+ match src:
23
+ case str():
24
+ return np.loadtxt(src)
25
+ case np.ndarray():
26
+ return src
27
+ case list():
28
+ return np.array(src)
29
+ def _show_one_frame(plot_dict, axes_dict, cbar_dict, **kwargs):
30
+ # populate name space
31
+ x, y, image = plot_dict['x'], plot_dict['y'], plot_dict['image']
32
+ fig, ax, cmap, norm = plot_dict['fig'], plot_dict['axes'][0], plot_dict['cmap'], plot_dict['norm']
33
+ # plot
34
+ img = ax.pcolormesh(x, y, image, cmap=cmap, norm=norm, **kwargs)
35
+ # label axes
36
+ ax.set_xlabel(axes_dict['xlabel'])
37
+ ax.set_ylabel(axes_dict['ylabel'])
38
+ # set limits
39
+ ax.set_xlim(axes_dict['xlim'])
40
+ ax.set_ylim(axes_dict['ylim'])
41
+ # set title
42
+ ax.set_title(axes_dict['title'])
43
+ # set aspect
44
+ ax.set_aspect(axes_dict['aspect'])
45
+ if cbar_dict['colorbar']:
46
+ divider = make_axes_locatable(ax)
47
+ cax = divider.append_axes(
48
+ cbar_dict['location'],
49
+ size=cbar_dict['size'],
50
+ pad=cbar_dict['pad']
51
+ )
52
+ fig.colorbar(
53
+ img,
54
+ cax=cax, ax=ax,
55
+ ticks=cbar_dict['cticks'],
56
+ label=cbar_dict['units']
57
+ )
58
+ return [img]
59
+ def construct_mass_norm(images: np.ndarray, norm):
60
+ match norm:
61
+ # linear norms
62
+ case Normalize():
63
+ vmin = np.nanmin(images) if norm.vmin is None else norm.vmin
64
+ vmax = np.nanmax(images) if norm.vmax is None else norm.vmax
65
+ return Normalize(vmin=vmin, vmax=vmax, clip=norm.clip)
66
+
67
+ case CenteredNorm():
68
+ vmin = np.nanmin(images) if norm.vmin is None else norm.vmin
69
+ vmax = np.nanmax(images) if norm.vmax is None else norm.vmax
70
+ # only center at 0 if the mean is close to 0 to avoid weird balance
71
+ vcen = norm.vcenter if norm.vcenter else 0 if np.nanmean(images) - np.nanstd(images)/2 else np.nanmean(images)
72
+ hrng = norm.halfrange if norm.halfrange else vmax/2 if vcen==0 else abs(vmax-vmin)/2
73
+ return CenteredNorm(vcenter=vcen, halfrange=hrng, clip=norm.clip)
74
+
75
+ case TwoSlopeNorm():
76
+ vmin = np.nanmin(images) if norm.vmin is None else norm.vmin
77
+ vmax = np.nanmax(images) if norm.vmax is None else norm.vmax
78
+ vcen = norm.vcenter if norm.vcenter else 0 if np.nanmean(images) - np.nanstd(images)/2 else np.nanmean(images)
79
+ return TwoSlopeNorm(vcenter=vcen, vmin=vmin, vmax=vmax)
80
+
81
+ case BoundaryNorm():
82
+ return BoundaryNorm(norm.boundaries, norm.ncolors, clip=norm.clip, extend=norm.extend)
83
+
84
+ # log-like norms
85
+ case LogNorm():
86
+ vmin = np.nanmin(images) if norm.vmin is None else norm.vmin
87
+ vmax = np.nanmax(images) if norm.vmax is None else norm.vmax
88
+ return LogNorm(vmin=vmin, vmax=vmax, clip=norm.clip)
89
+
90
+ case SymLogNorm():
91
+ vmin = np.nanmin(images) if norm.vmin is None else norm.vmin
92
+ vmax = np.nanmax(images) if norm.vmax is None else norm.vmax
93
+ #check that this isn't trivially the same as LogNorm
94
+ if vmin > 0: return LogNorm(vmin=vmin, vmax=vmax, clip=norm.clip)
95
+ linthresh = np.nanquantile(images, .1) if norm.linthresh is None else norm.linthresh
96
+ return SymLogNorm(linthresh, linscale=norm.linscale, vmin=vmin, vmax=vmax, clip=norm.clip)
97
+
98
+ case AsinhNorm():
99
+ vmin = np.nanmin(images) if norm.vmin is None else norm.vmin
100
+ vmax = np.nanmax(images) if norm.vmax is None else norm.vmax
101
+ linwidth = np.nanquantile(images, .1) if norm.linear_width is None else norm.linear_width
102
+ return AsinhNorm(linear_width=linwidth, vmin=vmin, vmax=vmax, clip=norm.clip)
103
+
104
+ case PowerNorm():
105
+ vmin = np.nanmin(images) if norm.vmin is None else norm.vmin
106
+ vmax = np.nanmax(images) if norm.vmax is None else norm.vmax
107
+ return PowerNorm(norm.gamma, vmin=vmin, vmax=vmax, clip=norm.clip)
108
+
109
+ case FuncNorm():
110
+ vmin = np.nanmin(images) if norm.vmin is None else norm.vmin
111
+ vmax = np.nanmax(images) if norm.vmax is None else norm.vmax
112
+ return FuncNorm(norm.functions, vmin=vmin, vmax=vmax, clip=norm.clip)
113
+
114
+ pass
115
+ def _show_multiple_frames(plot_dict: dict, axes_dict: dict, cbar_dict: dict, N: int, **kwargs):
116
+ fig, axes, images = plot_dict['fig'], plot_dict['axes'], plot_dict['image'] # these are set as single and multiple before arriving to this function
117
+ xs = parse_multiax_params(plot_dict['x'], [np.ndarray], N)
118
+ ys = parse_multiax_params(plot_dict['y'], [np.ndarray], N)
119
+ cmaps = parse_multiax_params(plot_dict['cmap'], Cmap.types, N)
120
+ colorbars = 'single' if cbar_dict['colorbar']=='single' else parse_multiax_params(cbar_dict['colorbar'], [bool], N)
121
+ norms = 'single' if colorbars=='single' else parse_multiax_params(plot_dict['norm'], Norm.types, N)
122
+ cbar_locations = parse_multiax_params(cbar_dict['location'], [str], N)
123
+ cbar_sizes = parse_multiax_params(cbar_dict['size'], [str], N)
124
+ cbar_pads = parse_multiax_params(cbar_dict['pad'], [float], N)
125
+ cticks = parse_multiax_params(cbar_dict['cticks'], [list], N)
126
+ units = parse_multiax_params(cbar_dict['units'], [str], N)
127
+ xlims = parse_multiax_params(axes_dict['xlim'], [tuple], N)
128
+ ylims = parse_multiax_params(axes_dict['ylim'], [tuple], N)
129
+ xlabels = 'single' if type(axes_dict['xlabel'])==str else parse_multiax_params(axes_dict['xlabel'], [str], N)
130
+ ylabels = 'single' if type(axes_dict['ylabel'])==str else parse_multiax_params(axes_dict['ylabel'], [str], N)
131
+ titles = 'single' if type(axes_dict['title'])==str else parse_multiax_params(axes_dict['title'], [str], N)
132
+ aspects = parse_multiax_params(axes_dict['aspect'], [str], N)
133
+ imgs = []
134
+ for i in range(N):
135
+ ax, image = axes[i], images[i]
136
+ imgs.append(ax.pcolormesh(xs[i], ys[i], image, cmap=cmaps[i], norm=norms[i], **kwargs))
137
+ return imgs
138
+ def show(
139
+ field: np.ndarray,
140
+ tile_image: bool = False,
141
+ #x/y axes
142
+ x: np.ndarray|None = None,
143
+ y: np.ndarray|None = None,
144
+ #figure setup
145
+ fig = None,
146
+ ax = None,
147
+ axis: bool = True,
148
+ figsize: tuple[float, float] = (10, 10),
149
+ show: bool = False,
150
+ #plot parameters
151
+ cmap = default_cmap,
152
+ colorbar: bool = True,
153
+ colorbar_style: dict = {'location':'right', "size":"7%", "pad":0.05},
154
+ cticks: list|None = None,
155
+ units: str|None = None,
156
+ #contour options
157
+ contour: np.ndarray|None = None,
158
+ contour_style: dict = {'levels': 10, 'colors': 'black'},
159
+ #plot formating
160
+ xlim: tuple = (None, None),
161
+ ylim: tuple = (None, None),
162
+ title: str|None = None,
163
+ #presentation parameters
164
+ save: str = "",
165
+ dpi: int = 100,
166
+ #everything else goes into pcolormesh
167
+ **kwargs
168
+ ):
169
+ """a convinient way to plt.imshow and arrange it nicely
170
+
171
+ Args:
172
+ field (np.ndarray): The image to be plotted
173
+ tile_image (bool, optional): whether to tile the image. Defaults to False.
174
+ x (np.ndarray | None, optional): x coordinates of pixels. Defaults to None.
175
+ y (np.ndarray | None, optional): y coordinates of pixels. Defaults to None.
176
+ fig (plt.Figure, optional): the figure on which to plot. Defaults to None.
177
+ ax (plt.Axes, optional): the ax on which to plot this image. Defaults to None.
178
+ axis (bool, optional): whether to include the whole axis, if False then only the plot area will be shown.
179
+ figsize (tuple[float, float], optional): unless fig and ax are given plot on a figure of this size. Defaults to (10, 10).
180
+ show (bool, optional): whether to use the plt.show() command at the end. Defaults to True.
181
+ cmap (plt.ColorMap, optional): the colormap to use for the image. Defaults to plasma.
182
+ colorbar (bool, optional): whether to add a colorbar. Defaults to True.
183
+ colorbar_style (dict, optional): dictionary containing kwargs for the colorbar command. Defaults to {'location':'right', "size":"7%", "pad":0.05}.
184
+ cticks (list | None, optional): the ticks to use on the colorbar. Defaults to None.
185
+ units (str | None, optional): label for the colorbar. Defaults to None.
186
+ contour (np.ndarray | None, optional): whether to put a contour plot on top. Defaults to None.
187
+ contour_style (dict, optional): kwargs for contour plotting command. Defaults to {'levels': 10, 'colors': 'black'}.
188
+ xlim (tuple, optional): the limits on the x axis. Defaults to (None, None).
189
+ ylim (tuple, optional): the limits on the y axis. Defaults to (None, None).
190
+ title (str | None, optional): the plot title. Defaults to None.
191
+ save (str, optional): if given save the plot to this path. Defaults to "".
192
+ dpi (int, optional): dots per inch to save at. Defaults to 100.
193
+
194
+ Returns:
195
+ fig (plt.Figure): The figure on which the plot was put
196
+ ax (plt.Axes): The ax on which the plot was put
197
+ img (plt.Artist): the plot artist
198
+ """
199
+ # prep data
200
+ assert (ndims:=len(field.shape))==2, f"show was given an image with {ndims} dimensions, please provide a 2d array"
201
+ image = tile(field) if tile_image else field
202
+ # check axes
203
+ if x is None: x, y = np.mgrid[:image.shape[0], :image.shape[1]]
204
+ if tile_image: x, y = np.r_[x, x+x[-1], x+2*x[-1]], np.r_[y, y+y[-1], y+2*y[-1]]
205
+ else: assert (len(x), len(y)) == image.shape, f"Given x of shape {len(x)} and y of shape {len(y)} but image is of shape {image.shape}"
206
+ # prep figure
207
+ if ax is None: (fig, ax) = plt.subplots(figsize=figsize)
208
+ fig = ax.get_figure()
209
+ if not axis:
210
+ ax.axis('off')
211
+ ax.set_position([0, 0, 1, 1])
212
+ colorbar = False
213
+ # plot data
214
+ img = ax.pcolormesh(x, y, image, cmap=cmap, **kwargs)
215
+ # colorbar
216
+ if colorbar:
217
+ divider = make_axes_locatable(ax)
218
+ colorbar_location = colorbar_style.pop("location") if "location" in colorbar_style.keys() else "right"
219
+ cax = divider.append_axes(colorbar_location, **colorbar_style)
220
+ fig.colorbar(img, cax=cax, ax=ax, ticks=cticks, label=units, orientation = 'vertical' if colorbar_location in ('right', 'left') else 'horizontal')
221
+ if colorbar_location=='top':
222
+ cax.xaxis.set_ticks_position('top')
223
+ cax.xaxis.set_label_position('top')
224
+ # contour
225
+ if not contour is None: ax.contour(contour, **contour_style)
226
+ # set limits
227
+ ax.set_xlim(xlim)
228
+ ax.set_ylim(ylim)
229
+ # set title
230
+ ax.set_title(title)
231
+ # set aspect
232
+ ax.set_aspect('equal')
233
+ # present the figure
234
+ if len(save)>0:
235
+ if not '.' in save: save +=".jpeg"
236
+ plt.savefig(save, dpi=dpi, bbox_inches='tight')
237
+ if show: plt.show()
238
+ return fig, ax, img
239
+ def contour(
240
+ Z: np.ndarray,
241
+ #x/y axes
242
+ x: np.ndarray|None = None,
243
+ y: np.ndarray|None = None,
244
+ #figure setup
245
+ fig = None,
246
+ ax = None,
247
+ axis: bool = True,
248
+ figsize: tuple[float, float] = (10, 10),
249
+ show: bool = False,
250
+ #plot parameters
251
+ levels = 10,
252
+ color = 'black',
253
+ colors = None,
254
+ cmap = None,
255
+ negative_linestyles = '-',
256
+ linestyles = '-',
257
+ #plot formating
258
+ xlim: tuple = (None, None),
259
+ ylim: tuple = (None, None),
260
+ title: str|None = None,
261
+ #presentation parameters
262
+ save: str = "",
263
+ dpi: int = 100,
264
+ #throw the rest in a dict
265
+ **kwds
266
+ ):
267
+ # prep data
268
+ assert (ndims:=len(Z.shape))==2, f"show was given an image with {ndims} dimensions, please provide a 2d array"
269
+ # check axes
270
+ if x is None: x, y = np.mgrid[:Z.shape[0], :Z.shape[1]]
271
+ else: assert (len(x), len(y)) == Z.shape, f"Given x of shape {len(x)} and y of shape {len(y)} but image is of shape {image.shape}"
272
+ # prep figure
273
+ if ax is None: (fig, ax) = plt.subplots(figsize=figsize)
274
+ fig = ax.get_figure()
275
+ if not axis:
276
+ ax.axis('off')
277
+ ax.set_position([0, 0, 1, 1])
278
+ colorbar = False
279
+ # plot data
280
+ contour_dict = {'levels':levels,"linestyles":linestyles,'negative_linestyles':negative_linestyles}
281
+ if cmap: #plot with a cmap
282
+ contour_dict['cmap'] = cmap
283
+ else: #plot with a color
284
+ contour_dict['colors'] = color if not colors else colors
285
+ img = ax.contour(
286
+ x, y, Z,
287
+ **contour_dict,
288
+ **kwds
289
+ )
290
+ # set limits
291
+ ax.set_xlim(xlim)
292
+ ax.set_ylim(ylim)
293
+ # set title
294
+ ax.set_title(title)
295
+ # set aspect
296
+ ax.set_aspect('equal')
297
+ # present the figure
298
+ if len(save)>0:
299
+ if not '.' in save: save +=".jpeg"
300
+ plt.savefig(save, dpi=dpi)
301
+ if show: plt.show()
302
+ return fig, ax, img
303
+ def contourf(
304
+ Z: np.ndarray,
305
+ #x/y axes
306
+ x: np.ndarray|None = None,
307
+ y: np.ndarray|None = None,
308
+ #figure setup
309
+ fig = None,
310
+ ax = None,
311
+ axis: bool = True,
312
+ figsize: tuple[float, float] = (10, 10),
313
+ show: bool = False,
314
+ #plot parameters
315
+ levels = 10,
316
+ cmap = default_cmap,
317
+ colorbar: bool = True,
318
+ colorbar_style: dict = {'location':'right', "size":"7%", "pad":0.05},
319
+ cticks: list|None = None,
320
+ units: str|None = None,
321
+ #plot formating
322
+ xlim: tuple = (None, None),
323
+ ylim: tuple = (None, None),
324
+ title: str|None = None,
325
+ #presentation parameters
326
+ save: str = "",
327
+ dpi: int = 100,
328
+ #throw the rest in a dict
329
+ **kwds
330
+ ):
331
+ # prep data
332
+ assert (ndims:=len(Z.shape))==2, f"show was given an image with {ndims} dimensions, please provide a 2d array"
333
+ # check axes
334
+ if x is None: x, y = np.mgrid[:z.shape[0], :z.shape[1]]
335
+ else: assert (len(x), len(y)) == z.shape, f"Given x of shape {len(x)} and y of shape {len(y)} but image is of shape {image.shape}"
336
+ # prep figure
337
+ if ax is None: (fig, ax) = plt.subplots(figsize=figsize)
338
+ fig = ax.get_figure()
339
+ if not axis:
340
+ ax.axis('off')
341
+ ax.set_position([0, 0, 1, 1])
342
+ colorbar = False
343
+ # plot data
344
+ contour_dict = {'levels':levels,"cmap":cmap}
345
+ img = ax.contourf(
346
+ x, y, Z,
347
+ **contour_dict,
348
+ **kwds
349
+ )
350
+ # colorbar
351
+ if colorbar:
352
+ divider = make_axes_locatable(ax)
353
+ colorbar_location = colorbar_style.pop("location") if "location" in colorbar_style.keys() else "right"
354
+ cax = divider.append_axes(colorbar_location, **colorbar_style)
355
+ fig.colorbar(img, cax=cax, ax=ax, ticks=cticks, label=units)
356
+ # set limits
357
+ ax.set_xlim(xlim)
358
+ ax.set_ylim(ylim)
359
+ # set title
360
+ ax.set_title(title)
361
+ # set aspect
362
+ ax.set_aspect('equal')
363
+ # present the figure
364
+ if len(save)>0:
365
+ if not '.' in save: save +=".jpeg"
366
+ plt.savefig(save, dpi=dpi)
367
+ if show: plt.show()
368
+ return fig, ax, img
369
+
370
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
371
+ # >-|===|> Decorators <|===|-<
372
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
373
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
374
+ # >-|===|> Classes <|===|-<
375
+ # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==