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.
- {kplot-1.1.6 → kplot-1.1.8}/PKG-INFO +3 -1
- {kplot-1.1.6 → kplot-1.1.8}/pyproject.toml +4 -1
- {kplot-1.1.6 → kplot-1.1.8}/src/kplot/cmaps.py +56 -25
- {kplot-1.1.6 → kplot-1.1.8}/src/kplot/movie.py +14 -21
- {kplot-1.1.6 → kplot-1.1.8}/README.md +0 -0
- {kplot-1.1.6 → kplot-1.1.8}/src/kplot/__init__.py +0 -0
- {kplot-1.1.6 → kplot-1.1.8}/src/kplot/axes.py +0 -0
- {kplot-1.1.6 → kplot-1.1.8}/src/kplot/hist.py +0 -0
- {kplot-1.1.6 → kplot-1.1.8}/src/kplot/image.py +0 -0
- {kplot-1.1.6 → kplot-1.1.8}/src/kplot/plot.py +0 -0
- {kplot-1.1.6 → kplot-1.1.8}/src/kplot/styles/example.mpl +0 -0
- {kplot-1.1.6 → kplot-1.1.8}/src/kplot/styles/publication.mpl +0 -0
- {kplot-1.1.6 → kplot-1.1.8}/src/kplot/threeD/__init__.py +0 -0
- {kplot-1.1.6 → kplot-1.1.8}/src/kplot/threeD/slider_images.py +0 -0
- {kplot-1.1.6 → kplot-1.1.8}/src/kplot/utils.py +0 -0
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: kplot
|
|
3
|
-
Version: 1.1.
|
|
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.
|
|
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.
|
|
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,
|
|
10
|
-
|
|
11
|
-
|
|
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 = [
|
|
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 =
|
|
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:
|
|
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 (
|
|
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[(-
|
|
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 =
|
|
67
|
-
high =
|
|
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=
|
|
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 =
|
|
75
|
-
mu =
|
|
76
|
-
if
|
|
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 =
|
|
80
|
-
mu =
|
|
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
|
|
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|
|
|
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 [
|
|
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
|
-
# >-|===|>
|
|
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] = (
|
|
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 =
|
|
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 =
|
|
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:
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|