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,458 @@
|
|
|
1
|
+
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Extraction computation module
|
|
5
|
+
-----------------------------
|
|
6
|
+
|
|
7
|
+
This module provides functions to extract sub-regions
|
|
8
|
+
and intensity profiles from images.
|
|
9
|
+
|
|
10
|
+
Main features include:
|
|
11
|
+
|
|
12
|
+
- Extraction of regions of interest (ROIs)
|
|
13
|
+
- Extraction of line, segment, average, and radial intensity profiles
|
|
14
|
+
|
|
15
|
+
These functions are useful for isolating specific image zones and for analyzing signal
|
|
16
|
+
intensity along defined paths.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
# pylint: disable=invalid-name # Allows short reference names like x, y, ...
|
|
20
|
+
|
|
21
|
+
# Note:
|
|
22
|
+
# ----
|
|
23
|
+
# - All `guidata.dataset.DataSet` parameter classes must also be imported
|
|
24
|
+
# in the `sigima.params` module.
|
|
25
|
+
# - All functions decorated by `computation_function` must be imported in the upper
|
|
26
|
+
# level `sigima.proc.image` module.
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
from typing import Callable
|
|
31
|
+
|
|
32
|
+
import guidata.dataset as gds
|
|
33
|
+
import numpy as np
|
|
34
|
+
from numpy import ma
|
|
35
|
+
|
|
36
|
+
import sigima.tools.image
|
|
37
|
+
from sigima.config import _
|
|
38
|
+
from sigima.objects.image import ImageObj, ImageROI, RectangularROI, ROI2DParam
|
|
39
|
+
from sigima.objects.signal import SignalObj
|
|
40
|
+
from sigima.proc.base import dst_1_to_1
|
|
41
|
+
from sigima.proc.decorator import computation_function
|
|
42
|
+
from sigima.proc.image.base import dst_1_to_1_signal
|
|
43
|
+
|
|
44
|
+
# NOTE: Only parameter classes DEFINED in this module should be included in __all__.
|
|
45
|
+
# Parameter classes imported from other modules (like sigima.proc.base) should NOT
|
|
46
|
+
# be re-exported to avoid Sphinx cross-reference conflicts. The sigima.params module
|
|
47
|
+
# serves as the central API point that imports and re-exports all parameter classes.
|
|
48
|
+
__all__ = [
|
|
49
|
+
"AverageProfileParam",
|
|
50
|
+
"LineProfileParam",
|
|
51
|
+
"ROIGridParam",
|
|
52
|
+
"RadialProfileParam",
|
|
53
|
+
"SegmentProfileParam",
|
|
54
|
+
"average_profile",
|
|
55
|
+
"extract_roi",
|
|
56
|
+
"extract_rois",
|
|
57
|
+
"generate_image_grid_roi",
|
|
58
|
+
"line_profile",
|
|
59
|
+
"radial_profile",
|
|
60
|
+
"segment_profile",
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@computation_function()
|
|
65
|
+
def extract_rois(src: ImageObj, params: list[ROI2DParam]) -> ImageObj:
|
|
66
|
+
"""Extract multiple regions of interest from data
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
src: input image object
|
|
70
|
+
params: list of ROI parameters
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Output image object
|
|
74
|
+
"""
|
|
75
|
+
# Initialize ix0, iy0 with maximum values:
|
|
76
|
+
iy0, ix0 = iymax, ixmax = src.data.shape
|
|
77
|
+
# Initialize ix1, iy1 with minimum values:
|
|
78
|
+
iy1, ix1 = iymin, ixmin = 0, 0
|
|
79
|
+
for p in params:
|
|
80
|
+
x0i, y0i, x1i, y1i = p.get_bounding_box_indices(src)
|
|
81
|
+
ix0, iy0, ix1, iy1 = min(ix0, x0i), min(iy0, y0i), max(ix1, x1i), max(iy1, y1i)
|
|
82
|
+
ix0, iy0 = max(ix0, ixmin), max(iy0, iymin)
|
|
83
|
+
ix1, iy1 = min(ix1, ixmax), min(iy1, iymax)
|
|
84
|
+
|
|
85
|
+
suffix = None
|
|
86
|
+
if len(params) == 1:
|
|
87
|
+
p = params[0]
|
|
88
|
+
suffix = p.get_suffix()
|
|
89
|
+
dst = dst_1_to_1(src, "extract_rois", suffix)
|
|
90
|
+
if src.is_uniform_coords:
|
|
91
|
+
dst.set_uniform_coords(
|
|
92
|
+
dst.dx, dst.dy, dst.x0 + ix0 * src.dx, dst.y0 + iy0 * src.dy
|
|
93
|
+
)
|
|
94
|
+
else:
|
|
95
|
+
dst.set_coords(src.xcoords[iy0:iy1], src.ycoords[ix0:ix1])
|
|
96
|
+
dst.roi = None
|
|
97
|
+
|
|
98
|
+
src2 = src.copy()
|
|
99
|
+
src2.roi = ImageROI.from_params(src2, params)
|
|
100
|
+
src2.data[src2.maskdata] = 0
|
|
101
|
+
dst.data = src2.data[iy0:iy1, ix0:ix1]
|
|
102
|
+
return dst
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@computation_function()
|
|
106
|
+
def extract_roi(src: ImageObj, p: ROI2DParam) -> ImageObj:
|
|
107
|
+
"""Extract single ROI
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
src: input image object
|
|
111
|
+
p: ROI parameters
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Output image object
|
|
115
|
+
"""
|
|
116
|
+
dst = dst_1_to_1(src, "extract_roi", p.get_suffix())
|
|
117
|
+
dst.data = p.get_data(src).copy()
|
|
118
|
+
dst.roi = p.get_extracted_roi(src)
|
|
119
|
+
x0, y0, _x1, _y1 = p.get_bounding_box_physical()
|
|
120
|
+
if src.is_uniform_coords:
|
|
121
|
+
dst.set_uniform_coords(dst.dx, dst.dy, dst.x0 + x0, dst.y0 + y0)
|
|
122
|
+
else:
|
|
123
|
+
dst.set_coords(src.xcoords + x0, src.ycoords + y0)
|
|
124
|
+
return dst
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class Direction(gds.LabeledEnum):
|
|
128
|
+
"""Direction choice"""
|
|
129
|
+
|
|
130
|
+
INCREASING = "increasing", _("increasing")
|
|
131
|
+
DECREASING = "decreasing", _("decreasing")
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class ROIGridParam(gds.DataSet):
|
|
135
|
+
"""ROI Grid parameters"""
|
|
136
|
+
|
|
137
|
+
# optional Python-level hook, no Qt
|
|
138
|
+
on_geometry_changed: Callable | None = None
|
|
139
|
+
|
|
140
|
+
# pylint: disable=unused-argument
|
|
141
|
+
def geometry_changed(self, item, value) -> None:
|
|
142
|
+
"""Notify host (if any) that geometry changed."""
|
|
143
|
+
if callable(self.on_geometry_changed):
|
|
144
|
+
self.on_geometry_changed() # pylint: disable=not-callable
|
|
145
|
+
|
|
146
|
+
_b_group0 = gds.BeginGroup(_("Geometry"))
|
|
147
|
+
ny = gds.IntItem(f"N<sub>y</sub> ({_('rows')})", default=3, nonzero=True).set_prop(
|
|
148
|
+
"display", callback=geometry_changed
|
|
149
|
+
)
|
|
150
|
+
nx = (
|
|
151
|
+
gds.IntItem(f"N<sub>x</sub> ({_('columns')})", default=3, nonzero=True)
|
|
152
|
+
.set_prop("display", callback=geometry_changed)
|
|
153
|
+
.set_pos(col=1)
|
|
154
|
+
)
|
|
155
|
+
xtranslation = gds.IntItem(
|
|
156
|
+
_("X translation"),
|
|
157
|
+
default=50,
|
|
158
|
+
min=0,
|
|
159
|
+
max=100,
|
|
160
|
+
unit="%",
|
|
161
|
+
slider=True,
|
|
162
|
+
).set_prop("display", callback=geometry_changed)
|
|
163
|
+
ytranslation = gds.IntItem(
|
|
164
|
+
_("Y translation"),
|
|
165
|
+
default=50,
|
|
166
|
+
min=0,
|
|
167
|
+
max=100,
|
|
168
|
+
unit="%",
|
|
169
|
+
slider=True,
|
|
170
|
+
).set_prop("display", callback=geometry_changed)
|
|
171
|
+
xsize = gds.IntItem(
|
|
172
|
+
f"X size ({_('column size')})",
|
|
173
|
+
default=50,
|
|
174
|
+
min=0,
|
|
175
|
+
max=100,
|
|
176
|
+
unit="%",
|
|
177
|
+
slider=True,
|
|
178
|
+
).set_prop("display", callback=geometry_changed)
|
|
179
|
+
ysize = gds.IntItem(
|
|
180
|
+
f"Y size ({_('row size')})",
|
|
181
|
+
default=50,
|
|
182
|
+
min=0,
|
|
183
|
+
max=100,
|
|
184
|
+
unit="%",
|
|
185
|
+
slider=True,
|
|
186
|
+
).set_prop("display", callback=geometry_changed)
|
|
187
|
+
_e_group0 = gds.EndGroup(_("Geometry"))
|
|
188
|
+
_b_group1 = gds.BeginGroup(_("ROI titles"))
|
|
189
|
+
base_name = gds.StringItem(_("Base name"), default="ROI").set_prop(
|
|
190
|
+
"display", callback=geometry_changed
|
|
191
|
+
)
|
|
192
|
+
name_pattern = gds.StringItem(
|
|
193
|
+
_("Name pattern"), default="{base}({r},{c})"
|
|
194
|
+
).set_prop("display", callback=geometry_changed)
|
|
195
|
+
xdirection = gds.ChoiceItem(_("X direction"), Direction).set_prop(
|
|
196
|
+
"display", callback=geometry_changed
|
|
197
|
+
)
|
|
198
|
+
ydirection = (
|
|
199
|
+
gds.ChoiceItem(_("Y direction"), Direction)
|
|
200
|
+
.set_prop("display", callback=geometry_changed)
|
|
201
|
+
.set_pos(col=1)
|
|
202
|
+
)
|
|
203
|
+
_e_group1 = gds.EndGroup(_("ROI titles"))
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def generate_image_grid_roi(src: ImageObj, p: ROIGridParam) -> ImageROI:
|
|
207
|
+
"""Create a grid of rectangular ROIs from an image object.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
obj: The image object to create the ROI for.
|
|
211
|
+
p: ROIGridParam object containing the grid parameters.
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
The created ROI object.
|
|
215
|
+
"""
|
|
216
|
+
dx_cell = src.width / p.nx
|
|
217
|
+
dy_cell = src.height / p.ny
|
|
218
|
+
dx = dx_cell * p.xsize / 100.0
|
|
219
|
+
dy = dy_cell * p.ysize / 100.0
|
|
220
|
+
xtrans = src.width * (p.xtranslation - 50.0) / 100.0
|
|
221
|
+
ytrans = src.height * (p.ytranslation - 50.0) / 100.0
|
|
222
|
+
lbl_rows = range(p.ny)
|
|
223
|
+
if p.ydirection == Direction.DECREASING:
|
|
224
|
+
lbl_rows = range(p.ny - 1, -1, -1)
|
|
225
|
+
lbl_cols = range(p.nx)
|
|
226
|
+
if p.xdirection == Direction.DECREASING:
|
|
227
|
+
lbl_cols = range(p.nx - 1, -1, -1)
|
|
228
|
+
ptn: str = p.name_pattern
|
|
229
|
+
roi = ImageROI()
|
|
230
|
+
for ir in range(p.ny):
|
|
231
|
+
for ic in range(p.nx):
|
|
232
|
+
x0 = src.x0 + (ic + 0.5) * dx_cell + xtrans - 0.5 * dx
|
|
233
|
+
y0 = src.y0 + (ir + 0.5) * dy_cell + ytrans - 0.5 * dy
|
|
234
|
+
nir, nic = lbl_rows[ir], lbl_cols[ic]
|
|
235
|
+
try:
|
|
236
|
+
title = ptn.format(base=p.base_name, r=nir + 1, c=nic + 1)
|
|
237
|
+
except Exception: # pylint: disable=broad-except
|
|
238
|
+
title = f"ROI({nir + 1},{nic + 1})"
|
|
239
|
+
roi.add_roi(RectangularROI([x0, y0, dx, dy], indices=False, title=title))
|
|
240
|
+
return roi
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
class LineProfileParam(gds.DataSet):
|
|
244
|
+
"""Horizontal or vertical profile parameters"""
|
|
245
|
+
|
|
246
|
+
_prop = gds.GetAttrProp("direction")
|
|
247
|
+
_directions = (("horizontal", _("horizontal")), ("vertical", _("vertical")))
|
|
248
|
+
direction = gds.ChoiceItem(_("Direction"), _directions, radio=True).set_prop(
|
|
249
|
+
"display", store=_prop
|
|
250
|
+
)
|
|
251
|
+
row = gds.IntItem(_("Row"), default=0, min=0).set_prop(
|
|
252
|
+
"display", active=gds.FuncProp(_prop, lambda x: x == "horizontal")
|
|
253
|
+
)
|
|
254
|
+
col = gds.IntItem(_("Column"), default=0, min=0).set_prop(
|
|
255
|
+
"display", active=gds.FuncProp(_prop, lambda x: x == "vertical")
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
@computation_function()
|
|
260
|
+
def line_profile(src: ImageObj, p: LineProfileParam) -> SignalObj:
|
|
261
|
+
"""Compute horizontal or vertical profile
|
|
262
|
+
|
|
263
|
+
Args:
|
|
264
|
+
src: input image object
|
|
265
|
+
p: parameters
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
Signal object with the profile
|
|
269
|
+
"""
|
|
270
|
+
data = src.get_masked_view()
|
|
271
|
+
p.row = min(p.row, data.shape[0] - 1)
|
|
272
|
+
p.col = min(p.col, data.shape[1] - 1)
|
|
273
|
+
if p.direction == "horizontal":
|
|
274
|
+
suffix, shape_index, pdata = f"row={p.row}", 1, data[p.row, :]
|
|
275
|
+
else:
|
|
276
|
+
suffix, shape_index, pdata = f"col={p.col}", 0, data[:, p.col]
|
|
277
|
+
pdata: ma.MaskedArray
|
|
278
|
+
x = np.arange(data.shape[shape_index])[~pdata.mask]
|
|
279
|
+
y = np.array(pdata, dtype=float)[~pdata.mask]
|
|
280
|
+
dst = dst_1_to_1_signal(src, "profile", suffix)
|
|
281
|
+
dst.set_xydata(x, y)
|
|
282
|
+
return dst
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
class SegmentProfileParam(gds.DataSet):
|
|
286
|
+
"""Segment profile parameters"""
|
|
287
|
+
|
|
288
|
+
row1 = gds.IntItem(_("Start row"), default=0, min=0)
|
|
289
|
+
col1 = gds.IntItem(_("Start column"), default=0, min=0)
|
|
290
|
+
row2 = gds.IntItem(_("End row"), default=0, min=0)
|
|
291
|
+
col2 = gds.IntItem(_("End column"), default=0, min=0)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def csline(data: np.ndarray, row0, col0, row1, col1) -> tuple[np.ndarray, np.ndarray]:
|
|
295
|
+
"""Return intensity profile of data along a line
|
|
296
|
+
|
|
297
|
+
Args:
|
|
298
|
+
data: 2D array
|
|
299
|
+
row0, col0: start point
|
|
300
|
+
row1, col1: end point
|
|
301
|
+
"""
|
|
302
|
+
# Keep coordinates inside the image
|
|
303
|
+
row0 = max(0, min(row0, data.shape[0] - 1))
|
|
304
|
+
col0 = max(0, min(col0, data.shape[1] - 1))
|
|
305
|
+
row1 = max(0, min(row1, data.shape[0] - 1))
|
|
306
|
+
col1 = max(0, min(col1, data.shape[1] - 1))
|
|
307
|
+
# Keep coordinates in the right order
|
|
308
|
+
row0, row1 = min(row0, row1), max(row0, row1)
|
|
309
|
+
col0, col1 = min(col0, col1), max(col0, col1)
|
|
310
|
+
# Extract the line
|
|
311
|
+
line = np.zeros((2, max(abs(row1 - row0), abs(col1 - col0)) + 1), dtype=int)
|
|
312
|
+
line[0, :] = np.linspace(row0, row1, line.shape[1]).astype(int)
|
|
313
|
+
line[1, :] = np.linspace(col0, col1, line.shape[1]).astype(int)
|
|
314
|
+
# Interpolate the line
|
|
315
|
+
y = np.ma.array(data[line[0], line[1]], float).filled(np.nan)
|
|
316
|
+
x = np.arange(y.size)
|
|
317
|
+
return x, y
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
@computation_function()
|
|
321
|
+
def segment_profile(src: ImageObj, p: SegmentProfileParam) -> SignalObj:
|
|
322
|
+
"""Compute segment profile
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
src: input image object
|
|
326
|
+
p: parameters
|
|
327
|
+
|
|
328
|
+
Returns:
|
|
329
|
+
Signal object with the segment profile
|
|
330
|
+
"""
|
|
331
|
+
data = src.get_masked_view()
|
|
332
|
+
p.row1 = min(p.row1, data.shape[0] - 1)
|
|
333
|
+
p.col1 = min(p.col1, data.shape[1] - 1)
|
|
334
|
+
p.row2 = min(p.row2, data.shape[0] - 1)
|
|
335
|
+
p.col2 = min(p.col2, data.shape[1] - 1)
|
|
336
|
+
suffix = f"({p.row1}, {p.col1})-({p.row2}, {p.col2})"
|
|
337
|
+
x, y = csline(data, p.row1, p.col1, p.row2, p.col2)
|
|
338
|
+
x, y = x[~np.isnan(y)], y[~np.isnan(y)] # Remove NaN values
|
|
339
|
+
dst = dst_1_to_1_signal(src, "segment_profile", suffix)
|
|
340
|
+
dst.set_xydata(np.array(x, dtype=float), np.array(y, dtype=float))
|
|
341
|
+
return dst
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
class AverageProfileParam(gds.DataSet):
|
|
345
|
+
"""Average horizontal or vertical profile parameters"""
|
|
346
|
+
|
|
347
|
+
_directions = (("horizontal", _("horizontal")), ("vertical", _("vertical")))
|
|
348
|
+
direction = gds.ChoiceItem(_("Direction"), _directions, radio=True)
|
|
349
|
+
_hgroup_begin = gds.BeginGroup(_("Profile rectangular area"))
|
|
350
|
+
row1 = gds.IntItem(_("Row 1"), default=0, min=0)
|
|
351
|
+
row2 = gds.IntItem(_("Row 2"), default=-1, min=-1)
|
|
352
|
+
col1 = gds.IntItem(_("Column 1"), default=0, min=0)
|
|
353
|
+
col2 = gds.IntItem(_("Column 2"), default=-1, min=-1)
|
|
354
|
+
_hgroup_end = gds.EndGroup(_("Profile rectangular area"))
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
@computation_function()
|
|
358
|
+
def average_profile(src: ImageObj, p: AverageProfileParam) -> SignalObj:
|
|
359
|
+
"""Compute horizontal or vertical average profile
|
|
360
|
+
|
|
361
|
+
Args:
|
|
362
|
+
src: input image object
|
|
363
|
+
p: parameters
|
|
364
|
+
|
|
365
|
+
Returns:
|
|
366
|
+
Signal object with the average profile
|
|
367
|
+
"""
|
|
368
|
+
data = src.get_masked_view()
|
|
369
|
+
if p.row2 == -1:
|
|
370
|
+
p.row2 = data.shape[0] - 1
|
|
371
|
+
if p.col2 == -1:
|
|
372
|
+
p.col2 = data.shape[1] - 1
|
|
373
|
+
if p.row1 > p.row2:
|
|
374
|
+
p.row1, p.row2 = p.row2, p.row1
|
|
375
|
+
if p.col1 > p.col2:
|
|
376
|
+
p.col1, p.col2 = p.col2, p.col1
|
|
377
|
+
p.row1 = min(p.row1, data.shape[0] - 1)
|
|
378
|
+
p.row2 = min(p.row2, data.shape[0] - 1)
|
|
379
|
+
p.col1 = min(p.col1, data.shape[1] - 1)
|
|
380
|
+
p.col2 = min(p.col2, data.shape[1] - 1)
|
|
381
|
+
suffix = f"{p.direction}, rows=[{p.row1}, {p.row2}], cols=[{p.col1}, {p.col2}]"
|
|
382
|
+
if p.direction == "horizontal":
|
|
383
|
+
x, axis = np.arange(p.col1, p.col2 + 1), 0
|
|
384
|
+
else:
|
|
385
|
+
x, axis = np.arange(p.row1, p.row2 + 1), 1
|
|
386
|
+
y = ma.mean(data[p.row1 : p.row2 + 1, p.col1 : p.col2 + 1], axis=axis)
|
|
387
|
+
dst = dst_1_to_1_signal(src, "average_profile", suffix)
|
|
388
|
+
dst.set_xydata(x, y)
|
|
389
|
+
return dst
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
class RadialProfileParam(gds.DataSet):
|
|
393
|
+
"""Radial profile parameters"""
|
|
394
|
+
|
|
395
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
396
|
+
super().__init__(*args, **kwargs)
|
|
397
|
+
self.__obj: ImageObj | None = None
|
|
398
|
+
|
|
399
|
+
def update_from_obj(self, obj: ImageObj) -> None:
|
|
400
|
+
"""Update parameters from image"""
|
|
401
|
+
self.__obj = obj
|
|
402
|
+
self.x0 = obj.xc
|
|
403
|
+
self.y0 = obj.yc
|
|
404
|
+
|
|
405
|
+
def choice_callback(self, item, value): # pylint: disable=unused-argument
|
|
406
|
+
"""Callback for choice item"""
|
|
407
|
+
if value == "centroid":
|
|
408
|
+
self.y0, self.x0 = sigima.tools.image.get_centroid_fourier(
|
|
409
|
+
self.__obj.get_masked_view()
|
|
410
|
+
)
|
|
411
|
+
elif value == "center":
|
|
412
|
+
self.x0, self.y0 = self.__obj.xc, self.__obj.yc
|
|
413
|
+
|
|
414
|
+
_prop = gds.GetAttrProp("center")
|
|
415
|
+
center = gds.ChoiceItem(
|
|
416
|
+
_("Center position"),
|
|
417
|
+
(
|
|
418
|
+
("centroid", _("Image centroid")),
|
|
419
|
+
("center", _("Image center")),
|
|
420
|
+
("user", _("User-defined")),
|
|
421
|
+
),
|
|
422
|
+
default="centroid",
|
|
423
|
+
).set_prop("display", store=_prop, callback=choice_callback)
|
|
424
|
+
|
|
425
|
+
_func_prop = gds.FuncProp(_prop, lambda x: x == "user")
|
|
426
|
+
_xyl = "<sub>" + _("Center") + "</sub>"
|
|
427
|
+
x0 = gds.FloatItem(f"X{_xyl}", default=0.0, unit="pixel").set_prop(
|
|
428
|
+
"display", active=_func_prop
|
|
429
|
+
)
|
|
430
|
+
y0 = gds.FloatItem(f"Y{_xyl}", default=0.0, unit="pixel").set_prop(
|
|
431
|
+
"display", active=_func_prop
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
@computation_function()
|
|
436
|
+
def radial_profile(src: ImageObj, p: RadialProfileParam) -> SignalObj:
|
|
437
|
+
"""Compute radial profile around the centroid
|
|
438
|
+
with :py:func:`sigima.tools.image.get_radial_profile`
|
|
439
|
+
|
|
440
|
+
Args:
|
|
441
|
+
src: input image object
|
|
442
|
+
p: parameters
|
|
443
|
+
|
|
444
|
+
Returns:
|
|
445
|
+
Signal object with the radial profile
|
|
446
|
+
"""
|
|
447
|
+
data = src.get_masked_view()
|
|
448
|
+
if p.center == "centroid":
|
|
449
|
+
y0, x0 = sigima.tools.image.get_centroid_fourier(data)
|
|
450
|
+
elif p.center == "center":
|
|
451
|
+
x0, y0 = src.xc, src.yc
|
|
452
|
+
else:
|
|
453
|
+
x0, y0 = p.x0, p.y0
|
|
454
|
+
suffix = f"center=({x0:.3f}, {y0:.3f})"
|
|
455
|
+
dst = dst_1_to_1_signal(src, "radial_profile", suffix)
|
|
456
|
+
x, y = sigima.tools.image.get_radial_profile(data, (x0, y0))
|
|
457
|
+
dst.set_xydata(x, y)
|
|
458
|
+
return dst
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.
|
|
2
|
+
|
|
3
|
+
"""Filtering computation module.
|
|
4
|
+
--------------------------------
|
|
5
|
+
|
|
6
|
+
This module provides spatial and frequency-based filtering operations for images.
|
|
7
|
+
Filtering functions are essential for enhancing image quality and removing noise.
|
|
8
|
+
|
|
9
|
+
Main features include:
|
|
10
|
+
* Gaussian, median, moving average and Wiener filters
|
|
11
|
+
* Butterworth and frequency domain Gaussian filters.
|
|
12
|
+
|
|
13
|
+
Filtering functions are essential for enhancing image quality
|
|
14
|
+
and removing noise prior to further analysis.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
# pylint: disable=invalid-name # Allows short names like x, y...
|
|
18
|
+
|
|
19
|
+
# Note:
|
|
20
|
+
# ----
|
|
21
|
+
# - All `guidata.dataset.DataSet` parameter classes must also be imported in the
|
|
22
|
+
# `sigima.params` module.
|
|
23
|
+
# - All functions decorated with `computation_function` must be imported in the upper
|
|
24
|
+
# level `sigima.proc.image` module.
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import guidata.dataset as gds # type: ignore[import]
|
|
29
|
+
import scipy.ndimage as spi # type: ignore[import]
|
|
30
|
+
import scipy.signal as sps # type: ignore[import]
|
|
31
|
+
from skimage import filters # type: ignore[import]
|
|
32
|
+
|
|
33
|
+
import sigima.tools.image
|
|
34
|
+
from sigima.config import _
|
|
35
|
+
from sigima.objects.image import ImageObj
|
|
36
|
+
from sigima.proc.base import (
|
|
37
|
+
GaussianParam,
|
|
38
|
+
MovingAverageParam,
|
|
39
|
+
MovingMedianParam,
|
|
40
|
+
dst_1_to_1,
|
|
41
|
+
)
|
|
42
|
+
from sigima.proc.decorator import computation_function
|
|
43
|
+
from sigima.proc.image.base import Wrap1to1Func, restore_data_outside_roi
|
|
44
|
+
|
|
45
|
+
# NOTE: Only parameter classes DEFINED in this module should be included in __all__.
|
|
46
|
+
# Parameter classes imported from other modules (like sigima.proc.base) should NOT
|
|
47
|
+
# be re-exported to avoid Sphinx cross-reference conflicts. The sigima.params module
|
|
48
|
+
# serves as the central API point that imports and re-exports all parameter classes.
|
|
49
|
+
__all__ = [
|
|
50
|
+
"ButterworthParam",
|
|
51
|
+
"GaussianFreqFilterParam",
|
|
52
|
+
"butterworth",
|
|
53
|
+
"gaussian_filter",
|
|
54
|
+
"gaussian_freq_filter",
|
|
55
|
+
"moving_average",
|
|
56
|
+
"moving_median",
|
|
57
|
+
"wiener",
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
# MARK: Noise reduction filters
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@computation_function()
|
|
64
|
+
def gaussian_filter(src: ImageObj, p: GaussianParam) -> ImageObj:
|
|
65
|
+
"""Compute gaussian filter with :py:func:`scipy.ndimage.gaussian_filter`.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
src: Input image object.
|
|
69
|
+
p: Parameters.
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Output image object.
|
|
73
|
+
"""
|
|
74
|
+
return Wrap1to1Func(spi.gaussian_filter, sigma=p.sigma)(src)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@computation_function()
|
|
78
|
+
def moving_average(src: ImageObj, p: MovingAverageParam) -> ImageObj:
|
|
79
|
+
"""Compute moving average with :py:func:`scipy.ndimage.uniform_filter`.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
src: Input image object.
|
|
83
|
+
p: Parameters.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
Output image object.
|
|
87
|
+
"""
|
|
88
|
+
return Wrap1to1Func(
|
|
89
|
+
spi.uniform_filter, size=p.n, mode=p.mode, func_name="moving_average"
|
|
90
|
+
)(src)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@computation_function()
|
|
94
|
+
def moving_median(src: ImageObj, p: MovingMedianParam) -> ImageObj:
|
|
95
|
+
"""Compute moving median with :py:func:`scipy.ndimage.median_filter`.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
src: Input image object.
|
|
99
|
+
p: Parameters.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
Output image object.
|
|
103
|
+
"""
|
|
104
|
+
return Wrap1to1Func(
|
|
105
|
+
spi.median_filter, size=p.n, mode=p.mode, func_name="moving_median"
|
|
106
|
+
)(src)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@computation_function()
|
|
110
|
+
def wiener(src: ImageObj) -> ImageObj:
|
|
111
|
+
"""Compute Wiener filter with :py:func:`scipy.signal.wiener`
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
src: Input image object.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
Output image object.
|
|
118
|
+
"""
|
|
119
|
+
return Wrap1to1Func(sps.wiener)(src)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class ButterworthParam(gds.DataSet):
|
|
123
|
+
"""Butterworth filter parameters."""
|
|
124
|
+
|
|
125
|
+
cut_off = gds.FloatItem(
|
|
126
|
+
_("Cut-off frequency ratio"),
|
|
127
|
+
default=0.005,
|
|
128
|
+
min=0.0,
|
|
129
|
+
max=0.5,
|
|
130
|
+
help=_("Cut-off frequency ratio"),
|
|
131
|
+
)
|
|
132
|
+
high_pass = gds.BoolItem(
|
|
133
|
+
_("High-pass filter"),
|
|
134
|
+
default=False,
|
|
135
|
+
help=_("If True, apply high-pass filter instead of low-pass"),
|
|
136
|
+
)
|
|
137
|
+
order = gds.IntItem(
|
|
138
|
+
_("Order"),
|
|
139
|
+
default=2,
|
|
140
|
+
min=1,
|
|
141
|
+
help=_("Order of the Butterworth filter"),
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# MARK: Frequency filters
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@computation_function()
|
|
149
|
+
def butterworth(src: ImageObj, p: ButterworthParam) -> ImageObj:
|
|
150
|
+
"""Compute Butterworth filter with :py:func:`skimage.filters.butterworth`.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
src: Input image object.
|
|
154
|
+
p: Parameters.
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
Output image object.
|
|
158
|
+
"""
|
|
159
|
+
dst = dst_1_to_1(
|
|
160
|
+
src,
|
|
161
|
+
"butterworth",
|
|
162
|
+
f"cut_off={p.cut_off:.3f}, order={p.order}, high_pass={p.high_pass}",
|
|
163
|
+
)
|
|
164
|
+
dst.data = filters.butterworth(src.data, p.cut_off, p.high_pass, p.order)
|
|
165
|
+
restore_data_outside_roi(dst, src)
|
|
166
|
+
return dst
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class GaussianFreqFilterParam(GaussianParam):
|
|
170
|
+
"""Parameters for Gaussian filter applied in the frequency domain."""
|
|
171
|
+
|
|
172
|
+
sigma = gds.FloatItem(
|
|
173
|
+
"σ",
|
|
174
|
+
default=1.0,
|
|
175
|
+
unit="pixel⁻¹",
|
|
176
|
+
min=0.0,
|
|
177
|
+
help=_("Standard deviation of the Gaussian filter"),
|
|
178
|
+
)
|
|
179
|
+
f0 = gds.FloatItem(
|
|
180
|
+
_("Center frequency"),
|
|
181
|
+
default=1.0,
|
|
182
|
+
unit="pixel⁻¹",
|
|
183
|
+
min=0.0,
|
|
184
|
+
help=_("Center frequency of the Gaussian filter"),
|
|
185
|
+
)
|
|
186
|
+
sigma = gds.FloatItem(
|
|
187
|
+
"σ",
|
|
188
|
+
default=0.5,
|
|
189
|
+
unit="pixels⁻¹",
|
|
190
|
+
min=0.0,
|
|
191
|
+
help=_("Standard deviation of the Gaussian filter"),
|
|
192
|
+
)
|
|
193
|
+
ifft_result_type = gds.ChoiceItem(
|
|
194
|
+
_("Inverse FFT result"),
|
|
195
|
+
(("real", _("Real part")), ("abs", _("Absolute value"))),
|
|
196
|
+
default="real",
|
|
197
|
+
help=_("How to return the inverse FFT result"),
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
@computation_function()
|
|
202
|
+
def gaussian_freq_filter(src: ImageObj, p: GaussianFreqFilterParam) -> ImageObj:
|
|
203
|
+
"""Apply a Gaussian filter in the frequency domain.
|
|
204
|
+
|
|
205
|
+
Args:
|
|
206
|
+
src: Source image object.
|
|
207
|
+
p: Parameters.
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
Output image object.
|
|
211
|
+
"""
|
|
212
|
+
dst = dst_1_to_1(
|
|
213
|
+
src,
|
|
214
|
+
"frequency_domain_gaussian_filter",
|
|
215
|
+
f"sigma={p.sigma:.3f}, f0={p.f0:.3f}",
|
|
216
|
+
)
|
|
217
|
+
dst.data = sigima.tools.image.gaussian_freq_filter(src.data, p.f0, p.sigma)
|
|
218
|
+
restore_data_outside_roi(dst, src)
|
|
219
|
+
return dst
|