sigima 0.0.1.dev0__py3-none-any.whl → 1.0.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.
- sigima/__init__.py +142 -2
- sigima/client/__init__.py +105 -0
- sigima/client/base.py +780 -0
- sigima/client/remote.py +469 -0
- sigima/client/stub.py +814 -0
- sigima/client/utils.py +90 -0
- sigima/config.py +444 -0
- sigima/data/logo/Sigima.svg +135 -0
- sigima/data/tests/annotations.json +798 -0
- sigima/data/tests/curve_fitting/exponential_fit.txt +511 -0
- sigima/data/tests/curve_fitting/gaussian_fit.txt +100 -0
- sigima/data/tests/curve_fitting/piecewiseexponential_fit.txt +1022 -0
- sigima/data/tests/curve_fitting/polynomial_fit.txt +100 -0
- sigima/data/tests/curve_fitting/twohalfgaussian_fit.txt +1000 -0
- sigima/data/tests/curve_formats/bandwidth.txt +201 -0
- sigima/data/tests/curve_formats/boxcar.npy +0 -0
- sigima/data/tests/curve_formats/datetime.txt +1001 -0
- sigima/data/tests/curve_formats/dynamic_parameters.txt +4000 -0
- sigima/data/tests/curve_formats/fw1e2.txt +301 -0
- sigima/data/tests/curve_formats/fwhm.txt +319 -0
- sigima/data/tests/curve_formats/multiple_curves.csv +29 -0
- sigima/data/tests/curve_formats/noised_saw.mat +0 -0
- sigima/data/tests/curve_formats/oscilloscope.csv +111 -0
- sigima/data/tests/curve_formats/other/other2/recursive2.txt +5 -0
- sigima/data/tests/curve_formats/other/recursive1.txt +5 -0
- sigima/data/tests/curve_formats/paracetamol.npy +0 -0
- sigima/data/tests/curve_formats/paracetamol.txt +1010 -0
- sigima/data/tests/curve_formats/paracetamol_dx_dy.csv +1000 -0
- sigima/data/tests/curve_formats/paracetamol_dy.csv +1001 -0
- sigima/data/tests/curve_formats/pulse1.npy +0 -0
- sigima/data/tests/curve_formats/pulse2.npy +0 -0
- sigima/data/tests/curve_formats/simple.txt +5 -0
- sigima/data/tests/curve_formats/spectrum.mca +2139 -0
- sigima/data/tests/curve_formats/square2.npy +0 -0
- sigima/data/tests/curve_formats/step.npy +0 -0
- sigima/data/tests/fabry-perot1.jpg +0 -0
- sigima/data/tests/fabry-perot2.jpg +0 -0
- sigima/data/tests/flower.npy +0 -0
- sigima/data/tests/image_formats/NF 180338201.scor-data +11003 -0
- sigima/data/tests/image_formats/binary_image.npy +0 -0
- sigima/data/tests/image_formats/binary_image.png +0 -0
- sigima/data/tests/image_formats/centroid_test.npy +0 -0
- sigima/data/tests/image_formats/coordinated_text/complex_image.txt +10011 -0
- sigima/data/tests/image_formats/coordinated_text/complex_ref_image.txt +10010 -0
- sigima/data/tests/image_formats/coordinated_text/image.txt +15 -0
- sigima/data/tests/image_formats/coordinated_text/image2.txt +14 -0
- sigima/data/tests/image_formats/coordinated_text/image_no_unit_no_label.txt +14 -0
- sigima/data/tests/image_formats/coordinated_text/image_with_nan.txt +15 -0
- sigima/data/tests/image_formats/coordinated_text/image_with_unit.txt +14 -0
- sigima/data/tests/image_formats/fiber.csv +480 -0
- sigima/data/tests/image_formats/fiber.jpg +0 -0
- sigima/data/tests/image_formats/fiber.png +0 -0
- sigima/data/tests/image_formats/fiber.txt +480 -0
- sigima/data/tests/image_formats/gaussian_spot_with_noise.npy +0 -0
- sigima/data/tests/image_formats/mr-brain.dcm +0 -0
- sigima/data/tests/image_formats/noised_gaussian.mat +0 -0
- sigima/data/tests/image_formats/sif_reader/nd_lum_image_no_glue.sif +0 -0
- sigima/data/tests/image_formats/sif_reader/raman1.sif +0 -0
- sigima/data/tests/image_formats/tiling.txt +10 -0
- sigima/data/tests/image_formats/uint16.tiff +0 -0
- sigima/data/tests/image_formats/uint8.tiff +0 -0
- sigima/data/tests/laser_beam/TEM00_z_13.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_18.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_23.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_30.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_35.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_40.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_45.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_50.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_55.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_60.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_65.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_70.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_75.jpg +0 -0
- sigima/data/tests/laser_beam/TEM00_z_80.jpg +0 -0
- sigima/enums.py +195 -0
- sigima/io/__init__.py +123 -0
- sigima/io/base.py +311 -0
- sigima/io/common/__init__.py +5 -0
- sigima/io/common/basename.py +164 -0
- sigima/io/common/converters.py +189 -0
- sigima/io/common/objmeta.py +181 -0
- sigima/io/common/textreader.py +58 -0
- sigima/io/convenience.py +157 -0
- sigima/io/enums.py +17 -0
- sigima/io/ftlab.py +395 -0
- sigima/io/image/__init__.py +9 -0
- sigima/io/image/base.py +177 -0
- sigima/io/image/formats.py +1016 -0
- sigima/io/image/funcs.py +414 -0
- sigima/io/signal/__init__.py +9 -0
- sigima/io/signal/base.py +129 -0
- sigima/io/signal/formats.py +290 -0
- sigima/io/signal/funcs.py +723 -0
- sigima/objects/__init__.py +260 -0
- sigima/objects/base.py +937 -0
- sigima/objects/image/__init__.py +88 -0
- sigima/objects/image/creation.py +556 -0
- sigima/objects/image/object.py +524 -0
- sigima/objects/image/roi.py +904 -0
- sigima/objects/scalar/__init__.py +57 -0
- sigima/objects/scalar/common.py +215 -0
- sigima/objects/scalar/geometry.py +502 -0
- sigima/objects/scalar/table.py +784 -0
- sigima/objects/shape.py +290 -0
- sigima/objects/signal/__init__.py +133 -0
- sigima/objects/signal/constants.py +27 -0
- sigima/objects/signal/creation.py +1428 -0
- sigima/objects/signal/object.py +444 -0
- sigima/objects/signal/roi.py +274 -0
- sigima/params.py +405 -0
- sigima/proc/__init__.py +96 -0
- sigima/proc/base.py +381 -0
- sigima/proc/decorator.py +330 -0
- sigima/proc/image/__init__.py +513 -0
- sigima/proc/image/arithmetic.py +335 -0
- sigima/proc/image/base.py +260 -0
- sigima/proc/image/detection.py +519 -0
- sigima/proc/image/edges.py +329 -0
- sigima/proc/image/exposure.py +406 -0
- sigima/proc/image/extraction.py +458 -0
- sigima/proc/image/filtering.py +219 -0
- sigima/proc/image/fourier.py +147 -0
- sigima/proc/image/geometry.py +661 -0
- sigima/proc/image/mathops.py +340 -0
- sigima/proc/image/measurement.py +195 -0
- sigima/proc/image/morphology.py +155 -0
- sigima/proc/image/noise.py +107 -0
- sigima/proc/image/preprocessing.py +182 -0
- sigima/proc/image/restoration.py +235 -0
- sigima/proc/image/threshold.py +217 -0
- sigima/proc/image/transformations.py +393 -0
- sigima/proc/signal/__init__.py +376 -0
- sigima/proc/signal/analysis.py +206 -0
- sigima/proc/signal/arithmetic.py +551 -0
- sigima/proc/signal/base.py +262 -0
- sigima/proc/signal/extraction.py +60 -0
- sigima/proc/signal/features.py +310 -0
- sigima/proc/signal/filtering.py +484 -0
- sigima/proc/signal/fitting.py +276 -0
- sigima/proc/signal/fourier.py +259 -0
- sigima/proc/signal/mathops.py +420 -0
- sigima/proc/signal/processing.py +580 -0
- sigima/proc/signal/stability.py +175 -0
- sigima/proc/title_formatting.py +227 -0
- sigima/proc/validation.py +272 -0
- sigima/tests/__init__.py +7 -0
- sigima/tests/common/__init__.py +0 -0
- sigima/tests/common/arithmeticparam_unit_test.py +26 -0
- sigima/tests/common/basename_unit_test.py +126 -0
- sigima/tests/common/client_unit_test.py +412 -0
- sigima/tests/common/converters_unit_test.py +77 -0
- sigima/tests/common/decorator_unit_test.py +176 -0
- sigima/tests/common/examples_unit_test.py +104 -0
- sigima/tests/common/kernel_normalization_unit_test.py +242 -0
- sigima/tests/common/roi_basic_unit_test.py +73 -0
- sigima/tests/common/roi_geometry_unit_test.py +171 -0
- sigima/tests/common/scalar_builder_unit_test.py +142 -0
- sigima/tests/common/scalar_unit_test.py +991 -0
- sigima/tests/common/shape_unit_test.py +183 -0
- sigima/tests/common/stat_unit_test.py +138 -0
- sigima/tests/common/title_formatting_unit_test.py +338 -0
- sigima/tests/common/tools_coordinates_unit_test.py +60 -0
- sigima/tests/common/transformations_unit_test.py +178 -0
- sigima/tests/common/validation_unit_test.py +205 -0
- sigima/tests/conftest.py +129 -0
- sigima/tests/data.py +998 -0
- sigima/tests/env.py +280 -0
- sigima/tests/guiutils.py +163 -0
- sigima/tests/helpers.py +532 -0
- sigima/tests/image/__init__.py +28 -0
- sigima/tests/image/binning_unit_test.py +128 -0
- sigima/tests/image/blob_detection_unit_test.py +312 -0
- sigima/tests/image/centroid_unit_test.py +170 -0
- sigima/tests/image/check_2d_array_unit_test.py +63 -0
- sigima/tests/image/contour_unit_test.py +172 -0
- sigima/tests/image/convolution_unit_test.py +178 -0
- sigima/tests/image/datatype_unit_test.py +67 -0
- sigima/tests/image/edges_unit_test.py +155 -0
- sigima/tests/image/enclosingcircle_unit_test.py +88 -0
- sigima/tests/image/exposure_unit_test.py +223 -0
- sigima/tests/image/fft2d_unit_test.py +189 -0
- sigima/tests/image/filtering_unit_test.py +166 -0
- sigima/tests/image/geometry_unit_test.py +654 -0
- sigima/tests/image/hough_circle_unit_test.py +147 -0
- sigima/tests/image/imageobj_unit_test.py +737 -0
- sigima/tests/image/morphology_unit_test.py +71 -0
- sigima/tests/image/noise_unit_test.py +57 -0
- sigima/tests/image/offset_correction_unit_test.py +72 -0
- sigima/tests/image/operation_unit_test.py +518 -0
- sigima/tests/image/peak2d_limits_unit_test.py +41 -0
- sigima/tests/image/peak2d_unit_test.py +133 -0
- sigima/tests/image/profile_unit_test.py +159 -0
- sigima/tests/image/projections_unit_test.py +121 -0
- sigima/tests/image/restoration_unit_test.py +141 -0
- sigima/tests/image/roi2dparam_unit_test.py +53 -0
- sigima/tests/image/roi_advanced_unit_test.py +588 -0
- sigima/tests/image/roi_grid_unit_test.py +279 -0
- sigima/tests/image/spectrum2d_unit_test.py +40 -0
- sigima/tests/image/threshold_unit_test.py +91 -0
- sigima/tests/io/__init__.py +0 -0
- sigima/tests/io/addnewformat_unit_test.py +125 -0
- sigima/tests/io/convenience_funcs_unit_test.py +470 -0
- sigima/tests/io/coordinated_text_format_unit_test.py +495 -0
- sigima/tests/io/datetime_csv_unit_test.py +198 -0
- sigima/tests/io/imageio_formats_test.py +41 -0
- sigima/tests/io/ioregistry_unit_test.py +69 -0
- sigima/tests/io/objmeta_unit_test.py +87 -0
- sigima/tests/io/readobj_unit_test.py +130 -0
- sigima/tests/io/readwriteobj_unit_test.py +67 -0
- sigima/tests/signal/__init__.py +0 -0
- sigima/tests/signal/analysis_unit_test.py +135 -0
- sigima/tests/signal/check_1d_arrays_unit_test.py +169 -0
- sigima/tests/signal/convolution_unit_test.py +404 -0
- sigima/tests/signal/datetime_unit_test.py +176 -0
- sigima/tests/signal/fft1d_unit_test.py +303 -0
- sigima/tests/signal/filters_unit_test.py +403 -0
- sigima/tests/signal/fitting_unit_test.py +929 -0
- sigima/tests/signal/fwhm_unit_test.py +111 -0
- sigima/tests/signal/noise_unit_test.py +128 -0
- sigima/tests/signal/offset_correction_unit_test.py +34 -0
- sigima/tests/signal/operation_unit_test.py +489 -0
- sigima/tests/signal/peakdetection_unit_test.py +145 -0
- sigima/tests/signal/processing_unit_test.py +657 -0
- sigima/tests/signal/pulse/__init__.py +112 -0
- sigima/tests/signal/pulse/crossing_times_unit_test.py +123 -0
- sigima/tests/signal/pulse/plateau_detection_unit_test.py +102 -0
- sigima/tests/signal/pulse/pulse_unit_test.py +1824 -0
- sigima/tests/signal/roi_advanced_unit_test.py +392 -0
- sigima/tests/signal/signalobj_unit_test.py +603 -0
- sigima/tests/signal/stability_unit_test.py +431 -0
- sigima/tests/signal/uncertainty_unit_test.py +611 -0
- sigima/tests/vistools.py +1030 -0
- sigima/tools/__init__.py +59 -0
- sigima/tools/checks.py +290 -0
- sigima/tools/coordinates.py +308 -0
- sigima/tools/datatypes.py +26 -0
- sigima/tools/image/__init__.py +97 -0
- sigima/tools/image/detection.py +451 -0
- sigima/tools/image/exposure.py +77 -0
- sigima/tools/image/extraction.py +48 -0
- sigima/tools/image/fourier.py +260 -0
- sigima/tools/image/geometry.py +190 -0
- sigima/tools/image/preprocessing.py +165 -0
- sigima/tools/signal/__init__.py +86 -0
- sigima/tools/signal/dynamic.py +254 -0
- sigima/tools/signal/features.py +135 -0
- sigima/tools/signal/filtering.py +171 -0
- sigima/tools/signal/fitting.py +1171 -0
- sigima/tools/signal/fourier.py +466 -0
- sigima/tools/signal/interpolation.py +70 -0
- sigima/tools/signal/peakdetection.py +126 -0
- sigima/tools/signal/pulse.py +1626 -0
- sigima/tools/signal/scaling.py +50 -0
- sigima/tools/signal/stability.py +258 -0
- sigima/tools/signal/windowing.py +90 -0
- sigima/worker.py +79 -0
- sigima-1.0.0.dist-info/METADATA +233 -0
- sigima-1.0.0.dist-info/RECORD +262 -0
- {sigima-0.0.1.dev0.dist-info → sigima-1.0.0.dist-info}/licenses/LICENSE +29 -29
- sigima-0.0.1.dev0.dist-info/METADATA +0 -60
- sigima-0.0.1.dev0.dist-info/RECORD +0 -6
- {sigima-0.0.1.dev0.dist-info → sigima-1.0.0.dist-info}/WHEEL +0 -0
- {sigima-0.0.1.dev0.dist-info → sigima-1.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Image objects subpackage
|
|
5
|
+
========================
|
|
6
|
+
|
|
7
|
+
This subpackage provides image data structures and utilities.
|
|
8
|
+
|
|
9
|
+
The subpackage is organized into the following modules:
|
|
10
|
+
|
|
11
|
+
- `roi`: Region of Interest (ROI) classes and parameters
|
|
12
|
+
- `object`: Main ImageObj class for handling 2D image data
|
|
13
|
+
- `creation`: Image creation utilities and parameter classes
|
|
14
|
+
|
|
15
|
+
All classes and functions are re-exported at the subpackage level for backward
|
|
16
|
+
compatibility. Existing imports like `from sigima.objects.image import ImageObj`
|
|
17
|
+
will continue to work.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
# Import all public classes and functions from submodules
|
|
21
|
+
from .creation import (
|
|
22
|
+
# Constants
|
|
23
|
+
DEFAULT_TITLE,
|
|
24
|
+
Gauss2DParam,
|
|
25
|
+
# Enums
|
|
26
|
+
ImageDatatypes,
|
|
27
|
+
ImageTypes,
|
|
28
|
+
# Base parameter classes
|
|
29
|
+
NewImageParam,
|
|
30
|
+
NormalDistribution2DParam,
|
|
31
|
+
PoissonDistribution2DParam,
|
|
32
|
+
Ramp2DParam,
|
|
33
|
+
UniformDistribution2DParam,
|
|
34
|
+
# Specific parameter classes
|
|
35
|
+
Zero2DParam,
|
|
36
|
+
check_all_image_parameters_classes,
|
|
37
|
+
# Core creation function
|
|
38
|
+
create_image,
|
|
39
|
+
create_image_from_param,
|
|
40
|
+
# Factory and utility functions
|
|
41
|
+
create_image_parameters,
|
|
42
|
+
get_next_image_number,
|
|
43
|
+
# Registration functions
|
|
44
|
+
register_image_parameters_class,
|
|
45
|
+
)
|
|
46
|
+
from .object import (
|
|
47
|
+
ImageObj,
|
|
48
|
+
)
|
|
49
|
+
from .roi import (
|
|
50
|
+
# ROI classes
|
|
51
|
+
BaseSingleImageROI,
|
|
52
|
+
CircularROI,
|
|
53
|
+
# Specific ROI types
|
|
54
|
+
ImageROI,
|
|
55
|
+
PolygonalROI,
|
|
56
|
+
RectangularROI,
|
|
57
|
+
ROI2DParam,
|
|
58
|
+
# ROI utility function
|
|
59
|
+
create_image_roi,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Define __all__ for explicit public API
|
|
63
|
+
__all__ = [
|
|
64
|
+
"DEFAULT_TITLE",
|
|
65
|
+
"BaseSingleImageROI",
|
|
66
|
+
"CircularROI",
|
|
67
|
+
"Gauss2DParam",
|
|
68
|
+
"ImageDatatypes",
|
|
69
|
+
"ImageObj",
|
|
70
|
+
"ImageROI",
|
|
71
|
+
"ImageTypes",
|
|
72
|
+
"NewImageParam",
|
|
73
|
+
"NormalDistribution2DParam",
|
|
74
|
+
"PoissonDistribution2DParam",
|
|
75
|
+
"PolygonalROI",
|
|
76
|
+
"ROI2DParam",
|
|
77
|
+
"Ramp2DParam",
|
|
78
|
+
"RectangularROI",
|
|
79
|
+
"UniformDistribution2DParam",
|
|
80
|
+
"Zero2DParam",
|
|
81
|
+
"check_all_image_parameters_classes",
|
|
82
|
+
"create_image",
|
|
83
|
+
"create_image_from_param",
|
|
84
|
+
"create_image_parameters",
|
|
85
|
+
"create_image_roi",
|
|
86
|
+
"get_next_image_number",
|
|
87
|
+
"register_image_parameters_class",
|
|
88
|
+
]
|
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Image creation utilities
|
|
5
|
+
========================
|
|
6
|
+
|
|
7
|
+
This module provides functions and parameter classes for creating new images.
|
|
8
|
+
|
|
9
|
+
The module includes:
|
|
10
|
+
|
|
11
|
+
- `create_image`: Factory function for creating ImageObj instances
|
|
12
|
+
- `ImageDatatypes`: Enumeration of supported image data types
|
|
13
|
+
- `ImageTypes`: Enumeration of supported image generation types
|
|
14
|
+
- `NewImageParam` and subclasses: Parameter classes for image generation
|
|
15
|
+
- Factory functions and registration utilities
|
|
16
|
+
|
|
17
|
+
These utilities support creating images from various sources:
|
|
18
|
+
- Raw NumPy arrays
|
|
19
|
+
- Synthetic data (zeros, random distributions, analytical functions)
|
|
20
|
+
- Parameterized image generation
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
# pylint: disable=invalid-name # Allows short reference names like x, y, ...
|
|
24
|
+
# pylint: disable=duplicate-code
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
from typing import Type
|
|
29
|
+
|
|
30
|
+
import guidata.dataset as gds
|
|
31
|
+
import numpy as np
|
|
32
|
+
|
|
33
|
+
from sigima.config import _
|
|
34
|
+
from sigima.objects import base
|
|
35
|
+
from sigima.objects.image.object import ImageObj
|
|
36
|
+
from sigima.tools.image import scale_data_to_min_max
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def create_image(
|
|
40
|
+
title: str,
|
|
41
|
+
data: np.ndarray | None = None,
|
|
42
|
+
metadata: dict | None = None,
|
|
43
|
+
units: tuple | None = None,
|
|
44
|
+
labels: tuple | None = None,
|
|
45
|
+
) -> ImageObj:
|
|
46
|
+
"""Create a new Image object
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
title: image title
|
|
50
|
+
data: image data
|
|
51
|
+
metadata: image metadata
|
|
52
|
+
units: X, Y, Z units (tuple of strings)
|
|
53
|
+
labels: X, Y, Z labels (tuple of strings)
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
Image object
|
|
57
|
+
"""
|
|
58
|
+
assert isinstance(title, str)
|
|
59
|
+
assert data is None or isinstance(data, np.ndarray)
|
|
60
|
+
image = ImageObj(title=title)
|
|
61
|
+
image.title = title
|
|
62
|
+
image.data = data
|
|
63
|
+
if units is not None:
|
|
64
|
+
image.xunit, image.yunit, image.zunit = units
|
|
65
|
+
if labels is not None:
|
|
66
|
+
image.xlabel, image.ylabel, image.zlabel = labels
|
|
67
|
+
if metadata is not None:
|
|
68
|
+
image.metadata.update(metadata)
|
|
69
|
+
return image
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class ImageDatatypes(gds.LabeledEnum):
|
|
73
|
+
"""Image data types"""
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def from_numpy_dtype(cls: type[ImageDatatypes], dtype: np.dtype) -> ImageDatatypes:
|
|
77
|
+
"""Return ImageDatatypes member from NumPy dtype
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
dtype: NumPy dtype object
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
Corresponding ImageDatatypes member
|
|
84
|
+
"""
|
|
85
|
+
dtype_str = str(dtype)
|
|
86
|
+
for member in cls:
|
|
87
|
+
if member.value == dtype_str:
|
|
88
|
+
return member
|
|
89
|
+
return cls.UINT8 # Default fallback
|
|
90
|
+
|
|
91
|
+
def to_numpy_dtype(self) -> np.dtype:
|
|
92
|
+
"""Return the corresponding NumPy dtype object.
|
|
93
|
+
|
|
94
|
+
This is the symmetrical counterpart to from_numpy_dtype().
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
NumPy dtype object that can be used directly with numpy functions.
|
|
98
|
+
"""
|
|
99
|
+
return np.dtype(self.value)
|
|
100
|
+
|
|
101
|
+
@classmethod
|
|
102
|
+
def check(cls: type[ImageDatatypes]) -> None:
|
|
103
|
+
"""Check if data types are valid"""
|
|
104
|
+
for member in cls:
|
|
105
|
+
assert hasattr(np, member.value)
|
|
106
|
+
|
|
107
|
+
#: Unsigned integer number stored with 8 bits
|
|
108
|
+
UINT8 = "uint8"
|
|
109
|
+
#: Unsigned integer number stored with 16 bits
|
|
110
|
+
UINT16 = "uint16"
|
|
111
|
+
#: Signed integer number stored with 16 bits
|
|
112
|
+
INT16 = "int16"
|
|
113
|
+
#: Float number stored with 32 bits
|
|
114
|
+
FLOAT32 = "float32"
|
|
115
|
+
#: Float number stored with 64 bits
|
|
116
|
+
FLOAT64 = "float64"
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
ImageDatatypes.check()
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class ImageTypes(gds.LabeledEnum):
|
|
123
|
+
"""Image types."""
|
|
124
|
+
|
|
125
|
+
#: Image filled with zero
|
|
126
|
+
ZEROS = "zero", _("Zero")
|
|
127
|
+
#: Image filled with random data (normal distribution)
|
|
128
|
+
NORMAL_DISTRIBUTION = "normal_distribution", _("Normal distribution")
|
|
129
|
+
#: Image filled with random data (Poisson distribution)
|
|
130
|
+
POISSON_DISTRIBUTION = "poisson_distribution", _("Poisson distribution")
|
|
131
|
+
#: Image filled with random data (uniform distribution)
|
|
132
|
+
UNIFORM_DISTRIBUTION = "uniform_distribution", _("Uniform distribution")
|
|
133
|
+
#: 2D Gaussian image
|
|
134
|
+
GAUSS = "gauss", _("Gaussian")
|
|
135
|
+
#: Bilinear form image
|
|
136
|
+
RAMP = "ramp", _("2D ramp")
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
DEFAULT_TITLE = _("Untitled image")
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class NewImageParam(gds.DataSet):
|
|
143
|
+
"""New image dataset.
|
|
144
|
+
|
|
145
|
+
Subclasses can optionally implement a ``generate_title()`` method to provide
|
|
146
|
+
automatic title generation based on their parameters. This method should return
|
|
147
|
+
a string containing the generated title, or an empty string if no title can be
|
|
148
|
+
generated.
|
|
149
|
+
|
|
150
|
+
Example::
|
|
151
|
+
|
|
152
|
+
def generate_title(self) -> str:
|
|
153
|
+
'''Generate a title based on current parameters.'''
|
|
154
|
+
return f"MyImage(param1={self.param1},param2={self.param2})"
|
|
155
|
+
"""
|
|
156
|
+
|
|
157
|
+
hide_height = False
|
|
158
|
+
hide_width = False
|
|
159
|
+
hide_dtype = False
|
|
160
|
+
hide_type = False
|
|
161
|
+
|
|
162
|
+
title = gds.StringItem(_("Title"), default=DEFAULT_TITLE)
|
|
163
|
+
height = gds.IntItem(
|
|
164
|
+
_("Height"), default=1024, help=_("Image height: number of rows"), min=1
|
|
165
|
+
).set_prop("display", hide=gds.GetAttrProp("hide_height"))
|
|
166
|
+
width = gds.IntItem(
|
|
167
|
+
_("Width"), default=1024, help=_("Image width: number of columns"), min=1
|
|
168
|
+
).set_prop("display", col=1, hide=gds.GetAttrProp("hide_width"))
|
|
169
|
+
dtype = gds.ChoiceItem(
|
|
170
|
+
_("Type"),
|
|
171
|
+
ImageDatatypes,
|
|
172
|
+
default=ImageDatatypes.FLOAT64,
|
|
173
|
+
help=_("Image data type"),
|
|
174
|
+
).set_prop("display", hide=gds.GetAttrProp("hide_dtype"))
|
|
175
|
+
xlabel = gds.StringItem(_("X label"), default="")
|
|
176
|
+
xunit = gds.StringItem(_("X unit"), default="").set_prop("display", col=1)
|
|
177
|
+
ylabel = gds.StringItem(_("Y label"), default="")
|
|
178
|
+
yunit = gds.StringItem(_("Y unit"), default="").set_prop("display", col=1)
|
|
179
|
+
zlabel = gds.StringItem(_("Z label"), default="")
|
|
180
|
+
zunit = gds.StringItem(_("Z unit"), default="").set_prop("display", col=1)
|
|
181
|
+
|
|
182
|
+
def generate_2d_data(self, shape: tuple[int, int]) -> np.ndarray:
|
|
183
|
+
"""Generate 2D data based on current parameters.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
shape: Tuple (height, width) for the output array.
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
2D data array
|
|
190
|
+
"""
|
|
191
|
+
return np.zeros(shape, dtype=self.dtype.to_numpy_dtype())
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
IMAGE_TYPE_PARAM_CLASSES = {}
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def register_image_parameters_class(itype: ImageTypes, param_class) -> None:
|
|
198
|
+
"""Register a parameters class for a given image type.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
itype: image type
|
|
202
|
+
param_class: parameters class
|
|
203
|
+
"""
|
|
204
|
+
IMAGE_TYPE_PARAM_CLASSES[itype] = param_class
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def __get_image_parameters_class(itype: ImageTypes) -> Type[NewImageParam]:
|
|
208
|
+
"""Get parameters class for a given image type.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
itype: image type
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
Parameters class
|
|
215
|
+
|
|
216
|
+
Raises:
|
|
217
|
+
ValueError: if no parameters class is registered for the given image type
|
|
218
|
+
"""
|
|
219
|
+
try:
|
|
220
|
+
return IMAGE_TYPE_PARAM_CLASSES[itype]
|
|
221
|
+
except KeyError as exc:
|
|
222
|
+
raise ValueError(
|
|
223
|
+
f"Image type {itype} has no parameters class registered"
|
|
224
|
+
) from exc
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def check_all_image_parameters_classes() -> None:
|
|
228
|
+
"""Check all registered parameters classes."""
|
|
229
|
+
for itype, param_class in IMAGE_TYPE_PARAM_CLASSES.items():
|
|
230
|
+
assert __get_image_parameters_class(itype) is param_class
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def create_image_parameters(
|
|
234
|
+
itype: ImageTypes,
|
|
235
|
+
title: str | None = None,
|
|
236
|
+
height: int | None = None,
|
|
237
|
+
width: int | None = None,
|
|
238
|
+
idtype: ImageDatatypes | None = None,
|
|
239
|
+
xlabel: str | None = None,
|
|
240
|
+
ylabel: str | None = None,
|
|
241
|
+
zlabel: str | None = None,
|
|
242
|
+
xunit: str | None = None,
|
|
243
|
+
yunit: str | None = None,
|
|
244
|
+
zunit: str | None = None,
|
|
245
|
+
**kwargs: dict,
|
|
246
|
+
) -> NewImageParam:
|
|
247
|
+
"""Create parameters for a given image type.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
itype: image type
|
|
251
|
+
title: image title
|
|
252
|
+
height: image height (number of rows)
|
|
253
|
+
width: image width (number of columns)
|
|
254
|
+
idtype: image data type (`ImageDatatypes` member)
|
|
255
|
+
xlabel: X axis label
|
|
256
|
+
ylabel: Y axis label
|
|
257
|
+
zlabel: Z axis label
|
|
258
|
+
xunit: X axis unit
|
|
259
|
+
yunit: Y axis unit
|
|
260
|
+
zunit: Z axis unit
|
|
261
|
+
**kwargs: additional parameters (specific to the image type)
|
|
262
|
+
|
|
263
|
+
Returns:
|
|
264
|
+
Parameters object for the given image type
|
|
265
|
+
"""
|
|
266
|
+
pclass = __get_image_parameters_class(itype)
|
|
267
|
+
p = pclass.create(**kwargs)
|
|
268
|
+
if title is not None:
|
|
269
|
+
p.title = title
|
|
270
|
+
if height is not None:
|
|
271
|
+
p.height = height
|
|
272
|
+
if width is not None:
|
|
273
|
+
p.width = width
|
|
274
|
+
if idtype is not None:
|
|
275
|
+
assert isinstance(idtype, ImageDatatypes)
|
|
276
|
+
p.dtype = idtype
|
|
277
|
+
if xlabel is not None:
|
|
278
|
+
p.xlabel = xlabel
|
|
279
|
+
if ylabel is not None:
|
|
280
|
+
p.ylabel = ylabel
|
|
281
|
+
if zlabel is not None:
|
|
282
|
+
p.zlabel = zlabel
|
|
283
|
+
if xunit is not None:
|
|
284
|
+
p.xunit = xunit
|
|
285
|
+
if yunit is not None:
|
|
286
|
+
p.yunit = yunit
|
|
287
|
+
if zunit is not None:
|
|
288
|
+
p.zunit = zunit
|
|
289
|
+
return p
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
class Zero2DParam(NewImageParam, title=_("Zero")):
|
|
293
|
+
"""Image parameters for a 2D image filled with zero."""
|
|
294
|
+
|
|
295
|
+
def generate_2d_data(self, shape: tuple[int, int]) -> np.ndarray:
|
|
296
|
+
"""Generate 2D data based on current parameters.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
shape: Tuple (height, width) for the output array.
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
2D data array
|
|
303
|
+
"""
|
|
304
|
+
return np.zeros(shape, dtype=self.dtype.to_numpy_dtype())
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
register_image_parameters_class(ImageTypes.ZEROS, Zero2DParam)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
class UniformDistribution2DParam(
|
|
311
|
+
NewImageParam, base.UniformDistributionParam, title=_("Uniform distribution")
|
|
312
|
+
):
|
|
313
|
+
"""Uniform-distribution image parameters."""
|
|
314
|
+
|
|
315
|
+
def generate_2d_data(self, shape: tuple[int, int]) -> np.ndarray:
|
|
316
|
+
"""Generate 2D data based on current parameters.
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
shape: Tuple (height, width) for the output array.
|
|
320
|
+
|
|
321
|
+
Returns:
|
|
322
|
+
2D data array
|
|
323
|
+
"""
|
|
324
|
+
rng = np.random.default_rng(self.seed)
|
|
325
|
+
assert self.vmin is not None
|
|
326
|
+
assert self.vmax is not None
|
|
327
|
+
data = scale_data_to_min_max(rng.random(shape), self.vmin, self.vmax)
|
|
328
|
+
return data.astype(self.dtype.to_numpy_dtype())
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
register_image_parameters_class(
|
|
332
|
+
ImageTypes.UNIFORM_DISTRIBUTION, UniformDistribution2DParam
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
class NormalDistribution2DParam(
|
|
337
|
+
NewImageParam, base.NormalDistributionParam, title=_("Normal distribution")
|
|
338
|
+
):
|
|
339
|
+
"""Normal-distribution image parameters."""
|
|
340
|
+
|
|
341
|
+
def generate_2d_data(self, shape: tuple[int, int]) -> np.ndarray:
|
|
342
|
+
"""Generate 2D data based on current parameters.
|
|
343
|
+
|
|
344
|
+
Args:
|
|
345
|
+
shape: Tuple (height, width) for the output array.
|
|
346
|
+
|
|
347
|
+
Returns:
|
|
348
|
+
2D data array.
|
|
349
|
+
"""
|
|
350
|
+
rng = np.random.default_rng(self.seed)
|
|
351
|
+
assert self.mu is not None
|
|
352
|
+
assert self.sigma is not None
|
|
353
|
+
data: np.ndarray = rng.normal(self.mu, self.sigma, shape)
|
|
354
|
+
return data.astype(self.dtype.to_numpy_dtype())
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
register_image_parameters_class(
|
|
358
|
+
ImageTypes.NORMAL_DISTRIBUTION, NormalDistribution2DParam
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
class PoissonDistribution2DParam(
|
|
363
|
+
NewImageParam, base.PoissonDistributionParam, title=_("Poisson distribution")
|
|
364
|
+
):
|
|
365
|
+
"""Poisson-distribution image parameters."""
|
|
366
|
+
|
|
367
|
+
def generate_2d_data(self, shape: tuple[int, int]) -> np.ndarray:
|
|
368
|
+
"""Generate 2D data based on current parameters.
|
|
369
|
+
|
|
370
|
+
Args:
|
|
371
|
+
shape: Tuple (height, width) for the output array.
|
|
372
|
+
|
|
373
|
+
Returns:
|
|
374
|
+
2D data array.
|
|
375
|
+
"""
|
|
376
|
+
rng = np.random.default_rng(self.seed)
|
|
377
|
+
assert self.lam is not None
|
|
378
|
+
data: np.ndarray = rng.poisson(lam=self.lam, size=shape)
|
|
379
|
+
return data.astype(self.dtype.to_numpy_dtype())
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
register_image_parameters_class(
|
|
383
|
+
ImageTypes.POISSON_DISTRIBUTION, PoissonDistribution2DParam
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
class Gauss2DParam(
|
|
388
|
+
NewImageParam,
|
|
389
|
+
title=_("Gaussian"),
|
|
390
|
+
comment="z = A exp(-((√((x - x<sub>0</sub>)<sup>2</sup> + "
|
|
391
|
+
"(y - y<sub>0</sub>)<sup>2</sup>) - μ)<sup>2</sup>) / (2 σ<sup>2</sup>))",
|
|
392
|
+
):
|
|
393
|
+
"""2D Gaussian parameters."""
|
|
394
|
+
|
|
395
|
+
a = gds.FloatItem("A", default=None, check=False)
|
|
396
|
+
xmin = gds.FloatItem("x<sub>min</sub>", default=-10.0).set_pos(col=1)
|
|
397
|
+
sigma = gds.FloatItem("σ", default=1.0)
|
|
398
|
+
xmax = gds.FloatItem("x<sub>max</sub>", default=10.0).set_pos(col=1)
|
|
399
|
+
mu = gds.FloatItem("μ", default=0.0)
|
|
400
|
+
ymin = gds.FloatItem("y<sub>min</sub>", default=-10.0).set_pos(col=1)
|
|
401
|
+
x0 = gds.FloatItem("x<sub>0</sub>", default=0.0)
|
|
402
|
+
ymax = gds.FloatItem("y<sub>max</sub>", default=10.0).set_pos(col=1)
|
|
403
|
+
y0 = gds.FloatItem("y<sub>0</sub>", default=0.0).set_pos(col=0, colspan=1)
|
|
404
|
+
|
|
405
|
+
def generate_title(self) -> str:
|
|
406
|
+
"""Generate a title based on current parameters."""
|
|
407
|
+
return (
|
|
408
|
+
f"Gauss(a={self.a:g},μ={self.mu:g},"
|
|
409
|
+
f"σ={self.sigma:g}),x0={self.x0:g},y0={self.y0:g})"
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
def generate_2d_data(self, shape: tuple[int, int]) -> np.ndarray:
|
|
413
|
+
"""Generate 2D data based on current parameters.
|
|
414
|
+
|
|
415
|
+
Args:
|
|
416
|
+
shape: Tuple (height, width) for the output array.
|
|
417
|
+
|
|
418
|
+
Returns:
|
|
419
|
+
2D data array
|
|
420
|
+
"""
|
|
421
|
+
if self.a is None:
|
|
422
|
+
try:
|
|
423
|
+
self.a = np.iinfo(self.dtype.to_numpy_dtype()).max / 2.0
|
|
424
|
+
except ValueError:
|
|
425
|
+
self.a = 10.0
|
|
426
|
+
x, y = np.meshgrid(
|
|
427
|
+
np.linspace(self.xmin, self.xmax, shape[1]),
|
|
428
|
+
np.linspace(self.ymin, self.ymax, shape[0]),
|
|
429
|
+
)
|
|
430
|
+
data = self.a * np.exp(
|
|
431
|
+
-((np.sqrt((x - self.x0) ** 2 + (y - self.y0) ** 2) - self.mu) ** 2)
|
|
432
|
+
/ (2.0 * self.sigma**2)
|
|
433
|
+
)
|
|
434
|
+
return np.array(data, dtype=self.dtype.to_numpy_dtype())
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
register_image_parameters_class(ImageTypes.GAUSS, Gauss2DParam)
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
class Ramp2DParam(
|
|
441
|
+
NewImageParam,
|
|
442
|
+
title=_("2D ramp"),
|
|
443
|
+
comment="z = A (x - x<sub>0</sub>) + B (y - y<sub>0</sub>) + C",
|
|
444
|
+
):
|
|
445
|
+
"""Define the parameters of a 2D ramp (planar ramp)."""
|
|
446
|
+
|
|
447
|
+
_g0_begin = gds.BeginGroup(_("Coefficients"))
|
|
448
|
+
a = gds.FloatItem("A", default=1.0).set_pos(col=0)
|
|
449
|
+
b = gds.FloatItem("B", default=1.0).set_pos(col=1)
|
|
450
|
+
c = gds.FloatItem("C", default=0.0).set_pos(colspan=1)
|
|
451
|
+
x0 = gds.FloatItem("x<sub>0</sub>", default=0.0).set_pos(col=0)
|
|
452
|
+
y0 = gds.FloatItem("y<sub>0</sub>", default=0.0).set_pos(col=1)
|
|
453
|
+
_g0_end = gds.EndGroup("")
|
|
454
|
+
_g1_begin = gds.BeginGroup(_("Domain"))
|
|
455
|
+
xmin = gds.FloatItem("x<sub>min</sub>", default=-1.0).set_pos(col=0)
|
|
456
|
+
xmax = gds.FloatItem("x<sub>max</sub>", default=1.0).set_pos(col=1)
|
|
457
|
+
ymin = gds.FloatItem("y<sub>min</sub>", default=-1.0).set_pos(col=0)
|
|
458
|
+
ymax = gds.FloatItem("y<sub>max</sub>", default=1.0).set_pos(col=1)
|
|
459
|
+
_g1_end = gds.EndGroup("")
|
|
460
|
+
|
|
461
|
+
def generate_title(self) -> str:
|
|
462
|
+
"""Generate a title based on current parameters."""
|
|
463
|
+
terms = [] # Build terms list for non-zero coefficients
|
|
464
|
+
if self.a != 0.0:
|
|
465
|
+
if self.x0 == 0.0:
|
|
466
|
+
x_part = f"{self.a:g} x"
|
|
467
|
+
else:
|
|
468
|
+
x_part = f"{self.a:g} (x - {self.x0:g})"
|
|
469
|
+
terms.append(x_part)
|
|
470
|
+
if self.b != 0.0:
|
|
471
|
+
if self.y0 == 0.0:
|
|
472
|
+
y_part = f"{self.b:g} y"
|
|
473
|
+
else:
|
|
474
|
+
y_part = f"{self.b:g} (y - {self.y0:g})"
|
|
475
|
+
terms.append(y_part)
|
|
476
|
+
if self.c != 0.0 or not terms: # Include c if it's the only term
|
|
477
|
+
terms.append(f"{self.c:g}")
|
|
478
|
+
return f"z = {' + '.join(terms)}"
|
|
479
|
+
|
|
480
|
+
def generate_2d_data(self, shape: tuple[int, int]) -> np.ndarray:
|
|
481
|
+
"""Generate 2D data based on current parameters.
|
|
482
|
+
|
|
483
|
+
Args:
|
|
484
|
+
shape: Tuple (height, width) for the output array.
|
|
485
|
+
|
|
486
|
+
Returns:
|
|
487
|
+
2D data array
|
|
488
|
+
"""
|
|
489
|
+
x = np.linspace(self.xmin, self.xmax, shape[1])
|
|
490
|
+
y = np.linspace(self.ymin, self.ymax, shape[0])
|
|
491
|
+
xx, yy = np.meshgrid(x, y)
|
|
492
|
+
data = self.a * (xx - self.x0) + self.b * (yy - self.y0) + self.c
|
|
493
|
+
return np.array(data, dtype=self.dtype.to_numpy_dtype())
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
register_image_parameters_class(ImageTypes.RAMP, Ramp2DParam)
|
|
497
|
+
check_all_image_parameters_classes()
|
|
498
|
+
|
|
499
|
+
IMG_NB = 0
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
def get_next_image_number():
|
|
503
|
+
"""Get the next image number.
|
|
504
|
+
|
|
505
|
+
This function is used to keep track of the number of signals created.
|
|
506
|
+
It is typically used to generate unique titles for new signals.
|
|
507
|
+
|
|
508
|
+
Returns:
|
|
509
|
+
int: new image number
|
|
510
|
+
"""
|
|
511
|
+
global IMG_NB # pylint: disable=global-statement
|
|
512
|
+
IMG_NB += 1
|
|
513
|
+
return IMG_NB
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
def create_image_from_param(param: NewImageParam) -> ImageObj:
|
|
517
|
+
"""Create a new Image object from parameters.
|
|
518
|
+
|
|
519
|
+
Args:
|
|
520
|
+
param: new image parameters
|
|
521
|
+
|
|
522
|
+
Returns:
|
|
523
|
+
Image object
|
|
524
|
+
|
|
525
|
+
Raises:
|
|
526
|
+
NotImplementedError: if the image type is not supported
|
|
527
|
+
"""
|
|
528
|
+
if param.height is None:
|
|
529
|
+
param.height = 1024
|
|
530
|
+
if param.width is None:
|
|
531
|
+
param.width = 1024
|
|
532
|
+
if param.dtype is None:
|
|
533
|
+
param.dtype = ImageDatatypes.UINT16
|
|
534
|
+
# Generate data first, as some `generate_title()` methods may depend on it:
|
|
535
|
+
shape = (param.height, param.width)
|
|
536
|
+
data = param.generate_2d_data(shape)
|
|
537
|
+
# Check if user has customized the title or left it as default/empty
|
|
538
|
+
use_generated_title = not param.title or param.title == DEFAULT_TITLE
|
|
539
|
+
if use_generated_title:
|
|
540
|
+
# Try to generate a descriptive title
|
|
541
|
+
gen_title = getattr(param, "generate_title", lambda: "")()
|
|
542
|
+
if gen_title:
|
|
543
|
+
title = gen_title
|
|
544
|
+
else:
|
|
545
|
+
# No generated title available, use default with number
|
|
546
|
+
title = f"{DEFAULT_TITLE} {get_next_image_number()}"
|
|
547
|
+
else:
|
|
548
|
+
# User has set a custom title, use it as-is
|
|
549
|
+
title = param.title
|
|
550
|
+
image = create_image(
|
|
551
|
+
title,
|
|
552
|
+
data,
|
|
553
|
+
units=(param.xunit, param.yunit, param.zunit),
|
|
554
|
+
labels=(param.xlabel, param.ylabel, param.zlabel),
|
|
555
|
+
)
|
|
556
|
+
return image
|