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.
Files changed (175) hide show
  1. oscura/__init__.py +19 -19
  2. oscura/analyzers/__init__.py +2 -0
  3. oscura/analyzers/digital/extraction.py +2 -3
  4. oscura/analyzers/digital/quality.py +1 -1
  5. oscura/analyzers/digital/timing.py +1 -1
  6. oscura/analyzers/eye/__init__.py +5 -1
  7. oscura/analyzers/eye/generation.py +501 -0
  8. oscura/analyzers/jitter/__init__.py +6 -6
  9. oscura/analyzers/jitter/timing.py +419 -0
  10. oscura/analyzers/patterns/__init__.py +94 -0
  11. oscura/analyzers/patterns/reverse_engineering.py +991 -0
  12. oscura/analyzers/power/__init__.py +35 -12
  13. oscura/analyzers/power/basic.py +3 -3
  14. oscura/analyzers/power/soa.py +1 -1
  15. oscura/analyzers/power/switching.py +3 -3
  16. oscura/analyzers/signal_classification.py +529 -0
  17. oscura/analyzers/signal_integrity/sparams.py +3 -3
  18. oscura/analyzers/statistics/__init__.py +4 -0
  19. oscura/analyzers/statistics/basic.py +152 -0
  20. oscura/analyzers/statistics/correlation.py +47 -6
  21. oscura/analyzers/validation.py +1 -1
  22. oscura/analyzers/waveform/__init__.py +2 -0
  23. oscura/analyzers/waveform/measurements.py +329 -163
  24. oscura/analyzers/waveform/measurements_with_uncertainty.py +91 -35
  25. oscura/analyzers/waveform/spectral.py +498 -54
  26. oscura/api/dsl/commands.py +15 -6
  27. oscura/api/server/templates/base.html +137 -146
  28. oscura/api/server/templates/export.html +84 -110
  29. oscura/api/server/templates/home.html +248 -267
  30. oscura/api/server/templates/protocols.html +44 -48
  31. oscura/api/server/templates/reports.html +27 -35
  32. oscura/api/server/templates/session_detail.html +68 -78
  33. oscura/api/server/templates/sessions.html +62 -72
  34. oscura/api/server/templates/waveforms.html +54 -64
  35. oscura/automotive/__init__.py +1 -1
  36. oscura/automotive/can/session.py +1 -1
  37. oscura/automotive/dbc/generator.py +638 -23
  38. oscura/automotive/dtc/data.json +102 -17
  39. oscura/automotive/uds/decoder.py +99 -6
  40. oscura/cli/analyze.py +8 -2
  41. oscura/cli/batch.py +36 -5
  42. oscura/cli/characterize.py +18 -4
  43. oscura/cli/export.py +47 -5
  44. oscura/cli/main.py +2 -0
  45. oscura/cli/onboarding/wizard.py +10 -6
  46. oscura/cli/pipeline.py +585 -0
  47. oscura/cli/visualize.py +6 -4
  48. oscura/convenience.py +400 -32
  49. oscura/core/config/loader.py +0 -1
  50. oscura/core/measurement_result.py +286 -0
  51. oscura/core/progress.py +1 -1
  52. oscura/core/schemas/device_mapping.json +8 -2
  53. oscura/core/schemas/packet_format.json +24 -4
  54. oscura/core/schemas/protocol_definition.json +12 -2
  55. oscura/core/types.py +300 -199
  56. oscura/correlation/multi_protocol.py +1 -1
  57. oscura/export/legacy/__init__.py +11 -0
  58. oscura/export/legacy/wav.py +75 -0
  59. oscura/exporters/__init__.py +19 -0
  60. oscura/exporters/wireshark.py +809 -0
  61. oscura/hardware/acquisition/file.py +5 -19
  62. oscura/hardware/acquisition/saleae.py +10 -10
  63. oscura/hardware/acquisition/socketcan.py +4 -6
  64. oscura/hardware/acquisition/synthetic.py +1 -5
  65. oscura/hardware/acquisition/visa.py +6 -6
  66. oscura/hardware/security/side_channel_detector.py +5 -508
  67. oscura/inference/message_format.py +686 -1
  68. oscura/jupyter/display.py +2 -2
  69. oscura/jupyter/magic.py +3 -3
  70. oscura/loaders/__init__.py +17 -12
  71. oscura/loaders/binary.py +1 -1
  72. oscura/loaders/chipwhisperer.py +1 -2
  73. oscura/loaders/configurable.py +1 -1
  74. oscura/loaders/csv_loader.py +2 -2
  75. oscura/loaders/hdf5_loader.py +1 -1
  76. oscura/loaders/lazy.py +6 -1
  77. oscura/loaders/mmap_loader.py +0 -1
  78. oscura/loaders/numpy_loader.py +8 -7
  79. oscura/loaders/preprocessing.py +3 -5
  80. oscura/loaders/rigol.py +21 -7
  81. oscura/loaders/sigrok.py +2 -5
  82. oscura/loaders/tdms.py +3 -2
  83. oscura/loaders/tektronix.py +38 -32
  84. oscura/loaders/tss.py +20 -27
  85. oscura/loaders/vcd.py +13 -8
  86. oscura/loaders/wav.py +1 -6
  87. oscura/pipeline/__init__.py +76 -0
  88. oscura/pipeline/handlers/__init__.py +165 -0
  89. oscura/pipeline/handlers/analyzers.py +1045 -0
  90. oscura/pipeline/handlers/decoders.py +899 -0
  91. oscura/pipeline/handlers/exporters.py +1103 -0
  92. oscura/pipeline/handlers/filters.py +891 -0
  93. oscura/pipeline/handlers/loaders.py +640 -0
  94. oscura/pipeline/handlers/transforms.py +768 -0
  95. oscura/reporting/__init__.py +88 -1
  96. oscura/reporting/automation.py +348 -0
  97. oscura/reporting/citations.py +374 -0
  98. oscura/reporting/core.py +54 -0
  99. oscura/reporting/formatting/__init__.py +11 -0
  100. oscura/reporting/formatting/measurements.py +320 -0
  101. oscura/reporting/html.py +57 -0
  102. oscura/reporting/interpretation.py +431 -0
  103. oscura/reporting/summary.py +329 -0
  104. oscura/reporting/templates/enhanced/protocol_re.html +504 -503
  105. oscura/reporting/visualization.py +542 -0
  106. oscura/side_channel/__init__.py +38 -57
  107. oscura/utils/builders/signal_builder.py +5 -5
  108. oscura/utils/comparison/compare.py +7 -9
  109. oscura/utils/comparison/golden.py +1 -1
  110. oscura/utils/filtering/convenience.py +2 -2
  111. oscura/utils/math/arithmetic.py +38 -62
  112. oscura/utils/math/interpolation.py +20 -20
  113. oscura/utils/pipeline/__init__.py +4 -17
  114. oscura/utils/progressive.py +1 -4
  115. oscura/utils/triggering/edge.py +1 -1
  116. oscura/utils/triggering/pattern.py +2 -2
  117. oscura/utils/triggering/pulse.py +2 -2
  118. oscura/utils/triggering/window.py +3 -3
  119. oscura/validation/hil_testing.py +11 -11
  120. oscura/visualization/__init__.py +47 -284
  121. oscura/visualization/batch.py +160 -0
  122. oscura/visualization/plot.py +542 -53
  123. oscura/visualization/styles.py +184 -318
  124. oscura/workflows/__init__.py +2 -0
  125. oscura/workflows/batch/advanced.py +1 -1
  126. oscura/workflows/batch/aggregate.py +7 -8
  127. oscura/workflows/complete_re.py +251 -23
  128. oscura/workflows/digital.py +27 -4
  129. oscura/workflows/multi_trace.py +136 -17
  130. oscura/workflows/waveform.py +788 -0
  131. {oscura-0.7.0.dist-info → oscura-0.10.0.dist-info}/METADATA +59 -79
  132. {oscura-0.7.0.dist-info → oscura-0.10.0.dist-info}/RECORD +135 -149
  133. oscura/side_channel/dpa.py +0 -1025
  134. oscura/utils/optimization/__init__.py +0 -19
  135. oscura/utils/optimization/parallel.py +0 -443
  136. oscura/utils/optimization/search.py +0 -532
  137. oscura/utils/pipeline/base.py +0 -338
  138. oscura/utils/pipeline/composition.py +0 -248
  139. oscura/utils/pipeline/parallel.py +0 -449
  140. oscura/utils/pipeline/pipeline.py +0 -375
  141. oscura/utils/search/__init__.py +0 -16
  142. oscura/utils/search/anomaly.py +0 -424
  143. oscura/utils/search/context.py +0 -294
  144. oscura/utils/search/pattern.py +0 -288
  145. oscura/utils/storage/__init__.py +0 -61
  146. oscura/utils/storage/database.py +0 -1166
  147. oscura/visualization/accessibility.py +0 -526
  148. oscura/visualization/annotations.py +0 -371
  149. oscura/visualization/axis_scaling.py +0 -305
  150. oscura/visualization/colors.py +0 -451
  151. oscura/visualization/digital.py +0 -436
  152. oscura/visualization/eye.py +0 -571
  153. oscura/visualization/histogram.py +0 -281
  154. oscura/visualization/interactive.py +0 -1035
  155. oscura/visualization/jitter.py +0 -1042
  156. oscura/visualization/keyboard.py +0 -394
  157. oscura/visualization/layout.py +0 -400
  158. oscura/visualization/optimization.py +0 -1079
  159. oscura/visualization/palettes.py +0 -446
  160. oscura/visualization/power.py +0 -508
  161. oscura/visualization/power_extended.py +0 -955
  162. oscura/visualization/presets.py +0 -469
  163. oscura/visualization/protocols.py +0 -1246
  164. oscura/visualization/render.py +0 -223
  165. oscura/visualization/rendering.py +0 -444
  166. oscura/visualization/reverse_engineering.py +0 -838
  167. oscura/visualization/signal_integrity.py +0 -989
  168. oscura/visualization/specialized.py +0 -643
  169. oscura/visualization/spectral.py +0 -1226
  170. oscura/visualization/thumbnails.py +0 -340
  171. oscura/visualization/time_axis.py +0 -351
  172. oscura/visualization/waveform.py +0 -454
  173. {oscura-0.7.0.dist-info → oscura-0.10.0.dist-info}/WHEEL +0 -0
  174. {oscura-0.7.0.dist-info → oscura-0.10.0.dist-info}/entry_points.txt +0 -0
  175. {oscura-0.7.0.dist-info → oscura-0.10.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
- ]