oscura 0.8.0__py3-none-any.whl → 0.11.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.
Files changed (161) hide show
  1. oscura/__init__.py +19 -19
  2. oscura/__main__.py +4 -0
  3. oscura/analyzers/__init__.py +2 -0
  4. oscura/analyzers/digital/extraction.py +2 -3
  5. oscura/analyzers/digital/quality.py +1 -1
  6. oscura/analyzers/digital/timing.py +1 -1
  7. oscura/analyzers/ml/signal_classifier.py +6 -0
  8. oscura/analyzers/patterns/__init__.py +66 -0
  9. oscura/analyzers/power/basic.py +3 -3
  10. oscura/analyzers/power/soa.py +1 -1
  11. oscura/analyzers/power/switching.py +3 -3
  12. oscura/analyzers/signal_classification.py +529 -0
  13. oscura/analyzers/signal_integrity/sparams.py +3 -3
  14. oscura/analyzers/statistics/basic.py +10 -7
  15. oscura/analyzers/validation.py +1 -1
  16. oscura/analyzers/waveform/measurements.py +200 -156
  17. oscura/analyzers/waveform/measurements_with_uncertainty.py +91 -35
  18. oscura/analyzers/waveform/spectral.py +182 -84
  19. oscura/api/dsl/commands.py +15 -6
  20. oscura/api/server/templates/base.html +137 -146
  21. oscura/api/server/templates/export.html +84 -110
  22. oscura/api/server/templates/home.html +248 -267
  23. oscura/api/server/templates/protocols.html +44 -48
  24. oscura/api/server/templates/reports.html +27 -35
  25. oscura/api/server/templates/session_detail.html +68 -78
  26. oscura/api/server/templates/sessions.html +62 -72
  27. oscura/api/server/templates/waveforms.html +54 -64
  28. oscura/automotive/__init__.py +1 -1
  29. oscura/automotive/can/session.py +1 -1
  30. oscura/automotive/dbc/generator.py +638 -23
  31. oscura/automotive/dtc/data.json +17 -102
  32. oscura/automotive/flexray/fibex.py +9 -1
  33. oscura/automotive/uds/decoder.py +99 -6
  34. oscura/cli/analyze.py +8 -2
  35. oscura/cli/batch.py +36 -5
  36. oscura/cli/characterize.py +18 -4
  37. oscura/cli/export.py +47 -5
  38. oscura/cli/main.py +2 -0
  39. oscura/cli/onboarding/wizard.py +10 -6
  40. oscura/cli/pipeline.py +585 -0
  41. oscura/cli/visualize.py +6 -4
  42. oscura/convenience.py +400 -32
  43. oscura/core/measurement_result.py +286 -0
  44. oscura/core/progress.py +1 -1
  45. oscura/core/schemas/device_mapping.json +2 -8
  46. oscura/core/schemas/packet_format.json +4 -24
  47. oscura/core/schemas/protocol_definition.json +2 -12
  48. oscura/core/types.py +232 -239
  49. oscura/correlation/multi_protocol.py +1 -1
  50. oscura/export/legacy/__init__.py +11 -0
  51. oscura/export/legacy/wav.py +75 -0
  52. oscura/exporters/__init__.py +19 -0
  53. oscura/exporters/wireshark.py +809 -0
  54. oscura/hardware/acquisition/file.py +5 -19
  55. oscura/hardware/acquisition/saleae.py +10 -10
  56. oscura/hardware/acquisition/socketcan.py +4 -6
  57. oscura/hardware/acquisition/synthetic.py +1 -5
  58. oscura/hardware/acquisition/visa.py +6 -6
  59. oscura/hardware/security/side_channel_detector.py +5 -508
  60. oscura/inference/message_format.py +686 -1
  61. oscura/jupyter/display.py +2 -2
  62. oscura/jupyter/magic.py +3 -3
  63. oscura/loaders/__init__.py +17 -12
  64. oscura/loaders/binary.py +1 -1
  65. oscura/loaders/chipwhisperer.py +1 -2
  66. oscura/loaders/configurable.py +1 -1
  67. oscura/loaders/csv_loader.py +2 -2
  68. oscura/loaders/hdf5_loader.py +1 -1
  69. oscura/loaders/lazy.py +6 -1
  70. oscura/loaders/mmap_loader.py +0 -1
  71. oscura/loaders/numpy_loader.py +8 -7
  72. oscura/loaders/preprocessing.py +3 -5
  73. oscura/loaders/rigol.py +21 -7
  74. oscura/loaders/sigrok.py +2 -5
  75. oscura/loaders/tdms.py +3 -2
  76. oscura/loaders/tektronix.py +38 -32
  77. oscura/loaders/tss.py +20 -27
  78. oscura/loaders/validation.py +17 -10
  79. oscura/loaders/vcd.py +13 -8
  80. oscura/loaders/wav.py +1 -6
  81. oscura/pipeline/__init__.py +76 -0
  82. oscura/pipeline/handlers/__init__.py +165 -0
  83. oscura/pipeline/handlers/analyzers.py +1045 -0
  84. oscura/pipeline/handlers/decoders.py +899 -0
  85. oscura/pipeline/handlers/exporters.py +1103 -0
  86. oscura/pipeline/handlers/filters.py +891 -0
  87. oscura/pipeline/handlers/loaders.py +640 -0
  88. oscura/pipeline/handlers/transforms.py +768 -0
  89. oscura/reporting/formatting/measurements.py +55 -14
  90. oscura/reporting/templates/enhanced/protocol_re.html +504 -503
  91. oscura/sessions/legacy.py +49 -1
  92. oscura/side_channel/__init__.py +38 -57
  93. oscura/utils/builders/signal_builder.py +5 -5
  94. oscura/utils/comparison/compare.py +7 -9
  95. oscura/utils/comparison/golden.py +1 -1
  96. oscura/utils/filtering/convenience.py +2 -2
  97. oscura/utils/math/arithmetic.py +38 -62
  98. oscura/utils/math/interpolation.py +20 -20
  99. oscura/utils/pipeline/__init__.py +4 -17
  100. oscura/utils/progressive.py +1 -4
  101. oscura/utils/triggering/edge.py +1 -1
  102. oscura/utils/triggering/pattern.py +2 -2
  103. oscura/utils/triggering/pulse.py +2 -2
  104. oscura/utils/triggering/window.py +3 -3
  105. oscura/validation/hil_testing.py +11 -11
  106. oscura/visualization/__init__.py +46 -284
  107. oscura/visualization/batch.py +72 -433
  108. oscura/visualization/plot.py +542 -53
  109. oscura/visualization/styles.py +184 -318
  110. oscura/workflows/batch/advanced.py +1 -1
  111. oscura/workflows/batch/aggregate.py +12 -9
  112. oscura/workflows/complete_re.py +251 -23
  113. oscura/workflows/digital.py +27 -4
  114. oscura/workflows/multi_trace.py +136 -17
  115. oscura/workflows/waveform.py +11 -6
  116. oscura-0.11.0.dist-info/METADATA +460 -0
  117. {oscura-0.8.0.dist-info → oscura-0.11.0.dist-info}/RECORD +120 -145
  118. oscura/side_channel/dpa.py +0 -1025
  119. oscura/utils/optimization/__init__.py +0 -19
  120. oscura/utils/optimization/parallel.py +0 -443
  121. oscura/utils/optimization/search.py +0 -532
  122. oscura/utils/pipeline/base.py +0 -338
  123. oscura/utils/pipeline/composition.py +0 -248
  124. oscura/utils/pipeline/parallel.py +0 -449
  125. oscura/utils/pipeline/pipeline.py +0 -375
  126. oscura/utils/search/__init__.py +0 -16
  127. oscura/utils/search/anomaly.py +0 -424
  128. oscura/utils/search/context.py +0 -294
  129. oscura/utils/search/pattern.py +0 -288
  130. oscura/utils/storage/__init__.py +0 -61
  131. oscura/utils/storage/database.py +0 -1166
  132. oscura/visualization/accessibility.py +0 -526
  133. oscura/visualization/annotations.py +0 -371
  134. oscura/visualization/axis_scaling.py +0 -305
  135. oscura/visualization/colors.py +0 -451
  136. oscura/visualization/digital.py +0 -436
  137. oscura/visualization/eye.py +0 -571
  138. oscura/visualization/histogram.py +0 -281
  139. oscura/visualization/interactive.py +0 -1035
  140. oscura/visualization/jitter.py +0 -1042
  141. oscura/visualization/keyboard.py +0 -394
  142. oscura/visualization/layout.py +0 -400
  143. oscura/visualization/optimization.py +0 -1079
  144. oscura/visualization/palettes.py +0 -446
  145. oscura/visualization/power.py +0 -508
  146. oscura/visualization/power_extended.py +0 -955
  147. oscura/visualization/presets.py +0 -469
  148. oscura/visualization/protocols.py +0 -1246
  149. oscura/visualization/render.py +0 -223
  150. oscura/visualization/rendering.py +0 -444
  151. oscura/visualization/reverse_engineering.py +0 -838
  152. oscura/visualization/signal_integrity.py +0 -989
  153. oscura/visualization/specialized.py +0 -643
  154. oscura/visualization/spectral.py +0 -1226
  155. oscura/visualization/thumbnails.py +0 -340
  156. oscura/visualization/time_axis.py +0 -351
  157. oscura/visualization/waveform.py +0 -454
  158. oscura-0.8.0.dist-info/METADATA +0 -661
  159. {oscura-0.8.0.dist-info → oscura-0.11.0.dist-info}/WHEEL +0 -0
  160. {oscura-0.8.0.dist-info → oscura-0.11.0.dist-info}/entry_points.txt +0 -0
  161. {oscura-0.8.0.dist-info → oscura-0.11.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,469 +0,0 @@
1
- """Comprehensive visualization presets combining styles, colors, and rendering.
2
-
3
- This module provides integrated presets that combine style settings, color
4
- palettes, and rendering configuration for different use cases.
5
-
6
-
7
- Example:
8
- >>> from oscura.visualization.presets import apply_preset
9
- >>> with apply_preset("ieee_publication"):
10
- ... plot_waveform(signal)
11
-
12
- References:
13
- - IEEE publication standards
14
- - Presentation best practices
15
- - Colorblind-safe palette design
16
- """
17
-
18
- from __future__ import annotations
19
-
20
- from contextlib import contextmanager
21
- from dataclasses import dataclass
22
- from typing import TYPE_CHECKING, Any
23
-
24
- if TYPE_CHECKING:
25
- from collections.abc import Iterator
26
-
27
- try:
28
- import matplotlib.pyplot as plt
29
-
30
- HAS_MATPLOTLIB = True
31
- except ImportError:
32
- HAS_MATPLOTLIB = False
33
-
34
-
35
- @dataclass
36
- class VisualizationPreset:
37
- """Complete visualization preset configuration.
38
-
39
- Attributes:
40
- name: Preset name
41
- description: Preset description
42
- style_params: Matplotlib rcParams
43
- color_palette: List of colors for multi-channel plots
44
- dpi: Target DPI
45
- figure_size: Default figure size (width, height) in inches
46
- font_family: Font family
47
- colorblind_safe: Whether palette is colorblind-safe
48
- print_optimized: Whether optimized for print output
49
- """
50
-
51
- name: str
52
- description: str
53
- style_params: dict[str, Any]
54
- color_palette: list[str]
55
- dpi: int = 96
56
- figure_size: tuple[float, float] = (10, 6)
57
- font_family: str = "sans-serif"
58
- colorblind_safe: bool = True
59
- print_optimized: bool = False
60
-
61
-
62
- # IEEE Publication Preset (VIS-020)
63
- IEEE_PUBLICATION_PRESET = VisualizationPreset(
64
- name="ieee_publication",
65
- description="IEEE publication quality (single-column, grayscale-friendly)",
66
- dpi=600,
67
- figure_size=(3.5, 2.5), # IEEE single-column width
68
- font_family="serif",
69
- colorblind_safe=True,
70
- print_optimized=True,
71
- color_palette=[
72
- "#000000", # Black
73
- "#555555", # Dark gray
74
- "#AAAAAA", # Light gray
75
- "#0173B2", # Blue (grayscale-safe)
76
- "#DE8F05", # Orange (grayscale-safe)
77
- "#029E73", # Green (grayscale-safe)
78
- ],
79
- style_params={
80
- "figure.dpi": 600,
81
- "savefig.dpi": 600,
82
- "savefig.format": "pdf",
83
- "savefig.bbox": "tight",
84
- "font.family": "serif",
85
- "font.size": 8,
86
- "axes.titlesize": 9,
87
- "axes.labelsize": 8,
88
- "xtick.labelsize": 7,
89
- "ytick.labelsize": 7,
90
- "legend.fontsize": 7,
91
- "lines.linewidth": 0.8,
92
- "lines.markersize": 3.0,
93
- "axes.linewidth": 0.6,
94
- "grid.linewidth": 0.4,
95
- "grid.alpha": 0.3,
96
- "grid.linestyle": ":",
97
- "axes.grid": True,
98
- "axes.axisbelow": True,
99
- "xtick.major.width": 0.6,
100
- "ytick.major.width": 0.6,
101
- "xtick.minor.width": 0.4,
102
- "ytick.minor.width": 0.4,
103
- "lines.antialiased": False, # Sharper for print
104
- "patch.antialiased": False,
105
- "mathtext.fontset": "cm", # Computer Modern (LaTeX-like)
106
- },
107
- )
108
-
109
- # IEEE Double-Column Preset
110
- IEEE_DOUBLE_COLUMN_PRESET = VisualizationPreset(
111
- name="ieee_double_column",
112
- description="IEEE publication quality (double-column width)",
113
- dpi=600,
114
- figure_size=(7.0, 2.5), # IEEE double-column width
115
- font_family="serif",
116
- colorblind_safe=True,
117
- print_optimized=True,
118
- color_palette=IEEE_PUBLICATION_PRESET.color_palette,
119
- style_params=IEEE_PUBLICATION_PRESET.style_params.copy(),
120
- )
121
-
122
- # Presentation Preset
123
- PRESENTATION_PRESET = VisualizationPreset(
124
- name="presentation",
125
- description="Presentation slides (high contrast, large fonts, bold lines)",
126
- dpi=96,
127
- figure_size=(12, 7),
128
- font_family="sans-serif",
129
- colorblind_safe=True,
130
- print_optimized=False,
131
- color_palette=[
132
- "#0173B2", # Blue
133
- "#DE8F05", # Orange
134
- "#029E73", # Green
135
- "#CC78BC", # Purple
136
- "#CA9161", # Brown
137
- "#ECE133", # Yellow
138
- ],
139
- style_params={
140
- "figure.dpi": 96,
141
- "font.family": "sans-serif",
142
- "font.size": 18,
143
- "axes.titlesize": 22,
144
- "axes.labelsize": 20,
145
- "xtick.labelsize": 16,
146
- "ytick.labelsize": 16,
147
- "legend.fontsize": 16,
148
- "lines.linewidth": 3.0,
149
- "lines.markersize": 10.0,
150
- "axes.linewidth": 2.0,
151
- "grid.linewidth": 1.0,
152
- "grid.alpha": 0.4,
153
- "axes.grid": True,
154
- "xtick.major.width": 2.0,
155
- "ytick.major.width": 2.0,
156
- "xtick.major.size": 8,
157
- "ytick.major.size": 8,
158
- "lines.antialiased": True,
159
- "patch.antialiased": True,
160
- },
161
- )
162
-
163
- # Screen/Interactive Preset
164
- SCREEN_PRESET = VisualizationPreset(
165
- name="screen",
166
- description="Screen viewing (vibrant colors, medium fonts, anti-aliased)",
167
- dpi=96,
168
- figure_size=(10, 6),
169
- font_family="sans-serif",
170
- colorblind_safe=True,
171
- print_optimized=False,
172
- color_palette=[
173
- "#1F77B4", # Blue
174
- "#FF7F0E", # Orange
175
- "#2CA02C", # Green
176
- "#D62728", # Red
177
- "#9467BD", # Purple
178
- "#8C564B", # Brown
179
- "#E377C2", # Pink
180
- "#7F7F7F", # Gray
181
- ],
182
- style_params={
183
- "figure.dpi": 96,
184
- "font.family": "sans-serif",
185
- "font.size": 10,
186
- "axes.titlesize": 12,
187
- "axes.labelsize": 10,
188
- "xtick.labelsize": 9,
189
- "ytick.labelsize": 9,
190
- "legend.fontsize": 9,
191
- "lines.linewidth": 1.5,
192
- "lines.markersize": 6.0,
193
- "axes.linewidth": 1.0,
194
- "grid.linewidth": 0.6,
195
- "grid.alpha": 0.3,
196
- "axes.grid": True,
197
- "lines.antialiased": True,
198
- "patch.antialiased": True,
199
- },
200
- )
201
-
202
- # Print Preset
203
- PRINT_PRESET = VisualizationPreset(
204
- name="print",
205
- description="Print output (300 DPI, CMYK-safe colors, optimized file size)",
206
- dpi=300,
207
- figure_size=(8, 5),
208
- font_family="serif",
209
- colorblind_safe=True,
210
- print_optimized=True,
211
- color_palette=[
212
- "#0173B2", # Blue (CMYK-safe)
213
- "#DE8F05", # Orange (CMYK-safe)
214
- "#029E73", # Green (CMYK-safe)
215
- "#CC78BC", # Purple (CMYK-safe)
216
- "#555555", # Gray (CMYK-safe)
217
- ],
218
- style_params={
219
- "figure.dpi": 300,
220
- "savefig.dpi": 300,
221
- "savefig.format": "pdf",
222
- "font.family": "serif",
223
- "font.size": 11,
224
- "axes.titlesize": 13,
225
- "axes.labelsize": 11,
226
- "xtick.labelsize": 10,
227
- "ytick.labelsize": 10,
228
- "legend.fontsize": 10,
229
- "lines.linewidth": 1.2,
230
- "lines.markersize": 5.0,
231
- "axes.linewidth": 1.0,
232
- "grid.linewidth": 0.6,
233
- "grid.alpha": 0.3,
234
- "axes.grid": True,
235
- "lines.antialiased": False, # Cleaner for print
236
- "patch.antialiased": False,
237
- },
238
- )
239
-
240
- # Dark Theme Preset
241
- DARK_THEME_PRESET = VisualizationPreset(
242
- name="dark",
243
- description="Dark theme (dark background, high-contrast colors)",
244
- dpi=96,
245
- figure_size=(10, 6),
246
- font_family="sans-serif",
247
- colorblind_safe=True,
248
- print_optimized=False,
249
- color_palette=[
250
- "#56B4E9", # Light blue
251
- "#E69F00", # Orange
252
- "#009E73", # Green
253
- "#F0E442", # Yellow
254
- "#CC79A7", # Pink
255
- "#0072B2", # Blue
256
- ],
257
- style_params={
258
- "figure.dpi": 96,
259
- "figure.facecolor": "#1E1E1E",
260
- "axes.facecolor": "#2D2D2D",
261
- "axes.edgecolor": "#CCCCCC",
262
- "axes.labelcolor": "#CCCCCC",
263
- "text.color": "#CCCCCC",
264
- "xtick.color": "#CCCCCC",
265
- "ytick.color": "#CCCCCC",
266
- "grid.color": "#555555",
267
- "grid.alpha": 0.5,
268
- "font.family": "sans-serif",
269
- "font.size": 10,
270
- "lines.linewidth": 1.5,
271
- "axes.grid": True,
272
- "lines.antialiased": True,
273
- },
274
- )
275
-
276
- # Preset registry
277
- PRESETS: dict[str, VisualizationPreset] = {
278
- "ieee_publication": IEEE_PUBLICATION_PRESET,
279
- "ieee_double_column": IEEE_DOUBLE_COLUMN_PRESET,
280
- "presentation": PRESENTATION_PRESET,
281
- "screen": SCREEN_PRESET,
282
- "print": PRINT_PRESET,
283
- "dark": DARK_THEME_PRESET,
284
- }
285
-
286
-
287
- @contextmanager
288
- def apply_preset(
289
- preset: str | VisualizationPreset,
290
- *,
291
- overrides: dict[str, Any] | None = None,
292
- ) -> Iterator[VisualizationPreset]:
293
- """Apply visualization preset as context manager./VIS-024.
294
-
295
- Combines style settings, color palette, and rendering configuration.
296
-
297
- Args:
298
- preset: Preset name or VisualizationPreset object.
299
- overrides: Dictionary of rcParams to override.
300
-
301
- Yields:
302
- VisualizationPreset object for access to color palette.
303
-
304
- Raises:
305
- ValueError: If preset name is unknown.
306
- ImportError: If matplotlib is not available.
307
-
308
- Example:
309
- >>> with apply_preset("ieee_publication") as preset:
310
- ... fig, ax = plt.subplots(figsize=preset.figure_size)
311
- ... ax.plot(x, y, color=preset.color_palette[0])
312
- ... plt.savefig("figure.pdf")
313
-
314
- >>> # With custom overrides
315
- >>> with apply_preset("screen", overrides={"font.size": 14}):
316
- ... plot_waveform(signal)
317
-
318
- References:
319
- VIS-020: IEEE Publication Style Preset
320
- VIS-024: Plot Style Presets
321
- """
322
- if not HAS_MATPLOTLIB:
323
- raise ImportError("matplotlib is required for visualization presets")
324
-
325
- # Get preset object
326
- if isinstance(preset, str):
327
- if preset not in PRESETS:
328
- raise ValueError(f"Unknown preset: {preset}. Available: {list(PRESETS.keys())}")
329
- preset_obj = PRESETS[preset]
330
- else:
331
- preset_obj = preset
332
-
333
- # Build rcParams dictionary
334
- # NECESSARY COPY: rc_dict is modified by .update() below.
335
- # Copy prevents mutations from affecting original preset.
336
- rc_dict = preset_obj.style_params.copy()
337
-
338
- # Apply overrides
339
- if overrides:
340
- rc_dict.update(overrides)
341
-
342
- # Apply as context
343
- with plt.rc_context(rc_dict):
344
- yield preset_obj
345
-
346
-
347
- def get_preset_colors(
348
- preset: str | VisualizationPreset,
349
- n_colors: int | None = None,
350
- ) -> list[str]:
351
- """Get color palette from preset.
352
-
353
- Args:
354
- preset: Preset name or object.
355
- n_colors: Number of colors to return (None = all).
356
-
357
- Returns:
358
- List of color hex codes.
359
-
360
- Raises:
361
- ValueError: If unknown preset name.
362
-
363
- Example:
364
- >>> colors = get_preset_colors("ieee_publication", n_colors=3)
365
- >>> # Use colors for multi-channel plot
366
-
367
- References:
368
- VIS-023: Data-Driven Color Palette
369
- VIS-024: Plot Style Presets
370
- """
371
- if isinstance(preset, str):
372
- if preset not in PRESETS:
373
- raise ValueError(f"Unknown preset: {preset}")
374
- preset_obj = PRESETS[preset]
375
- else:
376
- preset_obj = preset
377
-
378
- colors = preset_obj.color_palette
379
-
380
- if n_colors is not None:
381
- if n_colors <= len(colors):
382
- return colors[:n_colors]
383
- else:
384
- # Cycle colors if more needed
385
- return [colors[i % len(colors)] for i in range(n_colors)]
386
-
387
- return colors
388
-
389
-
390
- def list_presets() -> list[str]:
391
- """Get list of available preset names.
392
-
393
- Returns:
394
- List of preset names.
395
-
396
- Example:
397
- >>> presets = list_presets()
398
- >>> print(presets)
399
- ['ieee_publication', 'presentation', 'screen', 'print', 'dark']
400
- """
401
- return list(PRESETS.keys())
402
-
403
-
404
- def create_custom_preset(
405
- name: str,
406
- base_preset: str = "screen",
407
- **kwargs: Any,
408
- ) -> VisualizationPreset:
409
- """Create custom preset by inheriting from base.
410
-
411
- Args:
412
- name: Name for custom preset.
413
- base_preset: Base preset to inherit from.
414
- **kwargs: Attributes to override.
415
-
416
- Returns:
417
- Custom VisualizationPreset object.
418
-
419
- Raises:
420
- ValueError: If base_preset is unknown.
421
-
422
- Example:
423
- >>> custom = create_custom_preset(
424
- ... "my_preset",
425
- ... base_preset="ieee_publication",
426
- ... figure_size=(5, 3),
427
- ... dpi=300,
428
- ... )
429
- >>> with apply_preset(custom):
430
- ... plot_data()
431
-
432
- References:
433
- VIS-024: Plot Style Presets (custom preset creation)
434
- """
435
- if base_preset not in PRESETS:
436
- raise ValueError(f"Unknown base_preset: {base_preset}")
437
-
438
- base = PRESETS[base_preset]
439
-
440
- # Create copy with overrides
441
- preset_dict = {
442
- "name": name,
443
- "description": kwargs.get("description", f"Custom preset based on {base_preset}"),
444
- "style_params": kwargs.get("style_params", base.style_params.copy()),
445
- "color_palette": kwargs.get("color_palette", base.color_palette.copy()),
446
- "dpi": kwargs.get("dpi", base.dpi),
447
- "figure_size": kwargs.get("figure_size", base.figure_size),
448
- "font_family": kwargs.get("font_family", base.font_family),
449
- "colorblind_safe": kwargs.get("colorblind_safe", base.colorblind_safe),
450
- "print_optimized": kwargs.get("print_optimized", base.print_optimized),
451
- }
452
-
453
- return VisualizationPreset(**preset_dict)
454
-
455
-
456
- __all__ = [
457
- "DARK_THEME_PRESET",
458
- "IEEE_DOUBLE_COLUMN_PRESET",
459
- "IEEE_PUBLICATION_PRESET",
460
- "PRESENTATION_PRESET",
461
- "PRESETS",
462
- "PRINT_PRESET",
463
- "SCREEN_PRESET",
464
- "VisualizationPreset",
465
- "apply_preset",
466
- "create_custom_preset",
467
- "get_preset_colors",
468
- "list_presets",
469
- ]