diff --git a/Python/Filters/GrayscaleNormalizer.md b/Python/Filters/GrayscaleNormalizer.md new file mode 100644 index 00000000..de5b853a --- /dev/null +++ b/Python/Filters/GrayscaleNormalizer.md @@ -0,0 +1,47 @@ +# Normalize Grayscale Values [Python] # + +## Group (Subgroup) ## + +Processing(Normalization) + +## Description ## + +This **Filter** normalizes brightness and contrast values in a 3D grayscale data array. The **Filter** gets the averages each layer in the x, y, or z direction. + +## Parameters ## + +| Name | Type | Description | +|------|------|------| +| Slice Axis | int | The direction for each layer | +| Create New Array | boolean | If checked, a new array is created with the corrected values. Else, the inputted array is overwritten. | + + +## Required Geometry ## + +Image + +## Required Objects ## + +| Kind | Default Name | Type | Component Dimensions | Description | +|------|------|------|------|------| +| **Cell Data Array** | AttributeArray Name | Any | (3) | The selected data array to apply normalization | + +## Created Objects ## + +| Kind | Default Name | Type | Component Dimensions | Description | +|------|------|------|------|------| + | **Cell Data Array** |AttributeArray Name | same as Required Data Array | (3) | Optional array created to store normalized data | + + +## Example Pipelines ## + +List the names of the example pipelines where this filter is used. + +## License & Copyright ## + +Please see the description file distributed with this plugin. + +## DREAM3D Mailing Lists ## + +If you need more help with a filter, please consider asking your question on the DREAM3D Users mailing list: +https://groups.google.com/forum/?hl=en#!forum/dream3d-users diff --git a/Python/Filters/TDMStoH5.md b/Python/Filters/TDMStoH5.md index b0ba3331..e346cfba 100644 --- a/Python/Filters/TDMStoH5.md +++ b/Python/Filters/TDMStoH5.md @@ -4,6 +4,11 @@ Example(Sub Example) +## Required Conda Packages ## + ++ h5py ++ nptdms + ## Description ## This **Filter** converts a series of TDMS files into individual part files, where each part is stored in an HDF5 file. Three offsets can be used for the camera, diode, and laser. diff --git a/Python/Filters/d3d_review_filter_1.py b/Python/Filters/d3d_review_filter_1.py index 283c811d..0df82f42 100644 --- a/Python/Filters/d3d_review_filter_1.py +++ b/Python/Filters/d3d_review_filter_1.py @@ -58,14 +58,17 @@ from typing import List, Tuple, Union -from dream3d.Filter import Filter, FilterDelegatePy -from dream3d.simpl import BooleanFilterParameter, DataContainerArray, StringFilterParameter, InputFileFilterParameter, FilterDelegateCpp, FilterParameter, IntFilterParameter, DataArraySelectionFilterParameter, DataArrayPath -from dream3d.simpl import InputPathFilterParameter, FloatFilterParameter -class D3DReviewTestFilter(Filter): +from dream3d.Filter import Filter as SIMPLFilter +from dream3d.Filter import FilterDelegatePy as SIMPLFilterDelegatePy +from dream3d.simpl import * +import dream3d.simpl as simpl + + +class D3DReviewTestFilter(SIMPLFilter): def __init__(self) -> None: self.int_param: int = 5 - self.dap_param: DataArrayPath = DataArrayPath('', '', '') + self.dap_param: simpl.DataArrayPath = simpl.DataArrayPath('', '', '') self.str_param: str = "Something" self.bool_param:bool = False self.input_file_param:str = "/No/Path/anywhere.txt" @@ -74,6 +77,7 @@ def __init__(self) -> None: def _set_float_param(self, value: float) -> None: self.float_param = value + def _get_float_param(self) -> float: return self.float_param @@ -83,10 +87,10 @@ def _set_int(self, value: int) -> None: def _get_int(self) -> int: return self.int_param - def _set_dap(self, value: DataArrayPath) -> None: + def _set_dap(self, value: simpl.DataArrayPath) -> None: self.dap_param = value - def _get_dap(self) -> DataArrayPath: + def _get_dap(self) -> simpl.DataArrayPath: return self.dap_param def _get_str(self) -> str: @@ -113,7 +117,6 @@ def _get_input_path_param(self) -> str: def _set_input_path_param(self, value: str) -> None: self.input_path_param = value - @staticmethod def name() -> str: return 'D3DReviewTestFilter' @@ -142,34 +145,59 @@ def version() -> str: def compiled_lib_name() -> str: return 'DREAM3DReview [Python]' - def setup_parameters(self) -> List[FilterParameter]: - req = DataArraySelectionFilterParameter.RequirementType() + + def setup_parameters(self) -> List[simpl.FilterParameter]: + + """ + inline const QString Bool("bool"); + inline const QString Float("float"); + inline const QString Double("double"); + inline const QString Int8("int8_t"); + inline const QString UInt8("uint8_t"); + inline const QString Int16("int16_t"); + inline const QString UInt16("uint16_t"); + inline const QString Int32("int32_t"); + inline const QString UInt32("uint32_t"); + inline const QString Int64("int64_t"); + inline const QString UInt64("uint64_t"); + """ + # Create a DataArraySelectionFilterParameter Requirement Type that only takes the following: + # Image Geometry + # AttributeMatrix must be a CellType + # DataArrayTypes are only float, double, int32_t # See the list from above + # DataArray Component dimensions are 1 or 3, i.e., a scalar or vector of size 3 only + req = simpl.DataArraySelectionFilterParameter.RequirementType() + req.dcGeometryTypes = [simpl.IGeometry.Type.Image] + req.amTypes = [simpl.AttributeMatrix.Type.Cell] + req.daTypes = ["float", "double", "int32_t"] + req.componentDimensions = [VectorSizeT([1]), VectorSizeT([3])] + return [ - IntFilterParameter('Integer', 'int_param', self.int_param, FilterParameter.Category.Parameter, self._set_int, self._get_int, -1), - DataArraySelectionFilterParameter('Data Array Path Selection', 'dap_param', self.dap_param, FilterParameter.Category.RequiredArray, self._set_dap, self._get_dap, req, -1), - StringFilterParameter('String', 'str_param', self.str_param, FilterParameter.Category.Parameter, self._set_str, self._get_str, -1), - BooleanFilterParameter('Boolean', 'bool_param', self.bool_param, FilterParameter.Category.Parameter, self._set_bool, self._get_bool, -1), - InputFileFilterParameter('Input File', 'input_file_param', self.input_file_param, - FilterParameter.Category.Parameter, self._set_input_file, self._get_input_file,'*.ang', 'EDAX Ang', -1), - InputPathFilterParameter('Input Directory', 'input_path_param', self.input_path_param, - FilterParameter.Category.Parameter, self._set_input_path_param, self._get_input_path_param, -1) + simpl.IntFilterParameter('Integer', 'int_param', self.int_param, simpl.FilterParameter.Category.Parameter, self._set_int, self._get_int, -1), + simpl.DataArraySelectionFilterParameter('Data Array Path Selection', 'dap_param', self.dap_param, simpl.FilterParameter.Category.RequiredArray, self._set_dap, self._get_dap, req, -1), + simpl.StringFilterParameter('String', 'str_param', self.str_param, simpl.FilterParameter.Category.Parameter, self._set_str, self._get_str, -1), + simpl.BooleanFilterParameter('Boolean', 'bool_param', self.bool_param, simpl.FilterParameter.Category.Parameter, self._set_bool, self._get_bool, -1), + simpl.InputFileFilterParameter('Input File', 'input_file_param', self.input_file_param, + simpl.FilterParameter.Category.Parameter, self._set_input_file, self._get_input_file,'*.ang', 'EDAX Ang', -1), + simpl.InputPathFilterParameter('Input Directory', 'input_path_param', self.input_path_param, + simpl.FilterParameter.Category.Parameter, self._set_input_path_param, self._get_input_path_param, -1) ] - def data_check(self, dca: DataContainerArray, delegate: Union[FilterDelegateCpp, FilterDelegatePy] = FilterDelegatePy()) -> Tuple[int, str]: + def data_check(self, dca: simpl.DataContainerArray, delegate: Union[simpl.FilterDelegateCpp, SIMPLFilterDelegatePy] = SIMPLFilterDelegatePy()) -> Tuple[int, str]: am = dca.getAttributeMatrix(self.dap_param) if am is None: - return (-1, 'AttributeMatrix is None') + return -1, 'AttributeMatrix is None' da = am.getAttributeArray(self.dap_param) if da is None: - return (-2, 'DataArray is None') + return -2, 'DataArray is None' delegate.notifyStatusMessage('data_check finished!') - return (0, 'Success') + return 0, 'Success' - def _execute_impl(self, dca: DataContainerArray, delegate: Union[FilterDelegateCpp, FilterDelegatePy] = FilterDelegatePy()) -> Tuple[int, str]: + def _execute_impl(self, dca: simpl.DataContainerArray, delegate: Union[simpl.FilterDelegateCpp, SIMPLFilterDelegatePy] = SIMPLFilterDelegatePy()) -> Tuple[int, str]: delegate.notifyStatusMessage(f'int_param = {self.int_param}') da = dca.getAttributeMatrix(self.dap_param).getAttributeArray(self.dap_param) @@ -180,6 +208,6 @@ def _execute_impl(self, dca: DataContainerArray, delegate: Union[FilterDelegateC delegate.notifyStatusMessage(f'after = {data}') delegate.notifyStatusMessage('execute finished!') - return (0, 'Success') + return 0, 'Success' filters = [D3DReviewTestFilter] \ No newline at end of file diff --git a/Python/Filters/normalize_grayscale.py b/Python/Filters/normalize_grayscale.py new file mode 100644 index 00000000..bdcf74d2 --- /dev/null +++ b/Python/Filters/normalize_grayscale.py @@ -0,0 +1,212 @@ +import numpy as np + +from typing import List +from enum import IntEnum +from typing import List, Tuple, Union + +from dream3d.Filter import Filter, FilterDelegatePy +from dream3d.simpl import NumericTypes +from dream3d.simpl import * + + +class NormalizeGrayscale(Filter): + def __init__(self) -> None: + self.selected_data_array_path: DataArrayPath = DataArrayPath('', '', '') + self.axes: int = 0 + self.created_data_array_path: DataArrayPath = DataArrayPath('', '', '') + + def _set_selected_data_array_path(self, value: DataArrayPath) -> None: + self.selected_data_array_path = value + + def _get_selected_data_array_path(self) -> DataArrayPath: + return self.selected_data_array_path + + def _set_axes(self, value: int) -> None: + self.axes = value + + def _get_axes(self) -> int: + return self.axes + + def _set_created_data_array_path(self, value: DataArrayPath) -> None: + self.created_data_array_path = value + + def _get_created_data_array_path(self) -> DataArrayPath: + return self.created_data_array_path + + @staticmethod + def name() -> str: + return 'Grayscale Normalizer [Python]' + + @staticmethod + def uuid() -> str: + return '{b98fa052-4c74-4d25-96ce-5b95074fcecf}' + + @staticmethod + def group_name() -> str: + return 'Processing' + + @staticmethod + def sub_group_name() -> str: + return 'Normalization' + + @staticmethod + def human_label() -> str: + return 'Normalize Grayscale Values [Python]' + + @staticmethod + def version() -> str: + return '1.0.0' + + @staticmethod + def compiled_lib_name() -> str: + return 'Python' + + def setup_parameters(self) -> List[FilterParameter]: + # Create a DataArraySelectionFilterParameter Requirement Type that only takes the following: + # Image Geometry + # AttributeMatrix must be a CellType + # DataArray Component dimensions are 1, i.e., a scalar value + req = DataArraySelectionFilterParameter.RequirementType() + req.dcGeometryTypes = [IGeometry.Type.Image] + req.amTypes = [AttributeMatrix.Type.Cell] + # req.daTypes = ["float", "double", "int32_t"] + req.componentDimensions = [VectorSizeT([1])] + + return [ + DataArraySelectionFilterParameter('Input Data Array', 'selected_data_array_path', + self.selected_data_array_path, FilterParameter.Category.RequiredArray, + self._set_selected_data_array_path, self._get_selected_data_array_path, + req, -1), + ChoiceFilterParameter('Slice Axis', 'axes', self.axes, FilterParameter.Category.Parameter, self._set_axes, + self._get_axes, ["x", "y", "z"], False, -1), + DataArrayCreationFilterParameter('New Array', 'new_array', self.created_data_array_path, + FilterParameter.Category.CreatedArray, self._set_created_data_array_path, + self._get_created_data_array_path, + DataArrayCreationFilterParameter.RequirementType(), -1) + ] + + def data_check(self, dca: DataContainerArray, + status_delegate: Union[FilterDelegateCpp, FilterDelegatePy] = FilterDelegatePy()) -> Tuple[int, str]: + # Methodically check for each level of the selected data array path. + # Ensure the proper geometry is available + # Ensure the number of components of the input data array is 1 + # Error messages should give as much information as possible. users will rely on the + # error message to debug their pipelines, sometimes from the command line so printing + # out the DataArrayPath that was passed in is helpful for the user. + # Error Codes: Should start at a unique value somewhere between 10,000 and 100,000 and then just increment up from there + + dc: DataContainer = dca.getDataContainer(self.selected_data_array_path) + if dc is None: + return -61550, 'DataContainer does not exist: ' + self.selected_data_array_path.DataContainerName + + if not dc.Geometry: + return (-61551, 'Selected DataContainer has no geometry.') + + if dc.Geometry.getGeometryType() != IGeometry.Type.Image: + return (-61552, + 'This filter requires a DataContainer with an ImageGeometry. Selected DataContainer has ' + dc.Geometry.getGeometryTypeAsString()) + + am: AttributeMatrix = dca.getAttributeMatrix(self.selected_data_array_path) + if am is None: + return -61553, 'AttributeMatrix does not exist: ' + self.selected_data_array_path.AttributeMatrixName + + selected_data_array: IDataArray = am.getAttributeArray(self.selected_data_array_path.DataArrayName) + if selected_data_array is None: + return -61554, 'DataArray does not exist: ' + self.selected_data_array_path.DataArrayName + + if selected_data_array.getNumberOfComponents() != 1: + return -61555, 'Selected DataArray must have a single component: ' + str( + selected_data_array.getNumberOfComponents()) + + # If we are creating an array, what other options are there, then we need to create the array (but not allocate the memory during preflight) + # using the createNonPrereqArrayFromPath should do this for us. + arr_dtype = selected_data_array.dtype + if arr_dtype == "int8": + dca.createNonPrereqArrayFromPath(status_delegate, NumericTypes.Int8, self.created_data_array_path, 0, + VectorSizeT([1])) + elif arr_dtype == "uint8": + dca.createNonPrereqArrayFromPath(status_delegate, NumericTypes.UInt8, self.created_data_array_path, 0, + VectorSizeT([1])) + elif arr_dtype == "int16": + dca.createNonPrereqArrayFromPath(status_delegate, NumericTypes.Int16, self.created_data_array_path, 0, + VectorSizeT([1])) + elif arr_dtype == "uint16": + dca.createNonPrereqArrayFromPath(status_delegate, NumericTypes.UInt16, self.created_data_array_path, 0, + VectorSizeT([1])) + elif arr_dtype == "int32": + dca.createNonPrereqArrayFromPath(status_delegate, NumericTypes.Int32, self.created_data_array_path, 0, + VectorSizeT([1])) + elif arr_dtype == "uint32": + dca.createNonPrereqArrayFromPath(status_delegate, NumericTypes.UInt32, self.created_data_array_path, 0, + VectorSizeT([1])) + elif arr_dtype == "int64": + dca.createNonPrereqArrayFromPath(status_delegate, NumericTypes.Int64, self.created_data_array_path, 0, + VectorSizeT([1])) + elif arr_dtype == "uint64": + dca.createNonPrereqArrayFromPath(status_delegate, NumericTypes.UInt64, self.created_data_array_path, 0, + VectorSizeT([1])) + elif arr_dtype == "float32": + dca.createNonPrereqArrayFromPath(status_delegate, NumericTypes.Float, self.created_data_array_path, 0, + VectorSizeT([1])) + elif arr_dtype == "float64": + dca.createNonPrereqArrayFromPath(status_delegate, NumericTypes.Double, self.created_data_array_path, 0, + VectorSizeT([1])) + elif arr_dtype == "bool": + return -61556, 'Bool type arrays are not supported as inputs to this algorithm' + + return (0, 'Success') + + def _execute_impl(self, dca: DataContainerArray, + status_delegate: Union[FilterDelegateCpp, FilterDelegatePy] = FilterDelegatePy()) -> Tuple[ + int, str]: + + dc: DataContainer = dca.getDataContainer(self.selected_data_array_path) + am: AttributeMatrix = dc.getAttributeMatrix(self.selected_data_array_path) + selected_data_array: IDataArray = am.getAttributeArray(self.selected_data_array_path) + created_data_array: IDataArray = am.getAttributeArray(self.created_data_array_path) + status_delegate.notifyStatusMessage(f" Selected D3D data array is {selected_data_array}") + + # Note that the dimensions come back as X, Y, Z (Fastest to slowest) so we will need to invert the order if using them in a numpy array + # Note however that the data is actually stored in the SIMPL DataArray in the correct order + imageGeomDims: SizeVec3 = dc.Geometry.getDimensions() + shape = [np.int64(imageGeomDims[2]), np.int64(imageGeomDims[1]), np.int64(imageGeomDims[0])] + + npv_selected_data = selected_data_array.npview() + npv_selected_data = np.reshape(npv_selected_data, shape) + selected_data_dtype = npv_selected_data.dtype + + # Get a numpy view into the data and then reshape the numpy array + npv_created_data_array = created_data_array.npview() + npv_created_data_array = np.reshape(npv_created_data_array, shape) + + # Get the average of the entire data set + arr_avg = np.average(npv_selected_data) + slice_axis = self.axes + + # slices are in x-axis + if slice_axis == 0: + layer_avg = np.average(npv_selected_data, axis=(0, 1)) + correction_factor = layer_avg / arr_avg + numpy_created_data_array = npv_selected_data / correction_factor[None, None, :] # Makes a copy + + # slices are in y-axis + elif slice_axis == 1: + layer_avg = np.average(npv_selected_data, axis=(0, 2)) + correction_factor = layer_avg / arr_avg + numpy_created_data_array = npv_selected_data / correction_factor[None, :, None] # Makes a copy + + # slices are in z-axis + elif slice_axis == 2: + layer_avg = np.average(npv_selected_data, axis=(1, 2)) + correction_factor = layer_avg / arr_avg + numpy_created_data_array = npv_selected_data / correction_factor[:, None, None] # Makes a copy + + # Assigns the data from the numpy array numpy_created_data_array to the view into the SIMPL.DataArray + # through a copy operation. + # https://numpy.org/doc/stable/user/basics.indexing.html#assigning-values-to-indexed-arrays + npv_created_data_array[...] = numpy_created_data_array + + return (0, 'Success') + + +filters = [NormalizeGrayscale] diff --git a/Python/environment.yml b/Python/environment.yml new file mode 100644 index 00000000..d0da7145 --- /dev/null +++ b/Python/environment.yml @@ -0,0 +1,26 @@ +name: bar +channels: + - conda-forge + - nodefaults + - https://dream3d.bluequartz.net/binaries/conda +dependencies: + - python=3.9 + - hdf5=1.10.6 + - zarr + - xarray + - Pillow>=8.2.0 + - tqdm=4.60.0 + - lxml=4.6.3 + - pyevtk + - scikit-image + - matplotlib + - ipyparallel + - pathlib>=1.0.1 + - h5py>=3.2.1 + - dream3d-conda>=1.4.2 + - pip + - pip: + - aicspylibczi + - itk==5.1.2 + + diff --git a/Python/unit_test.py b/Python/unit_test.py index 0e63a52f..fdefd2d0 100644 --- a/Python/unit_test.py +++ b/Python/unit_test.py @@ -1,8 +1,7 @@ # make sure test_filter.py is on PYTHONPATH so it can be found for import from Filters import d3d_review_filter_1 -import dream3d.simpl as simpl -pf = simpl.PythonFilter(d3d_review_filter_1.D3DReviewTestFilter()) +import dream3d.simpl as simpl dca = simpl.DataContainerArray() @@ -11,6 +10,5 @@ py_filter = d3d_review_filter_1.D3DReviewTestFilter() error_code, message = py_filter.execute(dca) - print('Done.')