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
sigima/tools/__init__.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tools (:mod:`sigima.tools`)
|
|
3
|
+
---------------------------
|
|
4
|
+
|
|
5
|
+
This package contains functions operating on NumPy arrays that are intended to be
|
|
6
|
+
used in Sigima computation functions. These functions are complementary to the
|
|
7
|
+
algorithms provided by external libraries such as SciPy, NumPy, and scikit-image.
|
|
8
|
+
|
|
9
|
+
Even though these functions are primarily designed to be used in the Sigima pipeline,
|
|
10
|
+
they can also be used independently. They provide a wide range of features but are
|
|
11
|
+
not exhaustive due to the vast number of algorithms already available in the
|
|
12
|
+
scientific Python ecosystem.
|
|
13
|
+
|
|
14
|
+
.. seealso::
|
|
15
|
+
|
|
16
|
+
The :mod:`sigima.proc` module contains the Sigima computation functions that
|
|
17
|
+
operate on signal and image objects (i.e. :class:`sigima.objects.SignalObj` and
|
|
18
|
+
:class:`sigima.objects.ImageObj`, defined in the :mod:`sigima.objects` package).
|
|
19
|
+
|
|
20
|
+
These tools are organized in subpackages according to their purpose. The following
|
|
21
|
+
subpackages are available:
|
|
22
|
+
|
|
23
|
+
- :mod:`sigima.tools.checks`: Input data checks for all tools
|
|
24
|
+
- :mod:`sigima.tools.signal`: Signal processing tools
|
|
25
|
+
- :mod:`sigima.tools.image`: Image processing tools
|
|
26
|
+
- :mod:`sigima.tools.datatypes`: Data type conversion tools
|
|
27
|
+
- :mod:`sigima.tools.coordinates`: Coordinate conversion tools
|
|
28
|
+
|
|
29
|
+
Check functions
|
|
30
|
+
^^^^^^^^^^^^^^^
|
|
31
|
+
|
|
32
|
+
.. automodule:: sigima.tools.checks
|
|
33
|
+
:members:
|
|
34
|
+
|
|
35
|
+
Signal Processing Tools
|
|
36
|
+
^^^^^^^^^^^^^^^^^^^^^^^
|
|
37
|
+
|
|
38
|
+
.. automodule:: sigima.tools.signal
|
|
39
|
+
:members:
|
|
40
|
+
|
|
41
|
+
Image Processing Tools
|
|
42
|
+
^^^^^^^^^^^^^^^^^^^^^^
|
|
43
|
+
|
|
44
|
+
.. automodule:: sigima.tools.image
|
|
45
|
+
:members:
|
|
46
|
+
|
|
47
|
+
Data Type Conversion Tools
|
|
48
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
49
|
+
|
|
50
|
+
.. automodule:: sigima.tools.datatypes
|
|
51
|
+
:members:
|
|
52
|
+
|
|
53
|
+
Coordinate Conversion Tools
|
|
54
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
55
|
+
|
|
56
|
+
.. automodule:: sigima.tools.coordinates
|
|
57
|
+
:members:
|
|
58
|
+
|
|
59
|
+
"""
|
sigima/tools/checks.py
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
.. Checks for 1D and 2D NumPy arrays used in tools (:mod:`sigima.tools.checks`).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from functools import wraps
|
|
11
|
+
from typing import Any, Callable
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True)
|
|
17
|
+
class ArrayValidationRules:
|
|
18
|
+
"""Hold 1-D array validation rules."""
|
|
19
|
+
|
|
20
|
+
#: Label used in error messages (e.g., "x" or "y")
|
|
21
|
+
label: str
|
|
22
|
+
#: Whether to enforce 1-D.
|
|
23
|
+
require_1d: bool = True
|
|
24
|
+
#: Check minimum size
|
|
25
|
+
min_size: int | None = None
|
|
26
|
+
#: Expected dtype (np.issubdtype). Use None to skip.
|
|
27
|
+
dtype: type | None = None
|
|
28
|
+
#: Whether to enforce finite values only.
|
|
29
|
+
finite_only: bool = False
|
|
30
|
+
#: Whether to enforce non-decreasing order.
|
|
31
|
+
sorted_: bool = False
|
|
32
|
+
#: Whether to enforce constant spacing (within rtol).
|
|
33
|
+
evenly_spaced: bool = False
|
|
34
|
+
#: Relative tolerance for regular spacing.
|
|
35
|
+
rtol: float = 1e-5
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _validate_array_1d(arr: np.ndarray, *, rules: ArrayValidationRules) -> None:
|
|
39
|
+
"""Validate a single 1D NumPy array according to the provided rules.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
arr: Array to validate.
|
|
43
|
+
rules: Validation rules to apply.
|
|
44
|
+
|
|
45
|
+
Raises:
|
|
46
|
+
ValueError: If shape constraint is violated.
|
|
47
|
+
ValueError: If size constraint is violated.
|
|
48
|
+
ValueError: If finite constraint is violated.
|
|
49
|
+
ValueError: If order constraint is violated.
|
|
50
|
+
ValueError: If spacing constraint is violated.
|
|
51
|
+
TypeError: If dtype does not match.
|
|
52
|
+
"""
|
|
53
|
+
if rules.require_1d and arr.ndim != 1:
|
|
54
|
+
raise ValueError(f"{rules.label} must be 1-D.")
|
|
55
|
+
if rules.min_size is not None and arr.size < rules.min_size:
|
|
56
|
+
raise ValueError(f"{rules.label} must have at least {rules.min_size} elements.")
|
|
57
|
+
if rules.dtype is not None and not np.issubdtype(arr.dtype, rules.dtype):
|
|
58
|
+
raise TypeError(
|
|
59
|
+
f"{rules.label} must be of type {rules.dtype}, but got {arr.dtype}."
|
|
60
|
+
)
|
|
61
|
+
if rules.finite_only and not np.all(np.isfinite(arr)):
|
|
62
|
+
raise ValueError(f"{rules.label} must contain only finite values.")
|
|
63
|
+
if rules.sorted_ and arr.size > 1 and not np.all(np.diff(arr) > 0.0):
|
|
64
|
+
raise ValueError(f"{rules.label} must be sorted in ascending order.")
|
|
65
|
+
if rules.evenly_spaced and arr.size > 1:
|
|
66
|
+
dx = np.diff(arr)
|
|
67
|
+
if not np.allclose(dx, np.mean(dx), rtol=rules.rtol):
|
|
68
|
+
raise ValueError(f"{rules.label} must be evenly spaced.")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def check_1d_array(
|
|
72
|
+
func: Callable[..., Any] | None = None,
|
|
73
|
+
*,
|
|
74
|
+
require_1d: bool = True,
|
|
75
|
+
min_size: int | None = None,
|
|
76
|
+
dtype: type | None = np.inexact,
|
|
77
|
+
finite_only: bool = False,
|
|
78
|
+
sorted_: bool = False,
|
|
79
|
+
evenly_spaced: bool = False,
|
|
80
|
+
rtol: float = 1e-5,
|
|
81
|
+
label: str = "array",
|
|
82
|
+
) -> Callable:
|
|
83
|
+
"""Decorator to check a single 1D NumPy array.
|
|
84
|
+
|
|
85
|
+
Can be used with or without parentheses.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
require_1d: Whether to check if the array is 1-D.
|
|
89
|
+
min_size: Minimum size of the array.
|
|
90
|
+
dtype: Expected dtype of the array (np.issubdtype). Use None to skip.
|
|
91
|
+
finite_only: Whether to check if the array contains only finite values.
|
|
92
|
+
sorted_: Whether to check if the array is sorted in ascending order.
|
|
93
|
+
evenly_spaced: Whether to check if the array is evenly spaced.
|
|
94
|
+
rtol: Relative tolerance for regular spacing.
|
|
95
|
+
label: Label for error messages (e.g., "x", "y").
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
Decorated function with pre-checks on the single array.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
def decorator(inner_func: Callable[..., Any]) -> Callable[..., Any]:
|
|
102
|
+
@wraps(inner_func)
|
|
103
|
+
def wrapper(arr: np.ndarray, *args: Any, **kwargs: Any) -> Any:
|
|
104
|
+
_validate_array_1d(
|
|
105
|
+
arr,
|
|
106
|
+
rules=ArrayValidationRules(
|
|
107
|
+
label=label,
|
|
108
|
+
require_1d=require_1d,
|
|
109
|
+
min_size=min_size,
|
|
110
|
+
dtype=dtype,
|
|
111
|
+
finite_only=finite_only,
|
|
112
|
+
sorted_=sorted_,
|
|
113
|
+
evenly_spaced=evenly_spaced,
|
|
114
|
+
rtol=rtol,
|
|
115
|
+
),
|
|
116
|
+
)
|
|
117
|
+
return inner_func(arr, *args, **kwargs)
|
|
118
|
+
|
|
119
|
+
return wrapper
|
|
120
|
+
|
|
121
|
+
if func is not None:
|
|
122
|
+
return decorator(func)
|
|
123
|
+
return decorator
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def check_1d_arrays(
|
|
127
|
+
func: Callable[..., Any] | None = None,
|
|
128
|
+
*,
|
|
129
|
+
x_require_1d: bool = True,
|
|
130
|
+
x_min_size: int | None = None,
|
|
131
|
+
x_dtype: type | None = np.floating,
|
|
132
|
+
x_finite_only: bool = False,
|
|
133
|
+
x_sorted: bool = False,
|
|
134
|
+
x_evenly_spaced: bool = False,
|
|
135
|
+
y_require_1d: bool = True,
|
|
136
|
+
y_min_size: int | None = None,
|
|
137
|
+
y_dtype: type | None = np.inexact,
|
|
138
|
+
y_finite_only: bool = False,
|
|
139
|
+
same_size: bool = True,
|
|
140
|
+
rtol: float = 1e-5,
|
|
141
|
+
) -> Callable:
|
|
142
|
+
"""Decorator to check paired 1D NumPy arrays (x, y).
|
|
143
|
+
|
|
144
|
+
Can be used with or without parentheses.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
func: Function to decorate.
|
|
148
|
+
x_require_1d: Whether to check if x is 1-D.
|
|
149
|
+
x_min_size: Minimum size of x.
|
|
150
|
+
x_dtype: Expected dtype of x (np.issubdtype). Use None to skip.
|
|
151
|
+
x_finite_only: Whether to check if x contains only finite values.
|
|
152
|
+
x_sorted: Whether to check if x is sorted in ascending order.
|
|
153
|
+
x_evenly_spaced: Whether to check if x is evenly spaced.
|
|
154
|
+
y_require_1d: Whether to check if y is 1-D.
|
|
155
|
+
y_min_size: Minimum size of y.
|
|
156
|
+
y_dtype: Expected dtype of y (np.issubdtype). Use None to skip.
|
|
157
|
+
y_finite_only: Whether to check if y contains only finite values.
|
|
158
|
+
same_size: Whether to check that x and y have the same size.
|
|
159
|
+
rtol: Relative tolerance for regular spacing (used for x).
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
Decorated function with pre-checks on x/y.
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
def decorator(inner_func: Callable[..., Any]) -> Callable[..., Any]:
|
|
166
|
+
@wraps(inner_func)
|
|
167
|
+
def wrapper(x: np.ndarray, y: np.ndarray, *args: Any, **kwargs: Any) -> Any:
|
|
168
|
+
_validate_array_1d(
|
|
169
|
+
x,
|
|
170
|
+
rules=ArrayValidationRules(
|
|
171
|
+
label="x",
|
|
172
|
+
require_1d=x_require_1d,
|
|
173
|
+
min_size=x_min_size,
|
|
174
|
+
dtype=x_dtype,
|
|
175
|
+
finite_only=x_finite_only,
|
|
176
|
+
sorted_=x_sorted,
|
|
177
|
+
evenly_spaced=x_evenly_spaced,
|
|
178
|
+
rtol=rtol,
|
|
179
|
+
),
|
|
180
|
+
)
|
|
181
|
+
_validate_array_1d(
|
|
182
|
+
y,
|
|
183
|
+
rules=ArrayValidationRules(
|
|
184
|
+
label="y",
|
|
185
|
+
require_1d=y_require_1d,
|
|
186
|
+
min_size=y_min_size,
|
|
187
|
+
dtype=y_dtype,
|
|
188
|
+
finite_only=y_finite_only,
|
|
189
|
+
sorted_=False,
|
|
190
|
+
evenly_spaced=False,
|
|
191
|
+
rtol=rtol,
|
|
192
|
+
),
|
|
193
|
+
)
|
|
194
|
+
if same_size and x.size != y.size:
|
|
195
|
+
raise ValueError("x and y must have the same size.")
|
|
196
|
+
return inner_func(x, y, *args, **kwargs)
|
|
197
|
+
|
|
198
|
+
return wrapper
|
|
199
|
+
|
|
200
|
+
if func is not None:
|
|
201
|
+
return decorator(func)
|
|
202
|
+
return decorator
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def check_2d_array(
|
|
206
|
+
func: Callable[..., Any] | None = None,
|
|
207
|
+
*,
|
|
208
|
+
ndim: int = 2,
|
|
209
|
+
dtype: type | None = None,
|
|
210
|
+
non_constant: bool = False,
|
|
211
|
+
finite_only: bool = False,
|
|
212
|
+
) -> Callable:
|
|
213
|
+
"""
|
|
214
|
+
Decorator to check input for functions operating on 2D NumPy arrays (e.g. images).
|
|
215
|
+
|
|
216
|
+
Can be used with parentheses:
|
|
217
|
+
|
|
218
|
+
.. code-block:: python
|
|
219
|
+
|
|
220
|
+
@check_2d_array(ndim=3, dtype=np.uint8)
|
|
221
|
+
def process_image(image: np.ndarray) -> np.ndarray:
|
|
222
|
+
# Process the image
|
|
223
|
+
return image
|
|
224
|
+
|
|
225
|
+
Or without parentheses (default arguments):
|
|
226
|
+
|
|
227
|
+
.. code-block:: python
|
|
228
|
+
|
|
229
|
+
@check_2d_array
|
|
230
|
+
def process_image(image: np.ndarray) -> np.ndarray:
|
|
231
|
+
# Process the image
|
|
232
|
+
return image
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
ndim: Expected number of dimensions.
|
|
236
|
+
dtype: Expected dtype.
|
|
237
|
+
non_constant: Whether to check that the array has dynamic range.
|
|
238
|
+
finite_only: Whether to check that all values are finite.
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
Decorated function with pre-checks on data.
|
|
242
|
+
"""
|
|
243
|
+
|
|
244
|
+
def decorator(inner_func: Callable[..., Any]) -> Callable[..., Any]:
|
|
245
|
+
@wraps(inner_func)
|
|
246
|
+
def wrapper(data: np.ndarray, *args: Any, **kwargs: Any) -> Any:
|
|
247
|
+
# === Check input array
|
|
248
|
+
if data.ndim != ndim:
|
|
249
|
+
raise ValueError(f"Input array must be {ndim}D, got {data.ndim}D.")
|
|
250
|
+
if dtype is not None and not np.issubdtype(data.dtype, dtype):
|
|
251
|
+
raise TypeError(
|
|
252
|
+
f"Input array must be of type {dtype}, got {data.dtype}."
|
|
253
|
+
)
|
|
254
|
+
if non_constant:
|
|
255
|
+
dmin, dmax = np.nanmin(data), np.nanmax(data)
|
|
256
|
+
if dmin == dmax:
|
|
257
|
+
raise ValueError("Input array has no dynamic range.")
|
|
258
|
+
if finite_only and not np.all(np.isfinite(data)):
|
|
259
|
+
raise ValueError("Input array contains non-finite values.")
|
|
260
|
+
# === Call the original function
|
|
261
|
+
return inner_func(data, *args, **kwargs)
|
|
262
|
+
|
|
263
|
+
return wrapper
|
|
264
|
+
|
|
265
|
+
if func is not None:
|
|
266
|
+
# Usage: `@check_2d_array`
|
|
267
|
+
return decorator(func)
|
|
268
|
+
# Usage: `@check_2d_array(...)`
|
|
269
|
+
return decorator
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def normalize_kernel(kernel: np.ndarray) -> np.ndarray:
|
|
273
|
+
"""Normalize a convolution/deconvolution kernel if needed.
|
|
274
|
+
|
|
275
|
+
This utility function can normalize the kernel to sum to 1.0.
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
kernel: The kernel array to normalize.
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
The normalized kernel if it's not already normalized and if its sum is not
|
|
282
|
+
zero, otherwise the original kernel.
|
|
283
|
+
|
|
284
|
+
Note:
|
|
285
|
+
A kernel is considered normalized if ``np.isclose(sum(kernel), 1.0)``.
|
|
286
|
+
"""
|
|
287
|
+
kernel_sum = np.sum(kernel)
|
|
288
|
+
if not np.isclose(kernel_sum, 1.0) and kernel_sum != 0.0:
|
|
289
|
+
return kernel / kernel_sum
|
|
290
|
+
return kernel
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
.. Coordinates Algorithms (see parent package :mod:`sigima.tools`)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# pylint: disable=invalid-name # Allows short reference names like x, y, ...
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from typing import Literal
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
from sigima.tools.checks import check_1d_arrays
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def circle_to_diameter(
|
|
19
|
+
xc: float, yc: float, r: float
|
|
20
|
+
) -> tuple[float, float, float, float]:
|
|
21
|
+
"""Convert circle center and radius to X diameter coordinates
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
xc: Circle center X coordinate
|
|
25
|
+
yc: Circle center Y coordinate
|
|
26
|
+
r: Circle radius
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
tuple: Circle X diameter coordinates
|
|
30
|
+
"""
|
|
31
|
+
return xc - r, yc, xc + r, yc
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def array_circle_to_diameter(data: np.ndarray) -> np.ndarray:
|
|
35
|
+
"""Convert circle center and radius to X diameter coordinates (array version)
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
data: Circle center and radius, in the form of a 2D array (N, 3)
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Circle X diameter coordinates, in the form of a 2D array (N, 4)
|
|
42
|
+
"""
|
|
43
|
+
xc, yc, r = data[:, 0], data[:, 1], data[:, 2]
|
|
44
|
+
x_start = xc - r
|
|
45
|
+
x_end = xc + r
|
|
46
|
+
result = np.column_stack((x_start, yc, x_end, yc)).astype(float)
|
|
47
|
+
return result
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def circle_to_center_radius(
|
|
51
|
+
x0: float, y0: float, x1: float, y1: float
|
|
52
|
+
) -> tuple[float, float, float]:
|
|
53
|
+
"""Convert circle X diameter coordinates to center and radius
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
x0: Diameter start X coordinate
|
|
57
|
+
y0: Diameter start Y coordinate
|
|
58
|
+
x1: Diameter end X coordinate
|
|
59
|
+
y1: Diameter end Y coordinate
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
tuple: Circle center and radius
|
|
63
|
+
"""
|
|
64
|
+
xc, yc = (x0 + x1) / 2, (y0 + y1) / 2
|
|
65
|
+
r = np.sqrt((x1 - x0) ** 2 + (y1 - y0) ** 2) / 2
|
|
66
|
+
return xc, yc, r
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def array_circle_to_center_radius(data: np.ndarray) -> np.ndarray:
|
|
70
|
+
"""Convert circle X diameter coordinates to center and radius (array version)
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
data: Circle X diameter coordinates, in the form of a 2D array (N, 4)
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Circle center and radius, in the form of a 2D array (N, 3)
|
|
77
|
+
"""
|
|
78
|
+
x0, y0, x1, y1 = data[:, 0], data[:, 1], data[:, 2], data[:, 3]
|
|
79
|
+
xc, yc = (x0 + x1) / 2, (y0 + y1) / 2
|
|
80
|
+
r = np.sqrt((x1 - x0) ** 2 + (y1 - y0) ** 2) / 2
|
|
81
|
+
result = np.column_stack((xc, yc, r)).astype(float)
|
|
82
|
+
return result
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def ellipse_to_diameters(
|
|
86
|
+
xc: float, yc: float, a: float, b: float, theta: float
|
|
87
|
+
) -> tuple[float, float, float, float, float, float, float, float]:
|
|
88
|
+
"""Convert ellipse center, axes and angle to X/Y diameters coordinates
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
xc: Ellipse center X coordinate
|
|
92
|
+
yc: Ellipse center Y coordinate
|
|
93
|
+
a: Ellipse half larger axis
|
|
94
|
+
b: Ellipse half smaller axis
|
|
95
|
+
theta: Ellipse angle
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
Ellipse X/Y diameters (major/minor axes) coordinates
|
|
99
|
+
"""
|
|
100
|
+
dxa, dya = a * np.cos(theta), a * np.sin(theta)
|
|
101
|
+
dxb, dyb = b * np.sin(theta), b * np.cos(theta)
|
|
102
|
+
x0, y0, x1, y1 = xc - dxa, yc - dya, xc + dxa, yc + dya
|
|
103
|
+
x2, y2, x3, y3 = xc - dxb, yc - dyb, xc + dxb, yc + dyb
|
|
104
|
+
return x0, y0, x1, y1, x2, y2, x3, y3
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def array_ellipse_to_diameters(data: np.ndarray) -> np.ndarray:
|
|
108
|
+
"""Convert ellipse center, axes and angle to X/Y diameters coordinates
|
|
109
|
+
(array version)
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
data: Ellipse center, axes and angle, in the form of a 2D array (N, 5)
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Ellipse X/Y diameters (major/minor axes) coordinates,
|
|
116
|
+
in the form of a 2D array (N, 8)
|
|
117
|
+
"""
|
|
118
|
+
xc, yc, a, b, theta = data[:, 0], data[:, 1], data[:, 2], data[:, 3], data[:, 4]
|
|
119
|
+
dxa, dya = a * np.cos(theta), a * np.sin(theta)
|
|
120
|
+
dxb, dyb = b * np.sin(theta), b * np.cos(theta)
|
|
121
|
+
x0, y0, x1, y1 = xc - dxa, yc - dya, xc + dxa, yc + dya
|
|
122
|
+
x2, y2, x3, y3 = xc - dxb, yc - dyb, xc + dxb, yc + dyb
|
|
123
|
+
result = np.column_stack((x0, y0, x1, y1, x2, y2, x3, y3)).astype(float)
|
|
124
|
+
return result
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def ellipse_to_center_axes_angle(
|
|
128
|
+
x0: float,
|
|
129
|
+
y0: float,
|
|
130
|
+
x1: float,
|
|
131
|
+
y1: float,
|
|
132
|
+
x2: float,
|
|
133
|
+
y2: float,
|
|
134
|
+
x3: float,
|
|
135
|
+
y3: float,
|
|
136
|
+
) -> tuple[float, float, float, float, float]:
|
|
137
|
+
"""Convert ellipse X/Y diameters coordinates to center, axes and angle
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
x0: major axis start X coordinate
|
|
141
|
+
y0: major axis start Y coordinate
|
|
142
|
+
x1: major axis end X coordinate
|
|
143
|
+
y1: major axis end Y coordinate
|
|
144
|
+
x2: minor axis start X coordinate
|
|
145
|
+
y2: minor axis start Y coordinate
|
|
146
|
+
x3: minor axis end X coordinate
|
|
147
|
+
y3: minor axis end Y coordinate
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
Ellipse center, axes and angle
|
|
151
|
+
"""
|
|
152
|
+
xc, yc = (x0 + x1) / 2, (y0 + y1) / 2
|
|
153
|
+
a = np.sqrt((x1 - x0) ** 2 + (y1 - y0) ** 2) / 2
|
|
154
|
+
b = np.sqrt((x3 - x2) ** 2 + (y3 - y2) ** 2) / 2
|
|
155
|
+
theta = np.arctan2(y1 - y0, x1 - x0)
|
|
156
|
+
return xc, yc, a, b, theta
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def array_ellipse_to_center_axes_angle(data: np.ndarray) -> np.ndarray:
|
|
160
|
+
"""Convert ellipse X/Y diameters coordinates to center, axes and angle
|
|
161
|
+
(array version)
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
data: Ellipse X/Y diameters coordinates, in the form of a 2D array (N, 8)
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
Ellipse center, axes and angle, in the form of a 2D array (N, 5)
|
|
168
|
+
"""
|
|
169
|
+
x0, y0, x1, y1, x2, y2, x3, y3 = (
|
|
170
|
+
data[:, 0],
|
|
171
|
+
data[:, 1],
|
|
172
|
+
data[:, 2],
|
|
173
|
+
data[:, 3],
|
|
174
|
+
data[:, 4],
|
|
175
|
+
data[:, 5],
|
|
176
|
+
data[:, 6],
|
|
177
|
+
data[:, 7],
|
|
178
|
+
)
|
|
179
|
+
xc, yc = (x0 + x1) / 2, (y0 + y1) / 2
|
|
180
|
+
a = np.sqrt((x1 - x0) ** 2 + (y1 - y0) ** 2) / 2
|
|
181
|
+
b = np.sqrt((x3 - x2) ** 2 + (y3 - y2) ** 2) / 2
|
|
182
|
+
theta = np.arctan2(y1 - y0, x1 - x0)
|
|
183
|
+
result = np.column_stack((xc, yc, a, b, theta)).astype(float)
|
|
184
|
+
return result
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@check_1d_arrays
|
|
188
|
+
def to_polar(
|
|
189
|
+
x: np.ndarray, y: np.ndarray, unit: Literal["°", "rad"] = "rad"
|
|
190
|
+
) -> tuple[np.ndarray, np.ndarray]:
|
|
191
|
+
"""Convert Cartesian coordinates to polar coordinates.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
x: Cartesian x-coordinate.
|
|
195
|
+
y: Cartesian y-coordinate.
|
|
196
|
+
unit: Unit of the angle ("°" or "rad").
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
Polar coordinates (r, theta) where r is the radius and theta is the angle.
|
|
200
|
+
|
|
201
|
+
Raises:
|
|
202
|
+
ValueError: If the unit is not "°" or "rad".
|
|
203
|
+
"""
|
|
204
|
+
if unit not in ["rad", "°"]:
|
|
205
|
+
raise ValueError(f"Unit must be radian ('rad') or degree ('°'), got {unit}.")
|
|
206
|
+
r = np.sqrt(x**2 + y**2)
|
|
207
|
+
theta = np.arctan2(y, x)
|
|
208
|
+
if unit == "°":
|
|
209
|
+
theta = np.rad2deg(theta)
|
|
210
|
+
return r, theta
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
@check_1d_arrays
|
|
214
|
+
def to_cartesian(
|
|
215
|
+
r: np.ndarray, theta: np.ndarray, unit: Literal["°", "rad"] = "rad"
|
|
216
|
+
) -> tuple[np.ndarray, np.ndarray]:
|
|
217
|
+
"""Convert polar coordinates to Cartesian coordinates.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
r: Polar radius.
|
|
221
|
+
theta: Polar angle.
|
|
222
|
+
unit: Unit of the angle ("°" or "rad").
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
Cartesian coordinates (x, y) where x is the x-coordinate and y is the
|
|
226
|
+
y-coordinate.
|
|
227
|
+
|
|
228
|
+
Raises:
|
|
229
|
+
ValueError: If the unit is not "°" or "rad".
|
|
230
|
+
ValueError: If any value of the radius is negative.
|
|
231
|
+
"""
|
|
232
|
+
if unit not in ["rad", "°"]:
|
|
233
|
+
raise ValueError(f"Unit must be radian ('rad') or degree ('°'), got {unit}.")
|
|
234
|
+
if np.any(r < 0.0):
|
|
235
|
+
raise ValueError("Negative radius values are not allowed.")
|
|
236
|
+
if unit == "°":
|
|
237
|
+
theta = np.deg2rad(theta)
|
|
238
|
+
x = r * np.cos(theta)
|
|
239
|
+
y = r * np.sin(theta)
|
|
240
|
+
return x, y
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def rotate(angle: float) -> np.ndarray:
|
|
244
|
+
"""Return rotation matrix
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
angle: Rotation angle (in radians)
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
Rotation matrix
|
|
251
|
+
"""
|
|
252
|
+
cos_a = np.cos(angle)
|
|
253
|
+
sin_a = np.sin(angle)
|
|
254
|
+
return np.array([[cos_a, -sin_a, 0], [sin_a, cos_a, 0], [0, 0, 1]], dtype=float)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def colvector(x: float, y: float) -> np.ndarray:
|
|
258
|
+
"""Return vector from coordinates
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
x: x-coordinate
|
|
262
|
+
y: y-coordinate
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
Vector
|
|
266
|
+
"""
|
|
267
|
+
return np.array([x, y, 1]).T
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def vector_rotation(theta: float, dx: float, dy: float) -> tuple[float, float]:
|
|
271
|
+
"""Compute theta-rotation on vector
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
theta: Rotation angle
|
|
275
|
+
dx: x-coordinate of vector
|
|
276
|
+
dy: y-coordinate of vector
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
Tuple of (x, y) coordinates of rotated vector
|
|
280
|
+
"""
|
|
281
|
+
return (rotate(theta) @ colvector(dx, dy)).ravel()[:2]
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
@check_1d_arrays(x_require_1d=False, y_require_1d=False)
|
|
285
|
+
def polar_to_complex(
|
|
286
|
+
r: np.ndarray, theta: np.ndarray, unit: Literal["°", "rad"] = "rad"
|
|
287
|
+
) -> np.ndarray:
|
|
288
|
+
"""Convert polar coordinates to complex number.
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
r: Polar radius.
|
|
292
|
+
theta: Polar angle.
|
|
293
|
+
unit: Unit of the angle ("°" or "rad").
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
Complex numbers corresponding to the polar coordinates.
|
|
297
|
+
|
|
298
|
+
Raises:
|
|
299
|
+
ValueError: If the unit is not "°" or "rad".
|
|
300
|
+
ValueError: If any value of the radius is negative.
|
|
301
|
+
"""
|
|
302
|
+
if unit not in ["rad", "°"]:
|
|
303
|
+
raise ValueError(f"Unit must be radian ('rad') or degree ('°'), got {unit}.")
|
|
304
|
+
if np.any(r < 0.0):
|
|
305
|
+
raise ValueError("Negative radius values are not allowed.")
|
|
306
|
+
if unit == "°":
|
|
307
|
+
theta = np.deg2rad(theta)
|
|
308
|
+
return r * np.exp(1j * theta)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
.. Data Type Conversion Algorithms (see parent package :mod:`sigima.algorithms`)
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def clip_astype(data: np.ndarray, dtype: np.dtype) -> np.ndarray:
|
|
13
|
+
"""Convert array to a new data type, after having clipped values to the new
|
|
14
|
+
data type's range if it is an integer type.
|
|
15
|
+
If data type is not integer, this is equivalent to ``data.astype(dtype)``.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
data: Array to convert
|
|
19
|
+
dtype: Data type to convert to
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
Array converted to new data type
|
|
23
|
+
"""
|
|
24
|
+
if np.issubdtype(dtype, np.integer):
|
|
25
|
+
return np.clip(data, np.iinfo(dtype).min, np.iinfo(dtype).max).astype(dtype)
|
|
26
|
+
return data.astype(dtype)
|