kplot 1.1.6__tar.gz → 1.1.8__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.
@@ -1,7 +1,9 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: kplot
3
- Version: 1.1.6
3
+ Version: 1.1.8
4
4
  Summary: a matplotlib wrapper for keyan :-)
5
+ Author: Keyan Gootkin
6
+ Author-email: Keyan Gootkin <keyangootkin@gmail.com>
5
7
  Requires-Dist: kbasic>=0.1.4
6
8
  Requires-Dist: matplotlib>=3.10.8
7
9
  Requires-Dist: numpy>=2.4.2
@@ -1,8 +1,11 @@
1
1
  [project]
2
2
  name = "kplot"
3
- version = "1.1.6"
3
+ version = "1.1.8"
4
4
  description = "a matplotlib wrapper for keyan :-)"
5
5
  readme = "README.md"
6
+ authors = [
7
+ { name="Keyan Gootkin", email="keyangootkin@gmail.com" },
8
+ ]
6
9
  requires-python = ">=3.11"
7
10
  dependencies = [
8
11
  "kbasic>=0.1.4",
@@ -1,20 +1,23 @@
1
1
  # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
2
2
  # >-|===|> Imports <|===|-<
3
3
  # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
4
- from kbasic.array import tile
5
- from functools import wraps
6
- from glob import glob
7
- import matplotlib.pyplot as plt
4
+ from kbasic.typing import Number, Iterable
8
5
  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
6
+ from matplotlib.colors import Colormap, LinearSegmentedColormap, ListedColormap, \
7
+ hex2color, Normalize, LogNorm, FuncNorm, AsinhNorm, PowerNorm, SymLogNorm, \
8
+ BoundaryNorm, CenteredNorm, TwoSlopeNorm
9
+ from numpy import uint8, zeros, ndarray, inf, nanmin, nanmax, nanquantile, nanmean, \
10
+ nanstd, absolute, log10, ones, linspace
11
+ from colorist import ColorOKLCH
12
12
 
13
13
  # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
14
14
  # >-|===|> Types <|===|-<
15
15
  # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
16
16
  class Norm:
17
- types: list = [Normalize, LogNorm, FuncNorm, AsinhNorm, PowerNorm, SymLogNorm, BoundaryNorm, CenteredNorm, TwoSlopeNorm]
17
+ types: list = [
18
+ Normalize, LogNorm, FuncNorm, AsinhNorm, PowerNorm, SymLogNorm,
19
+ BoundaryNorm, CenteredNorm, TwoSlopeNorm
20
+ ]
18
21
  class Cmap:
19
22
  types: list = [Colormap, ListedColormap, LinearSegmentedColormap]
20
23
  # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
@@ -30,7 +33,7 @@ manoaskies_centered = LinearSegmentedColormap.from_list("manoaskies_centered", [
30
33
  manoaskies_background_blue = "#0C0524"
31
34
  pink2grey = LinearSegmentedColormap.from_list("p2g", [pink, shadow])
32
35
  grey2black = LinearSegmentedColormap.from_list("g2b", [shadow, "#000000"])
33
- colors_list = np.zeros((256, 4))
36
+ colors_list = zeros((256, 4))
34
37
  colors_list[:128] = list(hex2color(manoaskies_background_blue))+[1]
35
38
  for i in range(28): colors_list[128+i] = pink2grey(i/28)
36
39
  for i in range(100): colors_list[156+i] = grey2black(i/150)
@@ -40,10 +43,9 @@ default_cmap = cm.plasma
40
43
  # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
41
44
  # >-|===|> Functions <|===|-<
42
45
  # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
43
- # # Ploting utils
44
46
  def auto_norm(
45
47
  norm: str,
46
- frames: np.ndarray,
48
+ frames: ndarray,
47
49
  linear_threshold: float|None = None,
48
50
  center: float|None = None,
49
51
  saturate: float|None = None
@@ -52,7 +54,7 @@ def auto_norm(
52
54
 
53
55
  Args:
54
56
  norm (str): what type of scale to use, e.g. lognorm or centerednorm
55
- frames (np.ndarray): the images to base the normalization on
57
+ frames (ndarray): the images to base the normalization on
56
58
  linear_threshold (float | None, optional): for symlognorm. Defaults to None.
57
59
  center (float | None, optional): for centered normalizations. Defaults to None.
58
60
  saturate (float | None, optional): the level at which to saturate the norm, e.g. if saturate=0.01 then the
@@ -61,38 +63,67 @@ def auto_norm(
61
63
  Returns:
62
64
  matplotlib normalization
63
65
  """
64
- frames = frames[(-np.inf < frames)&(frames < np.inf)]
66
+ frames = frames[(-inf < frames)&(frames < inf)]
65
67
  # 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
+ low = nanmin(frames) if saturate is None else nanquantile(frames, 1-saturate[0]) if isinstance(saturate, tuple) else nanquantile(frames, 1-saturate)
69
+ high = nanmax(frames) if saturate is None else nanquantile(frames, 0+saturate[1]) if isinstance(saturate, tuple) else nanquantile(frames, 0+saturate)
68
70
  match norm.lower():
69
71
  case "lognorm"|"log":
70
72
  if low < 0: raise ValueError(f"minimum is {low}, LogNorm only takes positive values")
71
- if low==0: low=np.nanmin(frames[frames!=0])
73
+ if low==0: low=nanmin(frames[frames!=0])
72
74
  return LogNorm(vmin=low, vmax=high)
73
75
  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!")
76
+ sig = nanstd(frames)
77
+ mu = nanmean(frames)
78
+ if absolute(mu)-sig > 0: raise TypeError("SymLogNorm is only designed for stuff close to zero!")
77
79
  return SymLogNorm(sig if linear_threshold is None else linear_threshold, vmin=low, vmax=high)
78
80
  case n if n in ["centerednorm", "twoslope", "twoslopenorm"]:
79
- sig = np.nanstd(frames)
80
- mu = np.nanmean(frames)
81
+ sig = nanstd(frames)
82
+ mu = nanmean(frames)
81
83
  # 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
84
+ vcenter = center if not center is None else 0 if absolute(mu)-sig > 0 else mu
83
85
  return TwoSlopeNorm(vmin=low, vcenter=vcenter, vmax=high)
84
86
  case _: return Normalize(vmin=low, vmax=high)
85
- def align_algorithm(x: list|np.ndarray, mode: str):
87
+ def align_algorithm(x: list|ndarray, mode: str):
86
88
  match mode:
87
89
  case 'mid'|'m'|'center'|'c':
88
90
  return [(x[i] + x[i+1])/2 for i in range(len(x)-1)]
89
91
  case 'logmid'|'lm':
90
- return [np.log10((10**x[i] + 10**x[i+1])/ 2) for i in range(len(x)-1)]
92
+ return [log10((10**x[i] + 10**x[i+1])/ 2) for i in range(len(x)-1)]
91
93
  case 'left'|'l':
92
94
  return x[:-1]
93
95
  case 'right'|'r':
94
96
  return x[1:]
97
+ def oklch_cmap(
98
+ luminosity: float|Iterable[float] = 0.5,
99
+ chroma: float|Iterable[float] = 0.4,
100
+ hue: float|Iterable[float] = (90, 270),
101
+ ) -> ListedColormap:
102
+ ls = ones(256) * luminosity if type(luminosity) in Number.types else linspace(*luminosity, 256)
103
+ cs = ones(256) * chroma if type(chroma) in Number.types else linspace(*chroma, 256)
104
+ hs = ones(256) * hue if type(hue) in Number.types else linspace(*hue, 256)
105
+ return ListedColormap([
106
+ OKLCH(li, ci, hi).rgb for li, ci, hi in zip(ls, cs, hs)
107
+ ])
95
108
 
96
109
  # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
97
- # >-|===|> Decorators <|===|-<
110
+ # >-|===|> Classes <|===|-<
98
111
  # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
112
+ class OKLCH:
113
+ def __init__(self, lightness: float, chroma: float, hue: float, alpha: float = 1) -> None:
114
+ assert all(m:=[0<=lightness<=1, 0.<=chroma<=0.4, 0<=hue<360, 0<=alpha<=1]), \
115
+ f"invalid value given in {lightness, chroma, hue, alpha}"
116
+ self.lightness = lightness
117
+ self.chroma = chroma
118
+ self.hue = hue
119
+ self.alpha = alpha
120
+ @property
121
+ def rgb(self) -> tuple[float]:
122
+ cobj = ColorOKLCH(self.lightness, self.chroma, self.hue).convert_oklch_to_srgb()
123
+ return (uint8(cobj.red)/256, uint8(cobj.green)/256, uint8(cobj.blue)/256)
124
+ @property
125
+ def rgba(self) -> tuple[float]:
126
+ return tuple([*self.rgb, self.alpha])
127
+ @property
128
+ def ansi(self) -> str:
129
+ return ColorOKLCH(self.lightness, self.chroma, self.hue).generate_ansi_code()
@@ -4,31 +4,28 @@
4
4
  from kplot.utils import column_width, two_column_width
5
5
  from kplot.cmaps import auto_norm
6
6
  from kplot.image import show
7
+ from kbasic.strings import purple
7
8
  from kbasic.bar import verbose_bar
8
9
  from kbasic.parsing import ensure_path
9
- import matplotlib.pyplot as plt
10
- from matplotlib.figure import Figure
11
- import numpy as np
12
10
  from os import system
13
11
  from glob import glob
14
12
  from typing import Callable
13
+ from functools import wraps
14
+ from numpy import ndarray, array
15
+ from matplotlib.pyplot import subplots
16
+ from matplotlib.figure import Figure
15
17
 
16
- # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
17
- # >-|===|> Types <|===|-<
18
- # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
19
- # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
20
- # >-|===|> Definitions <|===|-<
21
- # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
22
18
  # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
23
19
  # >-|===|> Functions <|===|-<
24
20
  # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
25
21
  def ffmpeg(
26
22
  file_name: str,
27
23
  source: str = "./frames", destination: str = "./",
28
- fps: int = 30
24
+ fps: int = 30, verbose: bool = False,
29
25
  ) -> None:
30
26
  file_path: str = destination + '/' + file_name
31
27
  if not file_path.endswith('.mp4'): file_path += ".mp4"
28
+ if verbose: print(purple("FFMPeg running..."))
32
29
  system(f"ffmpeg -loglevel 8 -framerate {fps} -pattern_type glob -i '{source+'/*.png'}' -c:v libx264 -pix_fmt yuv420p -y {file_path}")
33
30
  def func_video(
34
31
  video_name: str, fig: Figure, updater: Callable, N: int,
@@ -39,7 +36,7 @@ def func_video(
39
36
  if len(glob(f"{frames}/*.png"))>0: system(f"rm {frames}/*.png")
40
37
  ndigits = len(str(N))
41
38
  # if the size isn't divisible by 2 ffmpeg gets mad???
42
- [wpix, hpix] = (np.array(fig.get_size_inches()) * dpi // 1).astype(int)
39
+ [wpix, hpix] = (array(fig.get_size_inches()) * dpi // 1).astype(int)
43
40
  if wpix%2==1: wpix += 1
44
41
  if hpix%2==1: hpix += 1
45
42
  fig.set_size_inches(wpix/dpi, hpix/dpi)
@@ -57,7 +54,7 @@ def line_video(
57
54
  fps=20, dpi=100,
58
55
  **kwargs
59
56
  ):
60
- if not ax: fig, ax = plt.subplots(figsize=figsize)
57
+ if not ax: fig, ax = subplots(figsize=figsize)
61
58
  fig = ax.get_figure()
62
59
  [line] = ax.plot(xs[0], ys[0], **kwargs)
63
60
  def update(f: int):
@@ -70,7 +67,7 @@ def lines_video(
70
67
  ax=None, figsize=(5,5),
71
68
  fps=20, dpi=100
72
69
  ) -> None:
73
- if not ax: fig, ax = plt.subplots(figsize=figsize)
70
+ if not ax: fig, ax = subplots(figsize=figsize)
74
71
  fig = ax.get_figure()
75
72
  lines = []
76
73
  for (x, y) in data:
@@ -81,7 +78,7 @@ def lines_video(
81
78
  line.set_data(x[i], y[i])
82
79
  func_video(fname, fig, update, min([len(data[i][0]) for i in range(len(data))]), fps=fps, dpi=dpi, destination=destination, frames=frames)
83
80
  def show_video(
84
- frames: np.ndarray,
81
+ frames: ndarray,
85
82
  fname: str,
86
83
  ax = None,
87
84
  norm = 'linear',
@@ -100,20 +97,16 @@ def show_video(
100
97
  # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
101
98
  # >-|===|> Decorators <|===|-<
102
99
  # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
103
- def line_video_function(func):
100
+ def line_video_function(func: Callable):
104
101
  @wraps(func)
105
102
  def line_video_wrapper(*args, ax=None, save="default.gif", **kwargs):
106
103
  # Calculate data via func
107
104
  xs, ys = func(*args, **kwargs)
108
105
  line_video(xs, ys, save, ax=ax)
109
106
  return line_video_wrapper
110
- def show_video_function(func):
107
+ def show_video_function(func: Callable):
111
108
  @wraps(func)
112
109
  def simple_video_wrapper(*args, save="default.gif", norm='linear', **kwargs):
113
110
  frames = func(*args, **kwargs)
114
111
  show_video(frames, save, norm=norm)
115
- return simple_video_wrapper
116
-
117
- # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
118
- # >-|===|> Classes <|===|-<
119
- # !==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==!==
112
+ return simple_video_wrapper
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes