oscura 0.7.0__py3-none-any.whl → 0.10.0__py3-none-any.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.
- oscura/__init__.py +19 -19
- oscura/analyzers/__init__.py +2 -0
- oscura/analyzers/digital/extraction.py +2 -3
- oscura/analyzers/digital/quality.py +1 -1
- oscura/analyzers/digital/timing.py +1 -1
- oscura/analyzers/eye/__init__.py +5 -1
- oscura/analyzers/eye/generation.py +501 -0
- oscura/analyzers/jitter/__init__.py +6 -6
- oscura/analyzers/jitter/timing.py +419 -0
- oscura/analyzers/patterns/__init__.py +94 -0
- oscura/analyzers/patterns/reverse_engineering.py +991 -0
- oscura/analyzers/power/__init__.py +35 -12
- oscura/analyzers/power/basic.py +3 -3
- oscura/analyzers/power/soa.py +1 -1
- oscura/analyzers/power/switching.py +3 -3
- oscura/analyzers/signal_classification.py +529 -0
- oscura/analyzers/signal_integrity/sparams.py +3 -3
- oscura/analyzers/statistics/__init__.py +4 -0
- oscura/analyzers/statistics/basic.py +152 -0
- oscura/analyzers/statistics/correlation.py +47 -6
- oscura/analyzers/validation.py +1 -1
- oscura/analyzers/waveform/__init__.py +2 -0
- oscura/analyzers/waveform/measurements.py +329 -163
- oscura/analyzers/waveform/measurements_with_uncertainty.py +91 -35
- oscura/analyzers/waveform/spectral.py +498 -54
- oscura/api/dsl/commands.py +15 -6
- oscura/api/server/templates/base.html +137 -146
- oscura/api/server/templates/export.html +84 -110
- oscura/api/server/templates/home.html +248 -267
- oscura/api/server/templates/protocols.html +44 -48
- oscura/api/server/templates/reports.html +27 -35
- oscura/api/server/templates/session_detail.html +68 -78
- oscura/api/server/templates/sessions.html +62 -72
- oscura/api/server/templates/waveforms.html +54 -64
- oscura/automotive/__init__.py +1 -1
- oscura/automotive/can/session.py +1 -1
- oscura/automotive/dbc/generator.py +638 -23
- oscura/automotive/dtc/data.json +102 -17
- oscura/automotive/uds/decoder.py +99 -6
- oscura/cli/analyze.py +8 -2
- oscura/cli/batch.py +36 -5
- oscura/cli/characterize.py +18 -4
- oscura/cli/export.py +47 -5
- oscura/cli/main.py +2 -0
- oscura/cli/onboarding/wizard.py +10 -6
- oscura/cli/pipeline.py +585 -0
- oscura/cli/visualize.py +6 -4
- oscura/convenience.py +400 -32
- oscura/core/config/loader.py +0 -1
- oscura/core/measurement_result.py +286 -0
- oscura/core/progress.py +1 -1
- oscura/core/schemas/device_mapping.json +8 -2
- oscura/core/schemas/packet_format.json +24 -4
- oscura/core/schemas/protocol_definition.json +12 -2
- oscura/core/types.py +300 -199
- oscura/correlation/multi_protocol.py +1 -1
- oscura/export/legacy/__init__.py +11 -0
- oscura/export/legacy/wav.py +75 -0
- oscura/exporters/__init__.py +19 -0
- oscura/exporters/wireshark.py +809 -0
- oscura/hardware/acquisition/file.py +5 -19
- oscura/hardware/acquisition/saleae.py +10 -10
- oscura/hardware/acquisition/socketcan.py +4 -6
- oscura/hardware/acquisition/synthetic.py +1 -5
- oscura/hardware/acquisition/visa.py +6 -6
- oscura/hardware/security/side_channel_detector.py +5 -508
- oscura/inference/message_format.py +686 -1
- oscura/jupyter/display.py +2 -2
- oscura/jupyter/magic.py +3 -3
- oscura/loaders/__init__.py +17 -12
- oscura/loaders/binary.py +1 -1
- oscura/loaders/chipwhisperer.py +1 -2
- oscura/loaders/configurable.py +1 -1
- oscura/loaders/csv_loader.py +2 -2
- oscura/loaders/hdf5_loader.py +1 -1
- oscura/loaders/lazy.py +6 -1
- oscura/loaders/mmap_loader.py +0 -1
- oscura/loaders/numpy_loader.py +8 -7
- oscura/loaders/preprocessing.py +3 -5
- oscura/loaders/rigol.py +21 -7
- oscura/loaders/sigrok.py +2 -5
- oscura/loaders/tdms.py +3 -2
- oscura/loaders/tektronix.py +38 -32
- oscura/loaders/tss.py +20 -27
- oscura/loaders/vcd.py +13 -8
- oscura/loaders/wav.py +1 -6
- oscura/pipeline/__init__.py +76 -0
- oscura/pipeline/handlers/__init__.py +165 -0
- oscura/pipeline/handlers/analyzers.py +1045 -0
- oscura/pipeline/handlers/decoders.py +899 -0
- oscura/pipeline/handlers/exporters.py +1103 -0
- oscura/pipeline/handlers/filters.py +891 -0
- oscura/pipeline/handlers/loaders.py +640 -0
- oscura/pipeline/handlers/transforms.py +768 -0
- oscura/reporting/__init__.py +88 -1
- oscura/reporting/automation.py +348 -0
- oscura/reporting/citations.py +374 -0
- oscura/reporting/core.py +54 -0
- oscura/reporting/formatting/__init__.py +11 -0
- oscura/reporting/formatting/measurements.py +320 -0
- oscura/reporting/html.py +57 -0
- oscura/reporting/interpretation.py +431 -0
- oscura/reporting/summary.py +329 -0
- oscura/reporting/templates/enhanced/protocol_re.html +504 -503
- oscura/reporting/visualization.py +542 -0
- oscura/side_channel/__init__.py +38 -57
- oscura/utils/builders/signal_builder.py +5 -5
- oscura/utils/comparison/compare.py +7 -9
- oscura/utils/comparison/golden.py +1 -1
- oscura/utils/filtering/convenience.py +2 -2
- oscura/utils/math/arithmetic.py +38 -62
- oscura/utils/math/interpolation.py +20 -20
- oscura/utils/pipeline/__init__.py +4 -17
- oscura/utils/progressive.py +1 -4
- oscura/utils/triggering/edge.py +1 -1
- oscura/utils/triggering/pattern.py +2 -2
- oscura/utils/triggering/pulse.py +2 -2
- oscura/utils/triggering/window.py +3 -3
- oscura/validation/hil_testing.py +11 -11
- oscura/visualization/__init__.py +47 -284
- oscura/visualization/batch.py +160 -0
- oscura/visualization/plot.py +542 -53
- oscura/visualization/styles.py +184 -318
- oscura/workflows/__init__.py +2 -0
- oscura/workflows/batch/advanced.py +1 -1
- oscura/workflows/batch/aggregate.py +7 -8
- oscura/workflows/complete_re.py +251 -23
- oscura/workflows/digital.py +27 -4
- oscura/workflows/multi_trace.py +136 -17
- oscura/workflows/waveform.py +788 -0
- {oscura-0.7.0.dist-info → oscura-0.10.0.dist-info}/METADATA +59 -79
- {oscura-0.7.0.dist-info → oscura-0.10.0.dist-info}/RECORD +135 -149
- oscura/side_channel/dpa.py +0 -1025
- oscura/utils/optimization/__init__.py +0 -19
- oscura/utils/optimization/parallel.py +0 -443
- oscura/utils/optimization/search.py +0 -532
- oscura/utils/pipeline/base.py +0 -338
- oscura/utils/pipeline/composition.py +0 -248
- oscura/utils/pipeline/parallel.py +0 -449
- oscura/utils/pipeline/pipeline.py +0 -375
- oscura/utils/search/__init__.py +0 -16
- oscura/utils/search/anomaly.py +0 -424
- oscura/utils/search/context.py +0 -294
- oscura/utils/search/pattern.py +0 -288
- oscura/utils/storage/__init__.py +0 -61
- oscura/utils/storage/database.py +0 -1166
- oscura/visualization/accessibility.py +0 -526
- oscura/visualization/annotations.py +0 -371
- oscura/visualization/axis_scaling.py +0 -305
- oscura/visualization/colors.py +0 -451
- oscura/visualization/digital.py +0 -436
- oscura/visualization/eye.py +0 -571
- oscura/visualization/histogram.py +0 -281
- oscura/visualization/interactive.py +0 -1035
- oscura/visualization/jitter.py +0 -1042
- oscura/visualization/keyboard.py +0 -394
- oscura/visualization/layout.py +0 -400
- oscura/visualization/optimization.py +0 -1079
- oscura/visualization/palettes.py +0 -446
- oscura/visualization/power.py +0 -508
- oscura/visualization/power_extended.py +0 -955
- oscura/visualization/presets.py +0 -469
- oscura/visualization/protocols.py +0 -1246
- oscura/visualization/render.py +0 -223
- oscura/visualization/rendering.py +0 -444
- oscura/visualization/reverse_engineering.py +0 -838
- oscura/visualization/signal_integrity.py +0 -989
- oscura/visualization/specialized.py +0 -643
- oscura/visualization/spectral.py +0 -1226
- oscura/visualization/thumbnails.py +0 -340
- oscura/visualization/time_axis.py +0 -351
- oscura/visualization/waveform.py +0 -454
- {oscura-0.7.0.dist-info → oscura-0.10.0.dist-info}/WHEEL +0 -0
- {oscura-0.7.0.dist-info → oscura-0.10.0.dist-info}/entry_points.txt +0 -0
- {oscura-0.7.0.dist-info → oscura-0.10.0.dist-info}/licenses/LICENSE +0 -0
oscura/visualization/palettes.py
DELETED
|
@@ -1,446 +0,0 @@
|
|
|
1
|
-
"""Colorblind-safe palettes for Oscura visualizations.
|
|
2
|
-
|
|
3
|
-
This module provides colorblind-safe color palettes and utilities for
|
|
4
|
-
creating accessible visualizations.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Example:
|
|
8
|
-
>>> from oscura.visualization.palettes import get_palette, show_palette
|
|
9
|
-
>>> colors = get_palette("colorblind_safe")
|
|
10
|
-
>>> show_palette("viridis")
|
|
11
|
-
|
|
12
|
-
References:
|
|
13
|
-
- Wong, B. (2011). Color blindness. Nature Methods, 8(6), 441.
|
|
14
|
-
- Colorbrewer 2.0 (colorbrewer2.org)
|
|
15
|
-
- WCAG 2.1 color contrast guidelines
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
from __future__ import annotations
|
|
19
|
-
|
|
20
|
-
from typing import TYPE_CHECKING, Literal
|
|
21
|
-
|
|
22
|
-
import matplotlib.pyplot as plt
|
|
23
|
-
import numpy as np
|
|
24
|
-
from matplotlib.patches import Rectangle
|
|
25
|
-
|
|
26
|
-
if TYPE_CHECKING:
|
|
27
|
-
from matplotlib.colors import Colormap
|
|
28
|
-
|
|
29
|
-
# Define colorblind-safe palettes
|
|
30
|
-
# Based on Wong 2011 and Paul Tol's schemes
|
|
31
|
-
PALETTES = {
|
|
32
|
-
"colorblind_safe": [
|
|
33
|
-
"#0173B2", # Blue
|
|
34
|
-
"#DE8F05", # Orange
|
|
35
|
-
"#029E73", # Green
|
|
36
|
-
"#CC78BC", # Purple
|
|
37
|
-
"#CA9161", # Brown
|
|
38
|
-
"#FBAFE4", # Pink
|
|
39
|
-
"#949494", # Gray
|
|
40
|
-
"#ECE133", # Yellow
|
|
41
|
-
],
|
|
42
|
-
"colorblind8": [ # Paul Tol's bright scheme
|
|
43
|
-
"#4477AA", # Blue
|
|
44
|
-
"#EE6677", # Red
|
|
45
|
-
"#228833", # Green
|
|
46
|
-
"#CCBB44", # Yellow
|
|
47
|
-
"#66CCEE", # Cyan
|
|
48
|
-
"#AA3377", # Purple
|
|
49
|
-
"#BBBBBB", # Gray
|
|
50
|
-
"#EE8866", # Orange
|
|
51
|
-
],
|
|
52
|
-
"high_contrast": [
|
|
53
|
-
"#000000", # Black
|
|
54
|
-
"#E69F00", # Orange
|
|
55
|
-
"#56B4E9", # Sky Blue
|
|
56
|
-
"#009E73", # Bluish Green
|
|
57
|
-
"#F0E442", # Yellow
|
|
58
|
-
"#0072B2", # Blue
|
|
59
|
-
"#D55E00", # Vermillion
|
|
60
|
-
"#CC79A7", # Reddish Purple
|
|
61
|
-
],
|
|
62
|
-
"grayscale": [
|
|
63
|
-
"#000000", # Black
|
|
64
|
-
"#404040", # Dark Gray
|
|
65
|
-
"#808080", # Medium Gray
|
|
66
|
-
"#BFBFBF", # Light Gray
|
|
67
|
-
"#E0E0E0", # Very Light Gray
|
|
68
|
-
],
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
# Line styles for multi-line plots
|
|
72
|
-
LINE_STYLES = ["solid", "dashed", "dotted", "dashdot"]
|
|
73
|
-
|
|
74
|
-
# Marker styles for scatter plots
|
|
75
|
-
MARKER_STYLES = ["o", "s", "^", "D", "v", "p", "*", "h"]
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
def get_palette(
|
|
79
|
-
name: Literal[
|
|
80
|
-
"colorblind_safe", "colorblind8", "high_contrast", "grayscale"
|
|
81
|
-
] = "colorblind_safe",
|
|
82
|
-
) -> list[str]:
|
|
83
|
-
"""Get a colorblind-safe color palette.
|
|
84
|
-
|
|
85
|
-
: Default palette is colorblind-safe.
|
|
86
|
-
Returns list of hex color codes that are distinguishable for colorblind users.
|
|
87
|
-
|
|
88
|
-
Args:
|
|
89
|
-
name: Palette name. Options:
|
|
90
|
-
- "colorblind_safe": Default palette (Wong 2011) with 8 colors
|
|
91
|
-
- "colorblind8": Paul Tol's bright scheme with 8 colors
|
|
92
|
-
- "high_contrast": High contrast palette suitable for presentations
|
|
93
|
-
- "grayscale": Grayscale palette for printing
|
|
94
|
-
|
|
95
|
-
Returns:
|
|
96
|
-
List of hex color codes
|
|
97
|
-
|
|
98
|
-
Raises:
|
|
99
|
-
ValueError: If palette name is not recognized
|
|
100
|
-
|
|
101
|
-
Example:
|
|
102
|
-
>>> from oscura.visualization.palettes import get_palette
|
|
103
|
-
>>> colors = get_palette("colorblind_safe")
|
|
104
|
-
>>> print(colors[0]) # First color (blue)
|
|
105
|
-
#0173B2
|
|
106
|
-
|
|
107
|
-
References:
|
|
108
|
-
ACC-001: Colorblind-Safe Visualization Palette
|
|
109
|
-
"""
|
|
110
|
-
if name not in PALETTES:
|
|
111
|
-
valid = ", ".join(PALETTES.keys())
|
|
112
|
-
raise ValueError(f"Unknown palette: {name}. Valid options: {valid}")
|
|
113
|
-
return PALETTES[name].copy()
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
def get_colormap(
|
|
117
|
-
name: Literal["viridis", "cividis", "plasma", "inferno", "magma"] = "viridis",
|
|
118
|
-
) -> Colormap:
|
|
119
|
-
"""Get a colorblind-safe matplotlib colormap.
|
|
120
|
-
|
|
121
|
-
: Default palette is colorblind-safe (e.g., viridis, cividis).
|
|
122
|
-
Returns perceptually uniform colormaps suitable for continuous data.
|
|
123
|
-
|
|
124
|
-
Args:
|
|
125
|
-
name: Colormap name. All options are colorblind-safe:
|
|
126
|
-
- "viridis": Default, blue-green-yellow (recommended)
|
|
127
|
-
- "cividis": Blue-yellow, optimized for CVD
|
|
128
|
-
- "plasma": Purple-red-yellow
|
|
129
|
-
- "inferno": Black-purple-yellow
|
|
130
|
-
- "magma": Black-purple-white
|
|
131
|
-
|
|
132
|
-
Returns:
|
|
133
|
-
Matplotlib Colormap object
|
|
134
|
-
|
|
135
|
-
Raises:
|
|
136
|
-
ValueError: If colormap name is not recognized
|
|
137
|
-
|
|
138
|
-
Example:
|
|
139
|
-
>>> from oscura.visualization.palettes import get_colormap
|
|
140
|
-
>>> import matplotlib.pyplot as plt
|
|
141
|
-
>>> cmap = get_colormap("viridis")
|
|
142
|
-
>>> plt.imshow(data, cmap=cmap)
|
|
143
|
-
|
|
144
|
-
References:
|
|
145
|
-
ACC-001: Colorblind-Safe Visualization Palette
|
|
146
|
-
"""
|
|
147
|
-
valid = ["viridis", "cividis", "plasma", "inferno", "magma"]
|
|
148
|
-
if name not in valid:
|
|
149
|
-
raise ValueError(f"Unknown colormap: {name}. Valid options: {', '.join(valid)}")
|
|
150
|
-
return plt.get_cmap(name)
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
def get_line_styles(
|
|
154
|
-
n_lines: int,
|
|
155
|
-
*,
|
|
156
|
-
palette: str = "colorblind_safe",
|
|
157
|
-
cycle_styles: bool = True,
|
|
158
|
-
) -> list[dict]: # type: ignore[type-arg]
|
|
159
|
-
"""Get line styles for multi-line plots.
|
|
160
|
-
|
|
161
|
-
: Multi-line plots use distinct line styles in addition to colors.
|
|
162
|
-
Combines colors with line styles for maximum distinguishability.
|
|
163
|
-
|
|
164
|
-
Args:
|
|
165
|
-
n_lines: Number of lines to style
|
|
166
|
-
palette: Palette name for colors
|
|
167
|
-
cycle_styles: Cycle through line styles if more lines than styles
|
|
168
|
-
|
|
169
|
-
Returns:
|
|
170
|
-
List of style dictionaries with 'color' and 'linestyle' keys
|
|
171
|
-
|
|
172
|
-
Example:
|
|
173
|
-
>>> from oscura.visualization.palettes import get_line_styles
|
|
174
|
-
>>> styles = get_line_styles(4)
|
|
175
|
-
>>> for i, style in enumerate(styles):
|
|
176
|
-
... plt.plot(x, y[i], **style, label=f"Line {i}")
|
|
177
|
-
|
|
178
|
-
References:
|
|
179
|
-
ACC-001: Multi-line plots use distinct line styles in addition to colors
|
|
180
|
-
"""
|
|
181
|
-
colors = get_palette(palette) # type: ignore[arg-type]
|
|
182
|
-
styles = []
|
|
183
|
-
|
|
184
|
-
for i in range(n_lines):
|
|
185
|
-
color = colors[i % len(colors)]
|
|
186
|
-
linestyle = LINE_STYLES[i % len(LINE_STYLES)] if cycle_styles else "solid"
|
|
187
|
-
|
|
188
|
-
styles.append({"color": color, "linestyle": linestyle})
|
|
189
|
-
|
|
190
|
-
return styles
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
def get_pass_fail_symbols() -> dict[str, str]:
|
|
194
|
-
"""Get pass/fail symbols for accessible reporting.
|
|
195
|
-
|
|
196
|
-
: Pass/fail uses symbols (✓/✗) not just red/green.
|
|
197
|
-
Returns symbols that work in text and don't rely on color alone.
|
|
198
|
-
|
|
199
|
-
Returns:
|
|
200
|
-
Dictionary with 'pass' and 'fail' symbol keys
|
|
201
|
-
|
|
202
|
-
Example:
|
|
203
|
-
>>> from oscura.visualization.palettes import get_pass_fail_symbols
|
|
204
|
-
>>> symbols = get_pass_fail_symbols()
|
|
205
|
-
>>> print(f"{symbols['pass']} Test passed")
|
|
206
|
-
✓ Test passed
|
|
207
|
-
|
|
208
|
-
References:
|
|
209
|
-
ACC-001: Pass/fail uses symbols (✓/✗) not just red/green
|
|
210
|
-
"""
|
|
211
|
-
return {"pass": "✓", "fail": "✗"}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
def get_pass_fail_colors(
|
|
215
|
-
*,
|
|
216
|
-
colorblind_safe: bool = True,
|
|
217
|
-
) -> dict[str, str]:
|
|
218
|
-
"""Get pass/fail colors.
|
|
219
|
-
|
|
220
|
-
: Pass/fail colors are colorblind-safe when combined with symbols.
|
|
221
|
-
Returns green/red or blue/orange based on colorblind_safe flag.
|
|
222
|
-
|
|
223
|
-
Args:
|
|
224
|
-
colorblind_safe: Use colorblind-safe blue/orange instead of green/red
|
|
225
|
-
|
|
226
|
-
Returns:
|
|
227
|
-
Dictionary with 'pass' and 'fail' color keys (hex codes)
|
|
228
|
-
|
|
229
|
-
Example:
|
|
230
|
-
>>> from oscura.visualization.palettes import get_pass_fail_colors
|
|
231
|
-
>>> colors = get_pass_fail_colors(colorblind_safe=True)
|
|
232
|
-
>>> print(colors['pass']) # Blue for pass
|
|
233
|
-
#0173B2
|
|
234
|
-
|
|
235
|
-
References:
|
|
236
|
-
ACC-001: Colorblind-Safe Visualization Palette
|
|
237
|
-
"""
|
|
238
|
-
if colorblind_safe:
|
|
239
|
-
return {
|
|
240
|
-
"pass": "#0173B2", # Blue
|
|
241
|
-
"fail": "#DE8F05", # Orange
|
|
242
|
-
}
|
|
243
|
-
else:
|
|
244
|
-
return {
|
|
245
|
-
"pass": "#2CA02C", # Green
|
|
246
|
-
"fail": "#D62728", # Red
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
def show_palette(
|
|
251
|
-
name: str = "colorblind_safe",
|
|
252
|
-
*,
|
|
253
|
-
save_path: str | None = None,
|
|
254
|
-
) -> None:
|
|
255
|
-
"""Display a color palette preview.
|
|
256
|
-
|
|
257
|
-
: Palette preview: show_palette(name).
|
|
258
|
-
Shows palette colors in a matplotlib figure for visual inspection.
|
|
259
|
-
|
|
260
|
-
Args:
|
|
261
|
-
name: Palette name or colormap name
|
|
262
|
-
save_path: Optional path to save the figure
|
|
263
|
-
|
|
264
|
-
Raises:
|
|
265
|
-
ValueError: If unknown palette or colormap name.
|
|
266
|
-
|
|
267
|
-
Example:
|
|
268
|
-
>>> from oscura.visualization.palettes import show_palette
|
|
269
|
-
>>> show_palette("colorblind_safe")
|
|
270
|
-
>>> show_palette("viridis", save_path="palette.png")
|
|
271
|
-
|
|
272
|
-
References:
|
|
273
|
-
ACC-001: Colorblind-Safe Visualization Palette
|
|
274
|
-
"""
|
|
275
|
-
_fig, ax = plt.subplots(figsize=(8, 2))
|
|
276
|
-
|
|
277
|
-
# Check if it's a discrete palette or continuous colormap
|
|
278
|
-
if name in PALETTES:
|
|
279
|
-
# Discrete palette
|
|
280
|
-
colors = PALETTES[name]
|
|
281
|
-
n_colors = len(colors)
|
|
282
|
-
x = np.arange(n_colors)
|
|
283
|
-
|
|
284
|
-
# Create color swatches
|
|
285
|
-
for i, color in enumerate(colors):
|
|
286
|
-
ax.add_patch(Rectangle((i, 0), 1, 1, facecolor=color, edgecolor="black"))
|
|
287
|
-
|
|
288
|
-
ax.set_xlim(0, n_colors)
|
|
289
|
-
ax.set_ylim(0, 1)
|
|
290
|
-
ax.set_yticks([])
|
|
291
|
-
ax.set_xticks(x + 0.5)
|
|
292
|
-
ax.set_xticklabels([f"C{i}" for i in range(n_colors)])
|
|
293
|
-
ax.set_title(f"Palette: {name}")
|
|
294
|
-
|
|
295
|
-
else:
|
|
296
|
-
# Continuous colormap
|
|
297
|
-
try:
|
|
298
|
-
cmap = get_colormap(name) # type: ignore[arg-type]
|
|
299
|
-
gradient = np.linspace(0, 1, 256).reshape(1, -1)
|
|
300
|
-
ax.imshow(gradient, aspect="auto", cmap=cmap)
|
|
301
|
-
ax.set_yticks([])
|
|
302
|
-
ax.set_xticks([0, 64, 128, 192, 255])
|
|
303
|
-
ax.set_xticklabels(["0", "0.25", "0.5", "0.75", "1.0"])
|
|
304
|
-
ax.set_title(f"Colormap: {name}")
|
|
305
|
-
except ValueError:
|
|
306
|
-
raise ValueError(f"Unknown palette or colormap: {name}")
|
|
307
|
-
|
|
308
|
-
plt.tight_layout()
|
|
309
|
-
|
|
310
|
-
if save_path:
|
|
311
|
-
plt.savefig(save_path, dpi=150, bbox_inches="tight")
|
|
312
|
-
else:
|
|
313
|
-
plt.show()
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
def create_custom_palette(
|
|
317
|
-
colors: list[str],
|
|
318
|
-
*,
|
|
319
|
-
name: str = "custom",
|
|
320
|
-
) -> list[str]:
|
|
321
|
-
"""Create a custom color palette.
|
|
322
|
-
|
|
323
|
-
: Custom palette creation support.
|
|
324
|
-
Validates and registers a custom color palette.
|
|
325
|
-
|
|
326
|
-
Args:
|
|
327
|
-
colors: List of hex color codes
|
|
328
|
-
name: Name for the custom palette
|
|
329
|
-
|
|
330
|
-
Returns:
|
|
331
|
-
List of validated hex color codes
|
|
332
|
-
|
|
333
|
-
Raises:
|
|
334
|
-
ValueError: If color codes are invalid
|
|
335
|
-
|
|
336
|
-
Example:
|
|
337
|
-
>>> from oscura.visualization.palettes import create_custom_palette
|
|
338
|
-
>>> custom = create_custom_palette(
|
|
339
|
-
... ["#FF0000", "#00FF00", "#0000FF"],
|
|
340
|
-
... name="rgb"
|
|
341
|
-
... )
|
|
342
|
-
>>> print(custom)
|
|
343
|
-
['#FF0000', '#00FF00', '#0000FF']
|
|
344
|
-
|
|
345
|
-
References:
|
|
346
|
-
ACC-001: Custom palette creation support
|
|
347
|
-
"""
|
|
348
|
-
# Validate hex codes
|
|
349
|
-
import re
|
|
350
|
-
|
|
351
|
-
hex_pattern = re.compile(r"^#[0-9A-Fa-f]{6}$")
|
|
352
|
-
validated = []
|
|
353
|
-
|
|
354
|
-
for color in colors:
|
|
355
|
-
if not hex_pattern.match(color):
|
|
356
|
-
raise ValueError(f"Invalid hex color code: {color}")
|
|
357
|
-
validated.append(color.upper())
|
|
358
|
-
|
|
359
|
-
# Optionally register the palette
|
|
360
|
-
PALETTES[name] = validated
|
|
361
|
-
|
|
362
|
-
return validated
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
def simulate_colorblindness(
|
|
366
|
-
color: str,
|
|
367
|
-
*,
|
|
368
|
-
deficiency: Literal["protanopia", "deuteranopia", "tritanopia"] = "deuteranopia",
|
|
369
|
-
) -> str:
|
|
370
|
-
"""Simulate how a color appears with color vision deficiency.
|
|
371
|
-
|
|
372
|
-
: Test with color blindness simulators.
|
|
373
|
-
Converts a color to approximate how it appears with CVD.
|
|
374
|
-
|
|
375
|
-
Args:
|
|
376
|
-
color: Hex color code
|
|
377
|
-
deficiency: Type of color vision deficiency:
|
|
378
|
-
- "protanopia": Red-blind (1% of males)
|
|
379
|
-
- "deuteranopia": Green-blind (1% of males)
|
|
380
|
-
- "tritanopia": Blue-blind (rare)
|
|
381
|
-
|
|
382
|
-
Returns:
|
|
383
|
-
Simulated hex color code
|
|
384
|
-
|
|
385
|
-
Raises:
|
|
386
|
-
ValueError: If unknown deficiency type.
|
|
387
|
-
|
|
388
|
-
Example:
|
|
389
|
-
>>> from oscura.visualization.palettes import simulate_colorblindness
|
|
390
|
-
>>> red = "#FF0000"
|
|
391
|
-
>>> simulated = simulate_colorblindness(red, deficiency="deuteranopia")
|
|
392
|
-
>>> print(simulated) # Appears brownish
|
|
393
|
-
#9C7A00
|
|
394
|
-
|
|
395
|
-
References:
|
|
396
|
-
ACC-001: Test with color blindness simulators
|
|
397
|
-
Brettel, H., Viénot, F., & Mollon, J. D. (1997). Computerized simulation
|
|
398
|
-
of color appearance for dichromats. JOSA A, 14(10), 2647-2655.
|
|
399
|
-
"""
|
|
400
|
-
# Convert hex to RGB
|
|
401
|
-
hex_color = color.lstrip("#")
|
|
402
|
-
r, g, b = int(hex_color[0:2], 16), int(hex_color[2:4], 16), int(hex_color[4:6], 16)
|
|
403
|
-
|
|
404
|
-
# Normalize to 0-1
|
|
405
|
-
r, g, b = r / 255.0, g / 255.0, b / 255.0 # type: ignore[assignment]
|
|
406
|
-
|
|
407
|
-
# Apply transformation matrices (simplified Brettel algorithm)
|
|
408
|
-
if deficiency == "protanopia":
|
|
409
|
-
# Red-blind: confuse red and green
|
|
410
|
-
r_sim = 0.56667 * r + 0.43333 * g
|
|
411
|
-
g_sim = 0.55833 * r + 0.44167 * g
|
|
412
|
-
b_sim = b
|
|
413
|
-
elif deficiency == "deuteranopia":
|
|
414
|
-
# Green-blind: confuse red and green
|
|
415
|
-
r_sim = 0.625 * r + 0.375 * g
|
|
416
|
-
g_sim = 0.7 * r + 0.3 * g
|
|
417
|
-
b_sim = b
|
|
418
|
-
elif deficiency == "tritanopia":
|
|
419
|
-
# Blue-blind: confuse blue and yellow
|
|
420
|
-
r_sim = r
|
|
421
|
-
g_sim = 0.95 * g + 0.05 * b
|
|
422
|
-
b_sim = 0.433 * g + 0.567 * b # type: ignore[assignment]
|
|
423
|
-
else:
|
|
424
|
-
raise ValueError(f"Unknown deficiency: {deficiency}")
|
|
425
|
-
|
|
426
|
-
# Convert back to 0-255 and hex
|
|
427
|
-
r_int = int(np.clip(r_sim * 255, 0, 255))
|
|
428
|
-
g_int = int(np.clip(g_sim * 255, 0, 255))
|
|
429
|
-
b_int = int(np.clip(b_sim * 255, 0, 255))
|
|
430
|
-
|
|
431
|
-
return f"#{r_int:02X}{g_int:02X}{b_int:02X}"
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
__all__ = [
|
|
435
|
-
"LINE_STYLES",
|
|
436
|
-
"MARKER_STYLES",
|
|
437
|
-
"PALETTES",
|
|
438
|
-
"create_custom_palette",
|
|
439
|
-
"get_colormap",
|
|
440
|
-
"get_line_styles",
|
|
441
|
-
"get_palette",
|
|
442
|
-
"get_pass_fail_colors",
|
|
443
|
-
"get_pass_fail_symbols",
|
|
444
|
-
"show_palette",
|
|
445
|
-
"simulate_colorblindness",
|
|
446
|
-
]
|