diff --git a/nion/data/Core.py b/nion/data/Core.py index 647e231..c557918 100755 --- a/nion/data/Core.py +++ b/nion/data/Core.py @@ -908,6 +908,44 @@ def calculate_data() -> _ImageDataType: return DataAndMetadata.new_data_and_metadata(data=calculate_data(), intensity_calibration=data_and_metadata.intensity_calibration, dimensional_calibrations=data_and_metadata.dimensional_calibrations) +def function_gaussian_window(data_shape: DataAndMetadata.ShapeType, sigma: float) -> DataAndMetadata.DataAndMetadata: + sigma = sigma if sigma > 0.0 else 1.0 # clamp sigma to avoid divide by zero + if len(data_shape) == 1: + w = data_shape[0] + return DataAndMetadata.new_data_and_metadata(scipy.signal.windows.gaussian(w, std=sigma)) + elif len(data_shape) == 2: + # uses circularly rotated approach of generating 2D filter from 1D + h, w = data_shape + y, x = numpy.meshgrid(numpy.arange(0, h) - (h - 1) / 2, numpy.arange(0, w) - (w - 1) / 2, indexing='ij') + r_squared = y * y + x * x + return DataAndMetadata.new_data_and_metadata(numpy.exp(-0.5 * r_squared / (sigma * sigma))) + raise ValueError("Window input data must be 1D or 2D") + + +def function_hamming_window(data_shape: DataAndMetadata.ShapeType) -> DataAndMetadata.DataAndMetadata: + if len(data_shape) == 1: + return DataAndMetadata.new_data_and_metadata(scipy.signal.windows.hamming(data_shape[0])) + elif len(data_shape) == 2: + # uses outer product approach of generating 2D filter from 1D + h, w = data_shape + w0 = numpy.reshape(scipy.signal.windows.hamming(w), (1, w)) + w1 = numpy.reshape(scipy.signal.windows.hamming(h), (h, 1)) + return DataAndMetadata.new_data_and_metadata(w0 * w1) + raise ValueError("Window input data must be 1D or 2D") + + +def function_hann_window(data_shape: DataAndMetadata.ShapeType) -> DataAndMetadata.DataAndMetadata: + if len(data_shape) == 1: + return DataAndMetadata.new_data_and_metadata(scipy.signal.windows.hann(data_shape[0])) + elif len(data_shape) == 2: + # uses outer product approach of generating 2D filter from 1D + h, w = data_shape + w0 = numpy.reshape(scipy.signal.windows.hann(w), (1, w)) + w1 = numpy.reshape(scipy.signal.windows.hann(h), (h, 1)) + return DataAndMetadata.new_data_and_metadata(w0 * w1) + raise ValueError("Window input data must be 1D or 2D") + + def function_transpose_flip(data_and_metadata_in: _DataAndMetadataLike, transpose: bool = False, flip_v: bool = False, flip_h: bool = False) -> DataAndMetadata.DataAndMetadata: data_and_metadata = DataAndMetadata.promote_ndarray(data_and_metadata_in) diff --git a/nion/data/test/Core_test.py b/nion/data/test/Core_test.py index b6e557a..503dcfb 100755 --- a/nion/data/test/Core_test.py +++ b/nion/data/test/Core_test.py @@ -3,7 +3,6 @@ import io import logging import math -import os import typing import unittest @@ -1429,6 +1428,18 @@ def test_warp_rgba(self) -> None: dst = Core.function_warp(src, coords) self._validate_warp_shape(src, dst, coords, is_channel_data=True) + def test_gaussian_window(self) -> None: + sigma = 8.0 + size = 17 # use an odd size so the center is well-defined + reference_data = scipy.signal.windows.gaussian(size, std=sigma) + data_1d = Core.function_gaussian_window((size,), sigma).data + data_2d = Core.function_gaussian_window((size, size), sigma).data + for i in range(size): + self.assertAlmostEqual(reference_data[i], data_1d[i]) + # check that center column and center row are close + self.assertAlmostEqual(reference_data[i], data_2d[i, size//2]) + self.assertAlmostEqual(reference_data[i], data_2d[size//2, i]) + if __name__ == '__main__': logging.getLogger().setLevel(logging.DEBUG) diff --git a/nion/data/xdata_1_0.py b/nion/data/xdata_1_0.py index d8f13d2..2e32c17 100755 --- a/nion/data/xdata_1_0.py +++ b/nion/data/xdata_1_0.py @@ -257,11 +257,32 @@ def median_filter(data_and_metadata: _DataAndMetadataLike, size: int) -> DataAnd def uniform_filter(data_and_metadata: _DataAndMetadataLike, size: int) -> DataAndMetadata.DataAndMetadata: return Core.function_uniform_filter(data_and_metadata, size) -def transpose_flip(data_and_metadata: _DataAndMetadataLike, transpose: bool=False, flip_v: bool=False, flip_h: bool=False) -> DataAndMetadata.DataAndMetadata: - return Core.function_transpose_flip(data_and_metadata, transpose, flip_v, flip_h) +# windows + +def gaussian_window(data_shape: DataAndMetadata.Shape2dType, sigma: float) -> DataAndMetadata.DataAndMetadata: + return Core.function_gaussian_window(data_shape, sigma) + +def hamming_window(data_shape: DataAndMetadata.Shape2dType) -> DataAndMetadata.DataAndMetadata: + return Core.function_hamming_window(data_shape) + +def hann_window(data_shape: DataAndMetadata.Shape2dType) -> DataAndMetadata.DataAndMetadata: + return Core.function_hann_window(data_shape) + +# scalar functions + +def sum_scalar(data_and_metadata_in: _DataAndMetadataLike) -> DataAndMetadata.ScalarAndMetadata: + data_and_metadata = DataAndMetadata.promote_ndarray(data_and_metadata_in) + return DataAndMetadata.ScalarAndMetadata.from_value(numpy.sum(data_and_metadata), data_and_metadata.intensity_calibration) + +def mean_scalar(data_and_metadata_in: _DataAndMetadataLike) -> DataAndMetadata.ScalarAndMetadata: + data_and_metadata = DataAndMetadata.promote_ndarray(data_and_metadata_in) + return DataAndMetadata.ScalarAndMetadata.from_value(numpy.average(data_and_metadata), data_and_metadata.intensity_calibration) # miscellaneous +def transpose_flip(data_and_metadata: _DataAndMetadataLike, transpose: bool=False, flip_v: bool=False, flip_h: bool=False) -> DataAndMetadata.DataAndMetadata: + return Core.function_transpose_flip(data_and_metadata, transpose, flip_v, flip_h) + def histogram(data_and_metadata: _DataAndMetadataLike, bins: int) -> DataAndMetadata.DataAndMetadata: return Core.function_histogram(data_and_metadata, bins)