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,444 @@
|
|
|
1
|
+
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
|
|
2
|
+
|
|
3
|
+
"""Signal object class
|
|
4
|
+
===================
|
|
5
|
+
|
|
6
|
+
This module provides the main SignalObj class for handling 1D signal data.
|
|
7
|
+
|
|
8
|
+
The module includes:
|
|
9
|
+
|
|
10
|
+
- `SignalObj`: Main class for signal data management and operations
|
|
11
|
+
|
|
12
|
+
The SignalObj class supports:
|
|
13
|
+
- Signal data storage with x, y coordinates
|
|
14
|
+
- Error bars (dx, dy) for uncertainty quantification
|
|
15
|
+
- Metadata and annotations
|
|
16
|
+
- ROI (Region of Interest) operations
|
|
17
|
+
- Axis labels and units management
|
|
18
|
+
- Copy operations with type conversion
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
# pylint: disable=invalid-name # Allows short reference names like x, y, ...
|
|
22
|
+
# pylint: disable=duplicate-code
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
from typing import Type
|
|
27
|
+
|
|
28
|
+
import guidata.dataset as gds
|
|
29
|
+
import numpy as np
|
|
30
|
+
import pandas as pd
|
|
31
|
+
|
|
32
|
+
from sigima.config import _
|
|
33
|
+
from sigima.objects import base
|
|
34
|
+
from sigima.objects.signal.constants import (
|
|
35
|
+
DATETIME_X_FORMAT_KEY,
|
|
36
|
+
DATETIME_X_KEY,
|
|
37
|
+
DEFAULT_DATETIME_FORMAT,
|
|
38
|
+
VALID_TIME_UNITS,
|
|
39
|
+
)
|
|
40
|
+
from sigima.objects.signal.roi import SignalROI
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class SignalObj(gds.DataSet, base.BaseObj[SignalROI]):
|
|
44
|
+
"""Signal object"""
|
|
45
|
+
|
|
46
|
+
PREFIX = "s"
|
|
47
|
+
VALID_DTYPES = (np.float32, np.float64, np.complex128)
|
|
48
|
+
|
|
49
|
+
_tabs = gds.BeginTabGroup("all")
|
|
50
|
+
|
|
51
|
+
_datag = gds.BeginGroup(_("Data and metadata"))
|
|
52
|
+
title = gds.StringItem(_("Signal title"), default=_("Untitled"))
|
|
53
|
+
xydata = gds.FloatArrayItem(_("Data"), transpose=True, minmax="rows")
|
|
54
|
+
metadata = gds.DictItem(_("Metadata"), default={}) # type: ignore[assignment]
|
|
55
|
+
annotations = gds.StringItem(_("Annotations"), default="").set_prop(
|
|
56
|
+
"display",
|
|
57
|
+
hide=True,
|
|
58
|
+
) # Annotations as a serialized JSON string # type: ignore[assignment]
|
|
59
|
+
_e_datag = gds.EndGroup(_("Data and metadata"))
|
|
60
|
+
|
|
61
|
+
_unitsg = gds.BeginGroup(_("Titles / Units"))
|
|
62
|
+
title = gds.StringItem(_("Signal title"), default=_("Untitled"))
|
|
63
|
+
_tabs_u = gds.BeginTabGroup("units")
|
|
64
|
+
_unitsx = gds.BeginGroup(_("X-axis"))
|
|
65
|
+
xlabel = gds.StringItem(_("Title"), default="")
|
|
66
|
+
xunit = gds.StringItem(_("Unit"), default="")
|
|
67
|
+
_e_unitsx = gds.EndGroup(_("X-axis"))
|
|
68
|
+
_unitsy = gds.BeginGroup(_("Y-axis"))
|
|
69
|
+
ylabel = gds.StringItem(_("Title"), default="")
|
|
70
|
+
yunit = gds.StringItem(_("Unit"), default="")
|
|
71
|
+
_e_unitsy = gds.EndGroup(_("Y-axis"))
|
|
72
|
+
_e_tabs_u = gds.EndTabGroup("units")
|
|
73
|
+
_e_unitsg = gds.EndGroup(_("Titles / Units"))
|
|
74
|
+
|
|
75
|
+
_scalesg = gds.BeginGroup(_("Scales"))
|
|
76
|
+
_prop_autoscale = gds.GetAttrProp("autoscale")
|
|
77
|
+
autoscale = gds.BoolItem(_("Auto scale"), default=True).set_prop(
|
|
78
|
+
"display", store=_prop_autoscale
|
|
79
|
+
)
|
|
80
|
+
_tabs_b = gds.BeginTabGroup("bounds")
|
|
81
|
+
_boundsx = gds.BeginGroup(_("X-axis"))
|
|
82
|
+
xscalelog = gds.BoolItem(_("Logarithmic scale"), default=False)
|
|
83
|
+
xscalemin = gds.FloatItem(_("Lower bound"), check=False).set_prop(
|
|
84
|
+
"display", active=gds.NotProp(_prop_autoscale)
|
|
85
|
+
)
|
|
86
|
+
xscalemax = gds.FloatItem(_("Upper bound"), check=False).set_prop(
|
|
87
|
+
"display", active=gds.NotProp(_prop_autoscale)
|
|
88
|
+
)
|
|
89
|
+
_e_boundsx = gds.EndGroup(_("X-axis"))
|
|
90
|
+
_boundsy = gds.BeginGroup(_("Y-axis"))
|
|
91
|
+
yscalelog = gds.BoolItem(_("Logarithmic scale"), default=False)
|
|
92
|
+
yscalemin = gds.FloatItem(_("Lower bound"), check=False).set_prop(
|
|
93
|
+
"display", active=gds.NotProp(_prop_autoscale)
|
|
94
|
+
)
|
|
95
|
+
yscalemax = gds.FloatItem(_("Upper bound"), check=False).set_prop(
|
|
96
|
+
"display", active=gds.NotProp(_prop_autoscale)
|
|
97
|
+
)
|
|
98
|
+
_e_boundsy = gds.EndGroup(_("Y-axis"))
|
|
99
|
+
_e_tabs_b = gds.EndTabGroup("bounds")
|
|
100
|
+
_e_scalesg = gds.EndGroup(_("Scales"))
|
|
101
|
+
|
|
102
|
+
_e_tabs = gds.EndTabGroup("all")
|
|
103
|
+
|
|
104
|
+
def __init__(self, title=None, comment=None, icon=""):
|
|
105
|
+
"""Constructor
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
title: title
|
|
109
|
+
comment: comment
|
|
110
|
+
icon: icon
|
|
111
|
+
"""
|
|
112
|
+
gds.DataSet.__init__(self, title, comment, icon)
|
|
113
|
+
base.BaseObj.__init__(self)
|
|
114
|
+
|
|
115
|
+
@staticmethod
|
|
116
|
+
def get_roi_class() -> Type[SignalROI]:
|
|
117
|
+
"""Return ROI class"""
|
|
118
|
+
return SignalROI
|
|
119
|
+
|
|
120
|
+
def copy(
|
|
121
|
+
self,
|
|
122
|
+
title: str | None = None,
|
|
123
|
+
dtype: np.dtype | None = None,
|
|
124
|
+
all_metadata: bool = False,
|
|
125
|
+
) -> SignalObj:
|
|
126
|
+
"""Copy object.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
title: title
|
|
130
|
+
dtype: data type
|
|
131
|
+
all_metadata: if True, copy all metadata, otherwise only basic metadata
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
Copied object
|
|
135
|
+
"""
|
|
136
|
+
title = self.title if title is None else title
|
|
137
|
+
obj = SignalObj(title=title)
|
|
138
|
+
obj.title = title
|
|
139
|
+
obj.xlabel = self.xlabel
|
|
140
|
+
obj.ylabel = self.ylabel
|
|
141
|
+
obj.xunit = self.xunit
|
|
142
|
+
obj.yunit = self.yunit
|
|
143
|
+
if dtype not in (None, float, complex, np.complex128):
|
|
144
|
+
raise RuntimeError("Signal data only supports float64/complex128 dtype")
|
|
145
|
+
obj.metadata = base.deepcopy_metadata(self.metadata, all_metadata=all_metadata)
|
|
146
|
+
obj.annotations = self.annotations
|
|
147
|
+
obj.xydata = np.array(self.xydata, copy=True, dtype=dtype)
|
|
148
|
+
obj.autoscale = self.autoscale
|
|
149
|
+
obj.xscalelog = self.xscalelog
|
|
150
|
+
obj.xscalemin = self.xscalemin
|
|
151
|
+
obj.xscalemax = self.xscalemax
|
|
152
|
+
obj.yscalelog = self.yscalelog
|
|
153
|
+
obj.yscalemin = self.yscalemin
|
|
154
|
+
obj.yscalemax = self.yscalemax
|
|
155
|
+
return obj
|
|
156
|
+
|
|
157
|
+
def set_data_type(self, dtype: np.dtype) -> None: # pylint: disable=unused-argument
|
|
158
|
+
"""Change data type.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
Data type
|
|
162
|
+
"""
|
|
163
|
+
raise RuntimeError("Setting data type is not support for signals")
|
|
164
|
+
|
|
165
|
+
def set_xydata(
|
|
166
|
+
self,
|
|
167
|
+
x: np.ndarray | list | None,
|
|
168
|
+
y: np.ndarray | list | None,
|
|
169
|
+
dx: np.ndarray | list | None = None,
|
|
170
|
+
dy: np.ndarray | list | None = None,
|
|
171
|
+
) -> None:
|
|
172
|
+
"""Set xy data
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
x: x data
|
|
176
|
+
y: y data
|
|
177
|
+
dx: dx data (optional: error bars). Use None to reset dx data to None,
|
|
178
|
+
or provide array to set new dx data.
|
|
179
|
+
dy: dy data (optional: error bars). Use None to reset dy data to None,
|
|
180
|
+
or provide array to set new dy data.
|
|
181
|
+
"""
|
|
182
|
+
if x is None and y is None:
|
|
183
|
+
# Using empty arrays (this allows initialization of the object without data)
|
|
184
|
+
x = np.array([], dtype=np.float64)
|
|
185
|
+
y = np.array([], dtype=np.float64)
|
|
186
|
+
if x is None and y is not None:
|
|
187
|
+
# If x is None, we create a default x array based on the length of y
|
|
188
|
+
assert isinstance(y, (list, np.ndarray))
|
|
189
|
+
x = np.arange(len(y), dtype=np.float64)
|
|
190
|
+
if x is not None:
|
|
191
|
+
x = np.array(x)
|
|
192
|
+
if y is not None:
|
|
193
|
+
y = np.array(y)
|
|
194
|
+
if dx is not None:
|
|
195
|
+
dx = np.array(dx)
|
|
196
|
+
if dy is not None:
|
|
197
|
+
dy = np.array(dy)
|
|
198
|
+
if dx is None and dy is None:
|
|
199
|
+
self.xydata = np.vstack([x, y])
|
|
200
|
+
else:
|
|
201
|
+
if dx is None:
|
|
202
|
+
dx = np.full_like(x, np.nan)
|
|
203
|
+
if dy is None:
|
|
204
|
+
dy = np.full_like(y, np.nan)
|
|
205
|
+
assert x is not None and y is not None
|
|
206
|
+
self.xydata = np.vstack((x, y, dx, dy))
|
|
207
|
+
|
|
208
|
+
def __get_x(self) -> np.ndarray | None:
|
|
209
|
+
"""Get x data"""
|
|
210
|
+
if self.xydata is not None:
|
|
211
|
+
x: np.ndarray = self.xydata[0]
|
|
212
|
+
# We have to ensure that x is a floating point array, because if y is
|
|
213
|
+
# complex, the whole xydata array will be complex, and we need to avoid
|
|
214
|
+
# any unintended type promotion.
|
|
215
|
+
return x.real.astype(float)
|
|
216
|
+
return None
|
|
217
|
+
|
|
218
|
+
def __set_x(self, data: np.ndarray | list[float]) -> None:
|
|
219
|
+
"""Set x data"""
|
|
220
|
+
assert isinstance(self.xydata, np.ndarray)
|
|
221
|
+
assert isinstance(data, (list, np.ndarray))
|
|
222
|
+
data = np.array(data, dtype=float)
|
|
223
|
+
assert data.shape[0] == self.xydata.shape[1], (
|
|
224
|
+
"X data size must match Y data size"
|
|
225
|
+
)
|
|
226
|
+
if not np.all(np.diff(data) >= 0.0):
|
|
227
|
+
raise ValueError("X data must be monotonic (sorted in ascending order)")
|
|
228
|
+
self.xydata[0] = data
|
|
229
|
+
|
|
230
|
+
def __get_y(self) -> np.ndarray | None:
|
|
231
|
+
"""Get y data"""
|
|
232
|
+
if self.xydata is not None:
|
|
233
|
+
return self.xydata[1]
|
|
234
|
+
return None
|
|
235
|
+
|
|
236
|
+
def __set_y(self, data: np.ndarray | list[float]) -> None:
|
|
237
|
+
"""Set y data"""
|
|
238
|
+
assert isinstance(self.xydata, np.ndarray)
|
|
239
|
+
assert isinstance(data, (list, np.ndarray))
|
|
240
|
+
data = np.array(data)
|
|
241
|
+
assert data.shape[0] == self.xydata.shape[1], (
|
|
242
|
+
"Y data size must match X data size"
|
|
243
|
+
)
|
|
244
|
+
assert np.issubdtype(data.dtype, np.inexact), "Y data must be float or complex"
|
|
245
|
+
self.xydata[1] = data
|
|
246
|
+
|
|
247
|
+
def __get_dx(self) -> np.ndarray | None:
|
|
248
|
+
"""Get dx data"""
|
|
249
|
+
if self.xydata is not None and len(self.xydata) == 4:
|
|
250
|
+
dx: np.ndarray = self.xydata[2]
|
|
251
|
+
if np.all(np.isnan(dx)):
|
|
252
|
+
return None
|
|
253
|
+
return dx.real.astype(float)
|
|
254
|
+
return None
|
|
255
|
+
|
|
256
|
+
def __set_dx(self, data: np.ndarray | list[float] | None) -> None:
|
|
257
|
+
"""Set dx data"""
|
|
258
|
+
if data is None:
|
|
259
|
+
data = np.full_like(self.x, np.nan)
|
|
260
|
+
assert isinstance(data, (list, np.ndarray))
|
|
261
|
+
data = np.array(data)
|
|
262
|
+
if self.xydata is None:
|
|
263
|
+
raise ValueError("Signal data not initialized")
|
|
264
|
+
assert data.shape[0] == self.xydata.shape[1], (
|
|
265
|
+
"dx data size must match X data size"
|
|
266
|
+
)
|
|
267
|
+
if len(self.xydata) == 2:
|
|
268
|
+
self.xydata = np.vstack((self.xydata, np.zeros((2, self.xydata.shape[1]))))
|
|
269
|
+
self.xydata[2] = np.array(data)
|
|
270
|
+
|
|
271
|
+
def __get_dy(self) -> np.ndarray | None:
|
|
272
|
+
"""Get dy data"""
|
|
273
|
+
if self.xydata is not None and len(self.xydata) == 4:
|
|
274
|
+
dy: np.ndarray = self.xydata[3]
|
|
275
|
+
if np.all(np.isnan(dy)):
|
|
276
|
+
return None
|
|
277
|
+
return dy
|
|
278
|
+
return None
|
|
279
|
+
|
|
280
|
+
def __set_dy(self, data: np.ndarray | list[float] | None) -> None:
|
|
281
|
+
"""Set dy data"""
|
|
282
|
+
if data is None:
|
|
283
|
+
data = np.full_like(self.x, np.nan)
|
|
284
|
+
assert isinstance(data, (list, np.ndarray))
|
|
285
|
+
data = np.array(data)
|
|
286
|
+
if self.xydata is None:
|
|
287
|
+
raise ValueError("Signal data not initialized")
|
|
288
|
+
assert data.shape[0] == self.xydata.shape[1], (
|
|
289
|
+
"dy data size must match X data size"
|
|
290
|
+
)
|
|
291
|
+
if len(self.xydata) == 2:
|
|
292
|
+
self.xydata = np.vstack((self.xydata, np.zeros((2, self.xydata.shape[1]))))
|
|
293
|
+
self.xydata[3] = np.array(data)
|
|
294
|
+
|
|
295
|
+
x = property(__get_x, __set_x)
|
|
296
|
+
y = data = property(__get_y, __set_y)
|
|
297
|
+
dx = property(__get_dx, __set_dx)
|
|
298
|
+
dy = property(__get_dy, __set_dy)
|
|
299
|
+
|
|
300
|
+
def get_data(self, roi_index: int | None = None) -> tuple[np.ndarray, np.ndarray]:
|
|
301
|
+
"""
|
|
302
|
+
Return original data (if ROI is not defined or `roi_index` is None),
|
|
303
|
+
or ROI data (if both ROI and `roi_index` are defined).
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
roi_index: ROI index
|
|
307
|
+
|
|
308
|
+
Returns:
|
|
309
|
+
Data
|
|
310
|
+
"""
|
|
311
|
+
if self.roi is None or roi_index is None:
|
|
312
|
+
assert isinstance(self.xydata, np.ndarray)
|
|
313
|
+
return self.x, self.y
|
|
314
|
+
single_roi = self.roi.get_single_roi(roi_index)
|
|
315
|
+
return single_roi.get_data(self)
|
|
316
|
+
|
|
317
|
+
def physical_to_indices(self, coords: list[float]) -> list[int]:
|
|
318
|
+
"""Convert coordinates from physical (real world) to indices (pixel)
|
|
319
|
+
|
|
320
|
+
Args:
|
|
321
|
+
coords: coordinates
|
|
322
|
+
|
|
323
|
+
Returns:
|
|
324
|
+
Indices
|
|
325
|
+
"""
|
|
326
|
+
assert isinstance(self.x, np.ndarray)
|
|
327
|
+
return [int(np.abs(self.x - x).argmin()) for x in coords]
|
|
328
|
+
|
|
329
|
+
def indices_to_physical(self, indices: list[int]) -> list[float]:
|
|
330
|
+
"""Convert coordinates from indices to physical (real world)
|
|
331
|
+
|
|
332
|
+
Args:
|
|
333
|
+
indices: indices
|
|
334
|
+
|
|
335
|
+
Returns:
|
|
336
|
+
Coordinates
|
|
337
|
+
"""
|
|
338
|
+
# We take the real part of the x data to avoid `ComplexWarning` warnings
|
|
339
|
+
# when creating and manipulating the `XRangeSelection` shape (`plotpy`)
|
|
340
|
+
return self.x.real[indices].tolist()
|
|
341
|
+
|
|
342
|
+
def is_x_datetime(self) -> bool:
|
|
343
|
+
"""Check if x data represents datetime values.
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
True if x data represents datetime values, False otherwise
|
|
347
|
+
"""
|
|
348
|
+
return self.metadata.get(DATETIME_X_KEY, False)
|
|
349
|
+
|
|
350
|
+
def set_x_from_datetime(
|
|
351
|
+
self,
|
|
352
|
+
dt_array: np.ndarray | list,
|
|
353
|
+
unit: str = "s",
|
|
354
|
+
format_str: str | None = None,
|
|
355
|
+
) -> None:
|
|
356
|
+
"""Set x values from datetime objects or strings.
|
|
357
|
+
|
|
358
|
+
This method converts datetime data to float timestamps (Unix time: seconds
|
|
359
|
+
since 1970-01-01) for efficient storage and computation. The datetime context
|
|
360
|
+
is preserved through metadata.
|
|
361
|
+
|
|
362
|
+
Note: X values are always stored as Unix timestamps (seconds since 1970-01-01)
|
|
363
|
+
regardless of the 'unit' parameter. The 'unit' parameter is stored in metadata
|
|
364
|
+
and used only for axis labeling when plotting.
|
|
365
|
+
|
|
366
|
+
Args:
|
|
367
|
+
dt_array: Array of datetime objects, datetime strings, or numpy datetime64
|
|
368
|
+
unit: Time unit label for display. Options: 's' (seconds),
|
|
369
|
+
'ms' (milliseconds), 'us' (microseconds), 'ns' (nanoseconds),
|
|
370
|
+
'min' (minutes), 'h' (hours). Default is 's'. This parameter only
|
|
371
|
+
affects the axis label, not the stored data.
|
|
372
|
+
format_str: Format string for datetime display. If None, uses default.
|
|
373
|
+
|
|
374
|
+
Raises:
|
|
375
|
+
ValueError: If unit is not valid
|
|
376
|
+
|
|
377
|
+
Example:
|
|
378
|
+
>>> from datetime import datetime
|
|
379
|
+
>>> signal = SignalObj()
|
|
380
|
+
>>> timestamps = [datetime(2025, 1, 1, 10, 0, 0),
|
|
381
|
+
... datetime(2025, 1, 1, 10, 0, 1)]
|
|
382
|
+
>>> signal.set_x_from_datetime(timestamps, unit='s')
|
|
383
|
+
>>> signal.is_x_datetime()
|
|
384
|
+
True
|
|
385
|
+
>>> # X data is stored as Unix timestamps (seconds since 1970)
|
|
386
|
+
>>> signal.x[0] > 1.7e9 # Year 2025
|
|
387
|
+
True
|
|
388
|
+
"""
|
|
389
|
+
if unit not in VALID_TIME_UNITS:
|
|
390
|
+
raise ValueError(
|
|
391
|
+
f"Invalid unit: {unit}. Must be one of: {', '.join(VALID_TIME_UNITS)}"
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
# Convert to pandas datetime (handles strings, datetime objects, etc.)
|
|
395
|
+
dt_series = pd.to_datetime(dt_array)
|
|
396
|
+
|
|
397
|
+
# Convert to float timestamp in seconds (pandas epoch is in nanoseconds)
|
|
398
|
+
# Note: We always store as Unix timestamps (seconds since 1970-01-01)
|
|
399
|
+
# regardless of the 'unit' parameter, which is only for display purposes
|
|
400
|
+
timestamp_seconds = dt_series.astype(np.int64) / 1e9
|
|
401
|
+
|
|
402
|
+
# Convert to numpy array (pandas may return Float64Index)
|
|
403
|
+
x_float = np.array(timestamp_seconds, dtype=np.float64)
|
|
404
|
+
|
|
405
|
+
# Check if signal already has data with matching size
|
|
406
|
+
if self.xydata is not None and self.xydata.shape[1] == len(x_float):
|
|
407
|
+
# Signal already has matching data, just update x
|
|
408
|
+
self.x = x_float
|
|
409
|
+
else:
|
|
410
|
+
# Initialize or reinitialize signal with x data (y will be zeros)
|
|
411
|
+
y_placeholder = np.zeros_like(x_float)
|
|
412
|
+
self.set_xydata(x_float, y_placeholder)
|
|
413
|
+
|
|
414
|
+
# Store metadata
|
|
415
|
+
self.metadata[DATETIME_X_KEY] = True
|
|
416
|
+
self.metadata[DATETIME_X_FORMAT_KEY] = (
|
|
417
|
+
format_str if format_str is not None else DEFAULT_DATETIME_FORMAT
|
|
418
|
+
)
|
|
419
|
+
# Store unit in xunit attribute (more intuitive than metadata)
|
|
420
|
+
self.xunit = unit
|
|
421
|
+
|
|
422
|
+
def get_x_as_datetime(self) -> np.ndarray:
|
|
423
|
+
"""Get x values as datetime objects if x is datetime data.
|
|
424
|
+
|
|
425
|
+
Returns x data as numpy datetime64 array if the signal contains datetime data,
|
|
426
|
+
otherwise returns the regular x data as floats.
|
|
427
|
+
|
|
428
|
+
Returns:
|
|
429
|
+
Array of datetime64 objects if x is datetime data, otherwise regular x array
|
|
430
|
+
|
|
431
|
+
Example:
|
|
432
|
+
>>> signal.set_x_from_datetime([datetime(2025, 1, 1, 10, 0, 0)])
|
|
433
|
+
>>> dt_values = signal.get_x_as_datetime()
|
|
434
|
+
>>> isinstance(dt_values[0], np.datetime64)
|
|
435
|
+
True
|
|
436
|
+
"""
|
|
437
|
+
if not self.is_x_datetime():
|
|
438
|
+
return self.x
|
|
439
|
+
# X values are always stored as Unix timestamps (seconds since 1970-01-01)
|
|
440
|
+
# regardless of the 'unit' parameter
|
|
441
|
+
x_float = self.x
|
|
442
|
+
|
|
443
|
+
# Convert seconds to datetime using pandas
|
|
444
|
+
return pd.to_datetime(x_float, unit="s").to_numpy()
|