diff --git a/conda/conda_build_config.yaml b/conda/conda_build_config.yaml index bba2dda8a6..839dc3a7a4 100644 --- a/conda/conda_build_config.yaml +++ b/conda/conda_build_config.yaml @@ -3,12 +3,12 @@ cxx_compiler: c_compiler_version: # [unix] - 10.4.0 # [linux] + - 17.0.6 # [osx] cxx_compiler_version: - 10.4.0 # [linux] + - 17.0.6 # [osx] python: - 3.12 - - 3.11 - - 3.10 - - 3.9 + diff --git a/conda/meta.yaml b/conda/meta.yaml index 9a1d238953..6fa7579945 100644 --- a/conda/meta.yaml +++ b/conda/meta.yaml @@ -1,5 +1,5 @@ {% set name = "simplnx" %} -{% set version = "1.4.0" %} +{% set version = "1.5.0" %} package: name: {{ name|lower }} @@ -9,19 +9,19 @@ source: - path: ../ folder: simplnx - git_url: https://github.com/BlueQuartzSoftware/EbsdLib - git_rev: v1.0.26 + git_rev: v1.0.29 folder: EbsdLib - git_url: https://github.com/BlueQuartzSoftware/H5Support git_rev: v1.0.13 folder: H5Support - git_url: https://github.com/martinmoene/expected-lite - git_rev: v0.5.0 + git_rev: v0.6.3 folder: expected-lite - git_url: https://github.com/fr00b0/nod - git_rev: v0.5.2 + git_rev: v0.5.3 folder: nod - git_url: https://github.com/martinmoene/span-lite - git_rev: v0.10.3 + git_rev: v0.11.0 folder: span-lite - url: https://raw.githubusercontent.com/BlueQuartzSoftware/simplnx-registry/9a5db7b5fa02b2495eb51654cbaab3c63820c779/ports/nod/CMakeLists.txt folder: nod diff --git a/conda/recipe.yaml b/conda/recipe.yaml index 019df1ea9a..93e3374ba9 100644 --- a/conda/recipe.yaml +++ b/conda/recipe.yaml @@ -1,5 +1,5 @@ context: - version: "1.4.0" + version: "1.5.0" name: simplnx package: @@ -10,21 +10,21 @@ source: - path: ../ folder: simplnx - git_url: https://github.com/BlueQuartzSoftware/EbsdLib - git_rev: v1.0.26 + git_rev: v1.0.29 folder: EbsdLib - git_url: https://github.com/BlueQuartzSoftware/H5Support git_rev: v1.0.13 folder: H5Support - git_url: https://github.com/martinmoene/expected-lite - git_rev: v0.5.0 + git_rev: v0.6.3 folder: expected-lite - git_url: https://github.com/fr00b0/nod - git_rev: v0.5.2 + git_rev: v0.5.3 folder: nod - git_url: https://github.com/martinmoene/span-lite - git_rev: v0.10.3 + git_rev: v0.11.0 folder: span-lite - - url: https://raw.githubusercontent.com/bluequartzsoftware/simplnx-registry/9a5db7b5fa02b2495eb51654cbaab3c63820c779/ports/nod/CMakeLists.txt + - url: https://raw.githubusercontent.com/BlueQuartzSoftware/simplnx-registry/9a5db7b5fa02b2495eb51654cbaab3c63820c779/ports/nod/CMakeLists.txt folder: nod - git_url: https://github.com/insightsoftwareconsortium/ITK git_rev: v5.2.1 diff --git a/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKLabelContourImageFilter.cpp b/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKLabelContourImageFilter.cpp index f44d6f9e80..00fb650019 100644 --- a/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKLabelContourImageFilter.cpp +++ b/src/Plugins/ITKImageProcessing/src/ITKImageProcessing/Filters/ITKLabelContourImageFilter.cpp @@ -75,7 +75,7 @@ Parameters ITKLabelContourImageFilter::parameters() const params.insertSeparator(Parameters::Separator{"Input Parameter(s)"}); params.insert(std::make_unique(k_FullyConnected_Key, "Fully Connected", "Set/Get whether the connected components are defined strictly by face connectivity or by face+edge+vertex connectivity. Default is FullyConnectedOff. " - "Note For objects that are 1 pixel wide, use FullyConnectedOn.", + "**NOTE** For objects that are 1 pixel wide, use FullyConnectedOn.", false)); params.insert(std::make_unique(k_BackgroundValue_Key, "Background Value", "Set/Get the background value used to identify the objects and mark the pixels not on the border of the objects.", 0)); diff --git a/src/Plugins/TestOne/src/TestOne/Filters/ExampleFilter2Filter.cpp b/src/Plugins/TestOne/src/TestOne/Filters/ExampleFilter2Filter.cpp index ba99137ece..b49aeaf420 100644 --- a/src/Plugins/TestOne/src/TestOne/Filters/ExampleFilter2Filter.cpp +++ b/src/Plugins/TestOne/src/TestOne/Filters/ExampleFilter2Filter.cpp @@ -3,6 +3,7 @@ #include "simplnx/Common/StringLiteral.hpp" #include "simplnx/Parameters/ArrayCreationParameter.hpp" #include "simplnx/Parameters/ArraySelectionParameter.hpp" +#include "simplnx/Parameters/AttributeMatrixSelectionParameter.hpp" #include "simplnx/Parameters/BoolParameter.hpp" #include "simplnx/Parameters/ChoicesParameter.hpp" #include "simplnx/Parameters/DataGroupCreationParameter.hpp" @@ -30,6 +31,7 @@ constexpr StringLiteral k_Param10 = "param10_path"; constexpr StringLiteral k_Param11 = "param11_path"; constexpr StringLiteral k_Param12 = "param12s"; constexpr StringLiteral k_Param13 = "param13"; +constexpr StringLiteral k_Param30 = "param30_path"; } // namespace @@ -81,6 +83,18 @@ Parameters ExampleFilter2Filter::parameters() const params.insert(std::make_unique(k_Param13, "Dynamic Table Parameter", "DynamicTableParameter Example Help Text", defaultTable, tableInfo)); // These should show up under the "Required Objects" Section in the GUI + + // params.linkParameters(k_Param3, k_Param10, std::make_any(0)); + // params.linkParameters(k_Param3, k_Param6, std::make_any(1)); + // params.linkParameters(k_Param3, k_Param11, std::make_any(2)); + + // These should show up under the "Created Objects" section in the GUI + + params.insertSeparator(Parameters::Separator{"Data Object Parameters"}); + + params.insert(std::make_unique(k_Param8, "DataGroup Creation Parameter", "Example data group creation help text", DataPath{})); + params.insert(std::make_unique(k_Param5, "Array Creation", "Example array creation help text", ArrayCreationParameter::ValueType{})); + params.insert(std::make_unique(k_Param9, "DataGroup Selection Parameter", "Example data group selection help text", DataPath{}, DataGroupSelectionParameter::AllowedTypes{BaseGroup::GroupType::DataGroup})); params.insert(std::make_unique(k_Param10, "DataPath Selection Parameter", "Example data path selection help text", DataPath{})); @@ -89,17 +103,10 @@ Parameters ExampleFilter2Filter::parameters() const std::make_unique(k_Param11, "Geometry Selection Parameter", "Example geometry selection help text", DataPath{}, GeometrySelectionParameter::AllowedTypes{})); params.insert(std::make_unique(k_Param12, "MultiArray Selection Parameter", "Example multiarray selection help text", MultiArraySelectionParameter::ValueType{}, MultiArraySelectionParameter::AllowedTypes{IArray::ArrayType::Any}, nx::core::GetAllDataTypes())); + params.insert(std::make_unique(k_Param30, "Attribute Matrix Selection", "Example Help Text", DataPath{})); params.linkParameters(k_Param7, k_Param9, std::make_any(true)); - params.linkParameters(k_Param3, k_Param10, std::make_any(0)); - params.linkParameters(k_Param3, k_Param6, std::make_any(1)); - params.linkParameters(k_Param3, k_Param11, std::make_any(2)); - - // These should show up under the "Created Objects" section in the GUI - params.insert(std::make_unique(k_Param8, "DataGroup Creation Parameter", "Example data group creation help text", DataPath{})); - params.insert(std::make_unique(k_Param5, "Array Creation", "Example array creation help text", ArrayCreationParameter::ValueType{})); - return params; } diff --git a/wrapping/python/ReadMe.md b/wrapping/python/ReadMe.md index c410210dfa..87b0f531fc 100644 --- a/wrapping/python/ReadMe.md +++ b/wrapping/python/ReadMe.md @@ -95,3 +95,27 @@ cd simplnx/docs/ make clean make html ``` + +## Restructured Text Heading Levels + +===================================== +Page Title +===================================== + +################################### +Part Title +################################### + +Heading 1 +========= + +Heading 2 +--------- + +Heading 3 +^^^^^^^^^ + +Heading 4 +"""""""""" + + \ No newline at end of file diff --git a/wrapping/python/docs/generate_sphinx_docs.cpp b/wrapping/python/docs/generate_sphinx_docs.cpp index ba8e2995bd..1c9be734ce 100644 --- a/wrapping/python/docs/generate_sphinx_docs.cpp +++ b/wrapping/python/docs/generate_sphinx_docs.cpp @@ -753,8 +753,8 @@ void GeneratePythonRstFiles() rstStream << ")\n\n"; rstStream << memberStream.str(); - rstStream << " :return: Returns a :ref:`Result ` object that holds any warnings and/or errors that were encountered during execution.\n"; - rstStream << " :rtype: :ref:`simplnx.Result `\n\n"; + rstStream << " :return: Returns a :ref:`nx.IFilter.ExecuteResult ` object that holds any warnings and/or errors that were encountered during execution.\n"; + rstStream << " :rtype: :ref:`nx.IFilter.ExecuteResult `\n\n"; rstStream << '\n'; } } diff --git a/wrapping/python/docs/index_template.rst b/wrapping/python/docs/index_template.rst index a98f419860..44c79fdddc 100644 --- a/wrapping/python/docs/index_template.rst +++ b/wrapping/python/docs/index_template.rst @@ -56,6 +56,7 @@ How to use SIMPLNX from Python Overview DataObjects Geometry + Reference_Frame_Notes .. toctree:: :maxdepth: 2 @@ -63,7 +64,9 @@ How to use SIMPLNX from Python Python_Introduction User_API - Reference_Frame_Notes + Tutorial_1 + Tutorial_2 + Tutorial_3 .. toctree:: :maxdepth: 1 diff --git a/wrapping/python/docs/source/Developer_API.rst b/wrapping/python/docs/source/Developer_API.rst index 4e7dbe8a31..df2f9c38dd 100644 --- a/wrapping/python/docs/source/Developer_API.rst +++ b/wrapping/python/docs/source/Developer_API.rst @@ -7,6 +7,11 @@ General Parameters .. _ArrayCreationParameter: .. py:class:: ArrayCreationParameter + The figure shows what the parameter looks like in the user interface. + + .. figure:: Images/Parameters/ArrayCreationParameter.png + :alt: Array Creation Parameter + Declaration ~~~~~~~~~~~ @@ -49,6 +54,12 @@ General Parameters .. _ArraySelectionParameter: .. py:class:: ArraySelectionParameter + The figure shows what the parameter looks like in the user interface. + + .. figure:: Images/Parameters/ArraySelectionParameter.png + :alt: Array Creation Parameter + + Declaration ~~~~~~~~~~~ .. code-block:: python @@ -102,6 +113,11 @@ General Parameters .. _ArrayThresholdsParameter: .. py:class:: ArrayThresholdsParameter + The figure shows what the parameter looks like in the user interface. + + .. figure:: Images/Parameters/ArrayThresholdsParameter.png + :alt: Array Creation Parameter + Declaration ~~~~~~~~~~~ .. code-block:: python @@ -112,7 +128,7 @@ General Parameters ~~~~~~~~~~~ The ``ArrayThresholdsParameter`` is used to specify thresholds for an array, allowing for filtering based on those thresholds. - This parameter holds a ArrayThresholdSet_ object and is used specifically for the :ref:`simplnx.MultiThresholdObjects() ` filter. + This parameter holds a ArrayThresholdSet_ object and is used specifically for the :ref:`simplnx.MultiThresholdObjectsFilter() ` filter. This parameter should not be directly invoked but instead its ArrayThresholdSet_ is invoked and used. Inputs @@ -188,7 +204,7 @@ General Parameters .. _ArrayThreshold: .. py:class:: ArrayThresholdSet.ArrayThreshold - This class holds the values that are used for comparison in the :ref:`simplnx.MultiThresholdObjects() ` filter. + This class holds the values that are used for comparison in the :ref:`simplnx.MultiThresholdObjectsFilter() ` filter. :ivar array_path: The :ref:`DataPath ` to the array to use for this ArrayThreshold :ivar comparison: Int. The comparison operator to use. 0=">", 1="<", 2="=", 3="!=" @@ -220,6 +236,11 @@ General Parameters .. _AttributeMatrixSelectionParameter: .. py:class:: AttributeMatrixSelectionParameter + The figure shows what the parameter looks like in the user interface. + + .. figure:: Images/Parameters/AttributeMatrixSelectionParameter.png + :alt: Array Creation Parameter + Declaration ~~~~~~~~~~~ .. code-block:: python @@ -260,6 +281,11 @@ General Parameters .. _BoolParameter: .. py:class:: BoolParameter + The figure shows what the parameter looks like in the user interface. + + .. figure:: Images/Parameters/BoolParameter.png + :alt: Array Creation Parameter + Declaration ~~~~~~~~~~~ .. code-block:: python @@ -315,6 +341,11 @@ General Parameters .. _CalculatorParameter: .. py:class:: CalculatorParameter + The figure shows what the parameter looks like in the user interface. + + .. figure:: Images/Parameters/CalculatorParameter.png + :alt: Array Creation Parameter + Declaration ~~~~~~~~~~~ .. code-block:: python @@ -368,6 +399,11 @@ General Parameters .. _ChoicesParameter: .. py:class:: ChoicesParameter + The figure shows what the parameter looks like in the user interface. + + .. figure:: Images/Parameters/ChoicesParameter.png + :alt: Array Creation Parameter + Declaration ~~~~~~~~~~~ .. code-block:: python @@ -425,6 +461,11 @@ General Parameters .. _DataGroupCreationParameter: .. py:class:: DataGroupCreationParameter + The figure shows what the parameter looks like in the user interface. + + .. figure:: Images/Parameters/DataGroupCreationParameter.png + :alt: Array Creation Parameter + Declaration ~~~~~~~~~~~ .. code-block:: python @@ -465,6 +506,11 @@ General Parameters .. _DataGroupSelectionParameter: .. py:class:: DataGroupSelectionParameter + The figure shows what the parameter looks like in the user interface. + + .. figure:: Images/Parameters/DataGroupSelectionParameter.png + :alt: Array Creation Parameter + Declaration ~~~~~~~~~~~ .. code-block:: python @@ -509,6 +555,11 @@ General Parameters .. _DataObjectNameParameter: .. py:class:: DataObjectNameParameter + The figure shows what the parameter looks like in the user interface. + + .. figure:: Images/Parameters/DataObjectNameParameter.png + :alt: DataObjectName Parameter + Declaration ~~~~~~~~~~~ .. code-block:: python @@ -517,7 +568,14 @@ General Parameters Description ~~~~~~~~~~~ - The ``DataObjectNameParameter`` is used to specify the name of a data object within the data structure. + The ``DataObjectNameParameter`` is used to specify the name of a data object within the data structure. This parameter + is used most often when the developer of the filter intends the output "Group|Array|ArrtributeMatrix|Geometry" to be + created as a sibling to an input DataObject or as a Child of an input DataObject. Examples would be a filter that asks + for an input array "Foo" and creates an output array that they want to be a sibling of "Foo" called "Bar". Another + example would be a filter that asks for an input Attribute Matrix and wants to create an output array as a child of + the input Attribute Matrix. The major difference between this Parameter and the String parameter is that if a user + changes the value of a DataObjectName parameter, other filters further down the pipeline will be updated with the new + value. Inputs ~~~~~~ @@ -549,6 +607,11 @@ General Parameters .. _DataPathSelectionParameter: .. py:class:: DataPathSelectionParameter + The figure shows what the parameter looks like in the user interface. + + .. figure:: Images/Parameters/DataPathSelectionParameter.png + :alt: DataPathSelection Parameter + Declaration ~~~~~~~~~~~ .. code-block:: python @@ -631,6 +694,11 @@ General Parameters .. _DataTypeParameter: .. py:class:: DataTypeParameter + The figure shows what the parameter looks like in the user interface. + + .. figure:: Images/Parameters/DataTypeParameter.png + :alt: DataTypeParameter + Declaration ~~~~~~~~~~~ .. code-block:: python @@ -687,6 +755,11 @@ General Parameters .. _Dream3dImportParameter: .. py:class:: Dream3dImportParameter + The figure shows what the parameter looks like in the user interface. + + .. figure:: Images/Parameters/Dream3dImportParameter.png + :alt: Dream3dImportParameter + Declaration ~~~~~~~~~~~ .. code-block:: python @@ -695,7 +768,8 @@ General Parameters Description ~~~~~~~~~~~ - The ``Dream3dImportParameter`` holds the information necessary to import a .dream3d file through the **ImportData** object. + The ``Dream3dImportParameter`` holds the information necessary to import a .dream3d file through the **ImportData** object. This parameter + should rarely, if ever, be used outside of the filter it was created to be used by. Inputs ~~~~~~ @@ -733,6 +807,11 @@ General Parameters .. _DynamicTableParameter: .. py:class:: DynamicTableParameter + The figure shows what the parameter looks like in the user interface. + + .. figure:: Images/Parameters/DynamicTableParameter.png + :alt: DynamicTableParameter + Declarations ~~~~~~~~~~~~ .. code-block:: python @@ -743,7 +822,10 @@ General Parameters Description ~~~~~~~~~~~ - The ``DynamicTableParameter`` is used to specify parameters for dynamic tables which can be modified by the user during runtime. It involves detailed configuration of rows and columns using the `DynamicTableInfo` class. + The ``DynamicTableParameter`` is used to specify parameters for dynamic tables which can be modified by the user + during runtime. It involves detailed configuration of rows and columns using the `DynamicTableInfo` class. The parameter + can have either static or adjustable (dynamic) rows and columns at runtime. This is done through the configuration + object. Inputs ~~~~~~ @@ -814,6 +896,11 @@ General Parameters .. _EnsembleInfoParameter: .. py:class:: EnsembleInfoParameter + The figure shows what the parameter looks like in the user interface. + + .. figure:: Images/Parameters/EnsembleInfoParameter.png + :alt: EnsembleInfoParameter + Declaration ~~~~~~~~~~~ .. code-block:: python @@ -822,7 +909,10 @@ General Parameters Description ~~~~~~~~~~~ - The ``EnsembleInfoParameter`` is used to represent a list of 3 value lists. Each list holds 3 values, Crystal Structure, Phase Type, Phase Name. + The ``EnsembleInfoParameter`` is used to create the necessary phase information that will help when analyzing EBSD style of + data where crystallographic information about the phases is needed. This kind of data is typically read as part of EBSD + files but there are cases where this kind of data is being read from CSV files. It is represented a list of 3 value lists. + Each list holds 3 values, Crystal Structure, Phase Type, Phase Name. Each row represents a specific phase. @@ -850,7 +940,8 @@ General Parameters The user can define their own phase names. - This is used in combination with the :ref:`OrientationAnalysis.CreateEnsembleInfoFilter() ` filter. + This parameter is typically used in combination with the :ref:`OrientationAnalysis.CreateEnsembleInfoFilter() ` filter + but could find uses in other filters that need this kind of information from the user. Inputs ~~~~~~ @@ -885,6 +976,11 @@ General Parameters .. _FileSystemPathParameter: .. py:class:: FileSystemPathParameter + The figure shows what the parameter looks like in the user interface. + + .. figure:: Images/Parameters/FileSystemPathParameter.png + :alt: FileSystemPathParameter + Declaration ~~~~~~~~~~~ .. code-block:: python @@ -893,7 +989,8 @@ General Parameters Description ~~~~~~~~~~~ - The ``FileSystemPathParameter`` is used to specify a file system path, allowing the user to select directories or files for input or output operations. + The ``FileSystemPathParameter`` is used to ask a user for a path from the local computer system. The parameter can be + configured to allow the user to select directories or files for input or output operations. Inputs ~~~~~~ @@ -940,6 +1037,11 @@ General Parameters .. _CreateColorMapParameter: .. py:class:: CreateColorMapParameter + The figure shows what the parameter looks like in the user interface. + + .. figure:: Images/Parameters/CreateColorMapParameter.png + :alt: CreateColorMapParameter + Declaration ~~~~~~~~~~~ .. code-block:: python @@ -953,23 +1055,7 @@ General Parameters This parameter is used specifically for the :ref:`simplnx.CreateColorMapFilter() ` filter. - These are the color table presets: - - - "Rainbow Desaturated" - - "Cold and Hot" - - "Black-Body Radiation" - - "X Ray" - - "Grayscale" - - "Black, Blue and White" - - "Black, Orange and White" - - "Rainbow Blended White" - - "Rainbow Blended Grey" - - "Rainbow Blended Black" - - "Blue to Yellow" - - "jet" - - "rainbow" - - "Haze" - - "hsv" + Please see the full documentation for the filter for the complete list of preset values. Inputs ~~~~~~ @@ -1001,6 +1087,11 @@ General Parameters .. _GeneratedFileListParameter: .. py:class:: GeneratedFileListParameter + The figure shows what the parameter looks like in the user interface. + + .. figure:: Images/Parameters/GeneratedFileListParameter.png + :alt: GeneratedFileListParameter + Declaration ~~~~~~~~~~~ .. code-block:: python @@ -1107,6 +1198,11 @@ General Parameters .. _GeometrySelectionParameter: .. py:class:: GeometrySelectionParameter + The figure shows what the parameter looks like in the user interface. + + .. figure:: Images/Parameters/GeometrySelectionParameter.png + :alt: GeometrySelectionParameter + Declaration ~~~~~~~~~~~ .. code-block:: python @@ -1164,6 +1260,11 @@ General Parameters .. _ReadCSVFileParameter: .. py:class:: ReadCSVFileParameter + The figure shows what the parameter looks like in the user interface. + + .. figure:: Images/Parameters/ReadCSVFileParameter.png + :alt: ReadCSVFileParameter + Declaration ~~~~~~~~~~~ .. code-block:: python @@ -1342,6 +1443,11 @@ General Parameters .. _ReadHDF5DatasetParameter: .. py:class:: ReadHDF5DatasetParameter + The figure shows what the parameter looks like in the user interface. + + .. figure:: Images/Parameters/ReadHDF5DatasetParameter.png + :alt: ReadHDF5DatasetParameter + Declaration ~~~~~~~~~~~ .. code-block:: python @@ -1420,6 +1526,11 @@ General Parameters .. _MultiArraySelectionParameter: .. py:class:: MultiArraySelectionParameter + The figure shows what the parameter looks like in the user interface. + + .. figure:: Images/Parameters/MultiArraySelectionParameter.png + :alt: MultiArraySelectionParameter + Declaration ~~~~~~~~~~~ .. code-block:: python @@ -1473,6 +1584,11 @@ General Parameters .. _MultiPathSelectionParameter: .. py:class:: MultiPathSelectionParameter + The figure shows what the parameter looks like in the user interface. + + .. figure:: Images/Parameters/MultiPathSelectionParameter.png + :alt: MultiPathSelectionParameter + Declaration ~~~~~~~~~~~ .. code-block:: python @@ -1513,6 +1629,11 @@ General Parameters .. _NeighborListSelectionParameter: .. py:class:: NeighborListSelectionParameter + The figure shows what the parameter looks like in the user interface. + + .. figure:: Images/Parameters/NeighborListSelectionParameter.png + :alt: NeighborListSelectionParameter + Declaration ~~~~~~~~~~~ .. code-block:: python @@ -1557,6 +1678,11 @@ General Parameters .. _NumericTypeParameter: .. py:class:: NumericTypeParameter + The figure shows what the parameter looks like in the user interface. + + .. figure:: Images/Parameters/NumericTypeParameter.png + :alt: NumericTypeParameter + Declaration ~~~~~~~~~~~ .. code-block:: python @@ -1608,6 +1734,11 @@ General Parameters .. _StringParameter: .. py:class:: StringParameter + The figure shows what the parameter looks like in the user interface. + + .. figure:: Images/Parameters/StringParameter.png + :alt: StringParameter + Declaration ~~~~~~~~~~~ .. code-block:: python @@ -1648,6 +1779,13 @@ General Parameters Numerical Parameters -------------------- + The figure shows two different numerical parameters. One for a floating point number and one + for an integer. + + .. figure:: Images/Parameters/NumericalParameter.png + :alt: NumericalParameter + + Declarations ~~~~~~~~~~~~ @@ -1709,6 +1847,13 @@ Usage Numerical Vector Parameters --------------------------- + The figure shows two different numerical vector parameters each having a different number of components to the vector. + The user interface has built in Copy/Paste functionality to easily get data into and out of the text fields. + + .. figure:: Images/Parameters/VectorParameter.png + :alt: VectorParameter + + Declarations ~~~~~~~~~~~~ diff --git a/wrapping/python/docs/source/Images/Parameters/ArrayCreationParameter.png b/wrapping/python/docs/source/Images/Parameters/ArrayCreationParameter.png new file mode 100644 index 0000000000..d076d38197 Binary files /dev/null and b/wrapping/python/docs/source/Images/Parameters/ArrayCreationParameter.png differ diff --git a/wrapping/python/docs/source/Images/Parameters/ArraySelectionParameter.png b/wrapping/python/docs/source/Images/Parameters/ArraySelectionParameter.png new file mode 100644 index 0000000000..2c6ce295ab Binary files /dev/null and b/wrapping/python/docs/source/Images/Parameters/ArraySelectionParameter.png differ diff --git a/wrapping/python/docs/source/Images/Parameters/ArrayThresholdsParameter.png b/wrapping/python/docs/source/Images/Parameters/ArrayThresholdsParameter.png new file mode 100644 index 0000000000..744e994731 Binary files /dev/null and b/wrapping/python/docs/source/Images/Parameters/ArrayThresholdsParameter.png differ diff --git a/wrapping/python/docs/source/Images/Parameters/AttributeMatrixSelectionParameter.png b/wrapping/python/docs/source/Images/Parameters/AttributeMatrixSelectionParameter.png new file mode 100644 index 0000000000..5bde8cdd78 Binary files /dev/null and b/wrapping/python/docs/source/Images/Parameters/AttributeMatrixSelectionParameter.png differ diff --git a/wrapping/python/docs/source/Images/Parameters/BoolParameter.png b/wrapping/python/docs/source/Images/Parameters/BoolParameter.png new file mode 100644 index 0000000000..714cd5d3cf Binary files /dev/null and b/wrapping/python/docs/source/Images/Parameters/BoolParameter.png differ diff --git a/wrapping/python/docs/source/Images/Parameters/CalculatorParameter.png b/wrapping/python/docs/source/Images/Parameters/CalculatorParameter.png new file mode 100644 index 0000000000..37c26454ba Binary files /dev/null and b/wrapping/python/docs/source/Images/Parameters/CalculatorParameter.png differ diff --git a/wrapping/python/docs/source/Images/Parameters/ChoicesParameter.png b/wrapping/python/docs/source/Images/Parameters/ChoicesParameter.png new file mode 100644 index 0000000000..33f65d117d Binary files /dev/null and b/wrapping/python/docs/source/Images/Parameters/ChoicesParameter.png differ diff --git a/wrapping/python/docs/source/Images/Parameters/CreateColorMapParameter.png b/wrapping/python/docs/source/Images/Parameters/CreateColorMapParameter.png new file mode 100644 index 0000000000..0660325d3e Binary files /dev/null and b/wrapping/python/docs/source/Images/Parameters/CreateColorMapParameter.png differ diff --git a/wrapping/python/docs/source/Images/Parameters/DataGroupCreationParameter.png b/wrapping/python/docs/source/Images/Parameters/DataGroupCreationParameter.png new file mode 100644 index 0000000000..6bac6c40c3 Binary files /dev/null and b/wrapping/python/docs/source/Images/Parameters/DataGroupCreationParameter.png differ diff --git a/wrapping/python/docs/source/Images/Parameters/DataGroupSelectionParameter.png b/wrapping/python/docs/source/Images/Parameters/DataGroupSelectionParameter.png new file mode 100644 index 0000000000..c14b710c40 Binary files /dev/null and b/wrapping/python/docs/source/Images/Parameters/DataGroupSelectionParameter.png differ diff --git a/wrapping/python/docs/source/Images/Parameters/DataObjectNameParameter.png b/wrapping/python/docs/source/Images/Parameters/DataObjectNameParameter.png new file mode 100644 index 0000000000..ec9342d5a3 Binary files /dev/null and b/wrapping/python/docs/source/Images/Parameters/DataObjectNameParameter.png differ diff --git a/wrapping/python/docs/source/Images/Parameters/DataPathSelectionParameter.png b/wrapping/python/docs/source/Images/Parameters/DataPathSelectionParameter.png new file mode 100644 index 0000000000..02eeeef31c Binary files /dev/null and b/wrapping/python/docs/source/Images/Parameters/DataPathSelectionParameter.png differ diff --git a/wrapping/python/docs/source/Images/Parameters/DataTypeParameter.png b/wrapping/python/docs/source/Images/Parameters/DataTypeParameter.png new file mode 100644 index 0000000000..5f86fc7189 Binary files /dev/null and b/wrapping/python/docs/source/Images/Parameters/DataTypeParameter.png differ diff --git a/wrapping/python/docs/source/Images/Parameters/Dream3dImportParameter.png b/wrapping/python/docs/source/Images/Parameters/Dream3dImportParameter.png new file mode 100644 index 0000000000..e504b3254c Binary files /dev/null and b/wrapping/python/docs/source/Images/Parameters/Dream3dImportParameter.png differ diff --git a/wrapping/python/docs/source/Images/Parameters/DynamicTableParameter.png b/wrapping/python/docs/source/Images/Parameters/DynamicTableParameter.png new file mode 100644 index 0000000000..fec711545b Binary files /dev/null and b/wrapping/python/docs/source/Images/Parameters/DynamicTableParameter.png differ diff --git a/wrapping/python/docs/source/Images/Parameters/EnsembleInfoParameter.png b/wrapping/python/docs/source/Images/Parameters/EnsembleInfoParameter.png new file mode 100644 index 0000000000..ab64243527 Binary files /dev/null and b/wrapping/python/docs/source/Images/Parameters/EnsembleInfoParameter.png differ diff --git a/wrapping/python/docs/source/Images/Parameters/FileSystemPathParameter.png b/wrapping/python/docs/source/Images/Parameters/FileSystemPathParameter.png new file mode 100644 index 0000000000..025641580e Binary files /dev/null and b/wrapping/python/docs/source/Images/Parameters/FileSystemPathParameter.png differ diff --git a/wrapping/python/docs/source/Images/Parameters/GeneratedFileListParameter.png b/wrapping/python/docs/source/Images/Parameters/GeneratedFileListParameter.png new file mode 100644 index 0000000000..82167af793 Binary files /dev/null and b/wrapping/python/docs/source/Images/Parameters/GeneratedFileListParameter.png differ diff --git a/wrapping/python/docs/source/Images/Parameters/GeometrySelectionParameter.png b/wrapping/python/docs/source/Images/Parameters/GeometrySelectionParameter.png new file mode 100644 index 0000000000..c54879187c Binary files /dev/null and b/wrapping/python/docs/source/Images/Parameters/GeometrySelectionParameter.png differ diff --git a/wrapping/python/docs/source/Images/Parameters/MultiArraySelectionParameter.png b/wrapping/python/docs/source/Images/Parameters/MultiArraySelectionParameter.png new file mode 100644 index 0000000000..16a63617c6 Binary files /dev/null and b/wrapping/python/docs/source/Images/Parameters/MultiArraySelectionParameter.png differ diff --git a/wrapping/python/docs/source/Images/Parameters/MultiPathSelectionParameter.png b/wrapping/python/docs/source/Images/Parameters/MultiPathSelectionParameter.png new file mode 100644 index 0000000000..1d5ae73527 Binary files /dev/null and b/wrapping/python/docs/source/Images/Parameters/MultiPathSelectionParameter.png differ diff --git a/wrapping/python/docs/source/Images/Parameters/NeighborListSelectionParameter.png b/wrapping/python/docs/source/Images/Parameters/NeighborListSelectionParameter.png new file mode 100644 index 0000000000..5035e22ebf Binary files /dev/null and b/wrapping/python/docs/source/Images/Parameters/NeighborListSelectionParameter.png differ diff --git a/wrapping/python/docs/source/Images/Parameters/NumericTypeParameter.png b/wrapping/python/docs/source/Images/Parameters/NumericTypeParameter.png new file mode 100644 index 0000000000..4900b8f760 Binary files /dev/null and b/wrapping/python/docs/source/Images/Parameters/NumericTypeParameter.png differ diff --git a/wrapping/python/docs/source/Images/Parameters/NumericalParameter.png b/wrapping/python/docs/source/Images/Parameters/NumericalParameter.png new file mode 100644 index 0000000000..45f39fc104 Binary files /dev/null and b/wrapping/python/docs/source/Images/Parameters/NumericalParameter.png differ diff --git a/wrapping/python/docs/source/Images/Parameters/ReadCSVFileParameter.png b/wrapping/python/docs/source/Images/Parameters/ReadCSVFileParameter.png new file mode 100644 index 0000000000..d8192fd05e Binary files /dev/null and b/wrapping/python/docs/source/Images/Parameters/ReadCSVFileParameter.png differ diff --git a/wrapping/python/docs/source/Images/Parameters/ReadHDF5DatasetParameter.png b/wrapping/python/docs/source/Images/Parameters/ReadHDF5DatasetParameter.png new file mode 100644 index 0000000000..f2e343ab14 Binary files /dev/null and b/wrapping/python/docs/source/Images/Parameters/ReadHDF5DatasetParameter.png differ diff --git a/wrapping/python/docs/source/Images/Parameters/StringParameter.png b/wrapping/python/docs/source/Images/Parameters/StringParameter.png new file mode 100644 index 0000000000..4e39c6fbf6 Binary files /dev/null and b/wrapping/python/docs/source/Images/Parameters/StringParameter.png differ diff --git a/wrapping/python/docs/source/Images/Parameters/VectorParameter.png b/wrapping/python/docs/source/Images/Parameters/VectorParameter.png new file mode 100644 index 0000000000..60f7fcb262 Binary files /dev/null and b/wrapping/python/docs/source/Images/Parameters/VectorParameter.png differ diff --git a/wrapping/python/docs/source/Images/Tutorial_1_Image_1.png b/wrapping/python/docs/source/Images/Tutorial_1_Image_1.png new file mode 100644 index 0000000000..1887c8c1e9 Binary files /dev/null and b/wrapping/python/docs/source/Images/Tutorial_1_Image_1.png differ diff --git a/wrapping/python/docs/source/Images/Tutorial_3/parameters_1.png b/wrapping/python/docs/source/Images/Tutorial_3/parameters_1.png new file mode 100644 index 0000000000..b3257138c2 Binary files /dev/null and b/wrapping/python/docs/source/Images/Tutorial_3/parameters_1.png differ diff --git a/wrapping/python/docs/source/Images/Tutorial_3/parameters_2.png b/wrapping/python/docs/source/Images/Tutorial_3/parameters_2.png new file mode 100644 index 0000000000..1a9bba8d2b Binary files /dev/null and b/wrapping/python/docs/source/Images/Tutorial_3/parameters_2.png differ diff --git a/wrapping/python/docs/source/Images/Tutorial_3/parameters_3.png b/wrapping/python/docs/source/Images/Tutorial_3/parameters_3.png new file mode 100644 index 0000000000..8f0497e2cb Binary files /dev/null and b/wrapping/python/docs/source/Images/Tutorial_3/parameters_3.png differ diff --git a/wrapping/python/docs/source/Images/Tutorial_3/parameters_4.png b/wrapping/python/docs/source/Images/Tutorial_3/parameters_4.png new file mode 100644 index 0000000000..ca6f02ccaa Binary files /dev/null and b/wrapping/python/docs/source/Images/Tutorial_3/parameters_4.png differ diff --git a/wrapping/python/docs/source/Images/Tutorial_3/parameters_5.png b/wrapping/python/docs/source/Images/Tutorial_3/parameters_5.png new file mode 100644 index 0000000000..1c864a7229 Binary files /dev/null and b/wrapping/python/docs/source/Images/Tutorial_3/parameters_5.png differ diff --git a/wrapping/python/docs/source/Images/Tutorial_3/parameters_6.png b/wrapping/python/docs/source/Images/Tutorial_3/parameters_6.png new file mode 100644 index 0000000000..0cddafe1ee Binary files /dev/null and b/wrapping/python/docs/source/Images/Tutorial_3/parameters_6.png differ diff --git a/wrapping/python/docs/source/Images/Tutorial_3/plugin_generated.png b/wrapping/python/docs/source/Images/Tutorial_3/plugin_generated.png new file mode 100644 index 0000000000..626a449fa3 Binary files /dev/null and b/wrapping/python/docs/source/Images/Tutorial_3/plugin_generated.png differ diff --git a/wrapping/python/docs/source/Images/Tutorial_3/plugin_loading_1.png b/wrapping/python/docs/source/Images/Tutorial_3/plugin_loading_1.png new file mode 100644 index 0000000000..138582ca31 Binary files /dev/null and b/wrapping/python/docs/source/Images/Tutorial_3/plugin_loading_1.png differ diff --git a/wrapping/python/docs/source/Images/Tutorial_3/plugin_loading_2.png b/wrapping/python/docs/source/Images/Tutorial_3/plugin_loading_2.png new file mode 100644 index 0000000000..b169da939e Binary files /dev/null and b/wrapping/python/docs/source/Images/Tutorial_3/plugin_loading_2.png differ diff --git a/wrapping/python/docs/source/Images/Tutorial_3/plugin_loading_3.png b/wrapping/python/docs/source/Images/Tutorial_3/plugin_loading_3.png new file mode 100644 index 0000000000..690bbcf3be Binary files /dev/null and b/wrapping/python/docs/source/Images/Tutorial_3/plugin_loading_3.png differ diff --git a/wrapping/python/docs/source/Python_Introduction.rst b/wrapping/python/docs/source/Python_Introduction.rst index 4a7188d0bb..752834b8fe 100644 --- a/wrapping/python/docs/source/Python_Introduction.rst +++ b/wrapping/python/docs/source/Python_Introduction.rst @@ -156,7 +156,7 @@ external data sources into the DataArray. npdata[:] = np.loadtxt(file_path, delimiter=',') Within the **simplnx** code repository, there are example python files that can be used -as a starting point. `GitHub.com `_ +as a starting point. `GitHub.com `_ Importing a .dream3d File ------------------------- @@ -245,7 +245,7 @@ of the list of vertex values (XYZ as 32 bit floating point values) and the conne list for the 1D, 2D and 3D geometries. :ref:`Please see the appropriate sections in the manual for detailed descriptions. ` -There are working examples within the python file . +There are working examples within the python file . The below code will create a TriangleGeometry by importing the vertices and triangle connectivity from a sample file. @@ -321,7 +321,7 @@ DataArray through the following: The following code examples show how to create a simplnx DataArray and then use that array as a numpy view. -The next code section was take from `basic_arrays.py `__ +The next code section was take from `basic_arrays.py `__ .. code:: python @@ -346,7 +346,7 @@ The next code section was take from `basic_arrays.py `__ +The next code section was take from `basic_arrays.py `__ .. code:: python diff --git a/wrapping/python/docs/source/Tutorial_1.rst b/wrapping/python/docs/source/Tutorial_1.rst new file mode 100644 index 0000000000..ac89c13956 --- /dev/null +++ b/wrapping/python/docs/source/Tutorial_1.rst @@ -0,0 +1,308 @@ + +.. _Tutorial_1: + +===================================== +Tutorial 1: Basic Python Integration +===================================== + +This tutorial is meant to be a very basic introduction to interacting with the DREAM3D-NX underlying library called 'simplnx'. This +tutorial will cover the following topics: + +- Environment setup +- Minimal import statements +- Executing a few basic filters +- Accessing your data through a Numpy View + +Once you understand how to execute a filter, all filters are generally setup the same way. Use the search feature on the web site to find the filter +that you are interested in running. + +.. _Tutorial_1_Setup: + +######################################### +1.1 Anaconda Virtual Environment Setup +######################################### + +.. code:: shell + + conda config --add channels conda-forge + conda config --set channel_priority strict + conda create -c bluequartzsoftware -n nxpython python=3.12 dream3dnx + conda activate nxpython + +.. _Tutorial_1_Intro: + +################################### +1.2 Introduction +################################### + +Setup your virtual environment following the directions from above. Then create a Tutorial_1.py file anywhere that you want and open that up in your Editor/IDE. + +.. _Tutorial_1_Imports: + +################################### +1.3 Necessary Import Statements +################################### + +Just about every python source code that is written will need the following import Statements: + +.. code:: python + + import simplnx as nx + import numpy as np + +If you will be using filters from DREAM3D-NX's other plugins, then you may additionally need the following: + +.. code:: python + + import itkimageprocessing as nxitk + import orientationanalysis as nxor + +Also use these import statements: + +.. code:: python + + from pathlib import Path + import matplotlib.pyplot as plt + +################################### +1.4 Set Output Directory +################################### + +Set the output directory where the output from this tutorial will be stored, and create the directory. We are going to set the output directory in the same location as the current script. + +.. code:: python + + output_dir = Path(__file__).parent / 'Output' / 'Tutorial_1_Output' + output_dir.mkdir(exist_ok=True, parents=True) + +######################################### +1.5 Creating the DataStructure Object +######################################### + +If you will be interacting with data stored in DREAM3D-NX, you will need to instantiate a :ref:`DataStructure` object. This is +simply done with the following line of code: + +.. code:: python + + # Create the DataStructure Object + data_structure = nx.DataStructure() + +A few caveats to take note of: +1. You can have as many :ref:`DataStructure` objects as you want/need. Typically all data is stored in a single DataStructure object but there are use cases where having more than a single :ref:`DataStructure` object is needed. +2. Only a **single** :ref:`DataStructure` object can be stored in a .dream3d file. + + +###################################################### +1.6 First Steps: Create a Group in the DataStructure +###################################################### + +As in the user interface of DREAM3D-NX, you as the developer can execute any of the filters from DREAM3D-NX using only Python codes. This is performed +by instantiating the filter and then calling the `execute()` method with the appropriate parameters used in the call. With the current API, we are tending to +inline instantiate the filter and execute it all in the same line. Some things to note with this small piece of code: + +- There will **always** be a required :ref:`DataStructure` object. All arguments in the `execute()` method are named arguments. None are positional. This means that each argument must be in the form of 'name=value'. +- The 2nd argument shows a use of the :ref:`DataPath` object. Lots of filters will require a :ref:`DataPath` object so this is a common use. +- There is a method called `hierarchy_to_str()` that is a part of the :ref:`DataStructure` class which will print the heirarchy of the DataStructure. + + +.. code:: python + + result = nx.CreateDataGroup.execute(data_structure=data_structure, + data_object_path=nx.DataPath("Top Level Group")) + print(f'{data_structure.hierarchy_to_str()}') + +If we were to run this code we would get the following: + +.. code:: text + + |--Top Level Group + + +**************************************** +1.6.1 Adding Multiple Groups (Optional) +**************************************** + +Let's try to add a bunch of groups to the :ref:`DataStructure` object by using a loop: + +.. code:: python + + for i in range(1, 6): + + current_data_group_path = nx.DataPath(f"Top Level Group {i}") + result = nx.CreateDataGroup.execute(data_structure=data_structure, + data_object_path=current_data_group_path) + print(f'{data_structure.hierarchy_to_str()}') + +And the output would look like the following: + +.. code:: text + + |--Top Level Group 1 + |--Top Level Group 2 + |--Top Level Group 3 + |--Top Level Group 4 + |--Top Level Group 5 + + + +################################################ +1.7 Result Objects +################################################ + +Each time a filter is executed, it will return a :ref:`nx.IFilter.ExecuteResult ` object. This +object can be interrogated for both warnings and errors that occured while the +filter was executing. A typical function that can be written to properly error +check the 'result' value is the following: + +.. code:: python + + def check_filter_result(filter: nx.IFilter, result: nx.IFilter.ExecuteResult) -> None: + """ + This function will check the `result` for any errors. If errors do exist then a + `RuntimeError` will be thrown. Your own code to modify this to return something + else that doesn't just stop your script in its tracks. + """ + if len(result.warnings) != 0: + for w in result.warnings: + print(f'Warning: ({w.code}) {w.message}') + + has_errors = len(result.errors) != 0 + if has_errors: + for err in result.errors: + print(f'Error: ({err.code}) {err.message}') + raise RuntimeError(result) + else: + print(f"{filter.name()} :: No errors running the filter") + +If you were to integrate this into your own code, then we would get the following when we wanted to execute a filter: + +.. code:: python + + result = nx.CreateDataGroup.execute(data_structure=data_structure, + data_object_path=nx.DataPath("Top Level Group")) + check_filter_result( nx.CreateDataGroup(), result) + + +################################################ +1.8 Creating a DataArray Object +################################################ + +Raw data is stored in a :ref:`DataArray` object within the :ref:`DataStructure`. The DREAM3D-NX python bindings only expose a subset of functionality +from the :ref:`DataArray`, enough to get the name, tuple shape and component shape. **ALL** interactions to modify a :ref:`DataArray` are done via a +`numpy view `_. Let us first create a :ref:`DataArray` object within the :ref:`DataStructure` by using the +:ref:`CreateDataArrayFilter ` filter. Adding into the current python source file... + +.. code:: python + + result = nx.CreateDataArrayFilter().execute(data_structure=data_structure, + component_count=1, + initialization_value="0", + numeric_type=nx.NumericType.float32, + output_data_array=nx.DataPath("Top Level Group/2D Array"), + tuple_dimensions=[[5,4]]) + check_filter_result( nx.CreateDataArrayFilter(), result) + print(f'{data_structure.hierarchy_to_str()}') + +Note how we are creating the array inside the very first :ref:`DataGroup` that we created. If we run the file from start to finish we now get the following output: + +.. code:: text + + |--Top Level Group + |--2D Array + |--Top Level Group 1 + |--Top Level Group 2 + |--Top Level Group 3 + |--Top Level Group 4 + |--Top Level Group 5 + +As you can see we have successfully created an array that can hold some data. The next step is to interact with that :ref:`DataArray` and use numpy to modify the array in place. + +################################################ +1.9 Modifying the DataArray Object using Numpy +################################################ + +The method from :ref:`DataStructure` that we will be using is item selection using the '[]' operator paired with an +immediate call to the '.npview()' method. This will retrieve the a numpy view of the DataArray that was created in the last step. + +.. code:: python + + array_view = data_structure["Top Level Group/2D Array"].npview() + +Now that we have a numpy view we can do anything to the array that numpy (or any other package that accepts numpy views) can do for us. For example, we can +create random data in the array using the following: + +.. code:: python + + # Fill the numpy data view with random numbers + rng = np.random.default_rng() + rng.standard_normal(out=array_view, dtype=np.float32) + print(f'{array_view}') + +The output from this code would print something similar to: + +.. code:: text + + [[[-1.3746183 ] + [-0.08409024] + [ 1.2792562 ] + [-0.37265882] + [ 0.05201177]] + + [[-0.11597582] + [-0.35329401] + [-0.88307136] + [-0.98040694] + [ 0.28385338]] + + [[ 0.7635286 ] + [-1.3911186 ] + [ 0.5670461 ] + [ 0.11915083] + [-0.8656706 ]] + + [[ 2.1133974 ] + [ 1.3168721 ] + [ 2.6951575 ] + [ 0.10712756] + [-0.07898012]]] + +And if you wanted to use `matplotlib `_ to view the data, that is easily done in the usual manner: + +.. code:: python + + # Show the result + plt.imshow(array_view) + plt.title("Random Data") + plt.axis('off') # to turn off axes + plt.show() + + +.. figure:: Images/Tutorial_1_Image_1.png + :alt: MatPlotLib output + + +################################################ +1.10 Saving your Data to a .dream3d file +################################################ + +Most pipelines would want to save any modified data to a .dream3d file (if you are wanting the easiest compatibility with DREAM3D-NX). In order +to do this one would run the :ref:`WriteDREAM3DFilter `. Appending the following code will write the entire +:ref:`DataStructure` to a .dream3d file (which is a plain HDF5 file). + +.. code:: python + + # Use the WriteDREAM3DFilter to write out the modified DataStructure to disk + result = nx.WriteDREAM3DFilter.execute(data_structure=data_structure, + export_file_path=str(output_dir / 'tutorial_1.dream3d'), + write_xdmf_file=False) + check_filter_result( nx.WriteDREAM3DFilter(), result) + + +################# +1.11 Full Example +################# + +Full example of this tutorial is located at: + +https://github.com/BlueQuartzSoftware/NXWorkshop/blob/develop/PythonTutorial/tutorial_1.py + diff --git a/wrapping/python/docs/source/Tutorial_2.rst b/wrapping/python/docs/source/Tutorial_2.rst new file mode 100644 index 0000000000..ca88876f54 --- /dev/null +++ b/wrapping/python/docs/source/Tutorial_2.rst @@ -0,0 +1,294 @@ +.. _Tutorial_2: + +================================== +Tutorial 2: Manipulating Pipelines +================================== + +This tutorial is an introduction on how to interact with simplnx pipelines. It will cover the following topics: + +- Inserting a filter into a pipeline +- Modifying the arguments of a filter in an existing pipeline +- Printing filter information from a pipeline + +################################### +2.1 Introduction +################################### + +Setup your environment in the same way as from :ref:`Tutorial 1`. In this tutorial we will be manipulating a basic pipeline. + +################################### +2.2 Necessary Import Statements +################################### + +Use the same import statements as from :ref:`Tutorial 1`: + +.. code:: python + + import simplnx as nx + import numpy as np + +If you will be using filters from DREAM3D-NX's other plugins, then you may additionally need the following: + +.. code:: python + + import itkimageprocessing as nxitk + import orientationanalysis as nxor + +Also use the following imports: + +.. code:: python + + from pathlib import Path + +################################### +2.3 Set Output Directory +################################### + +Set the output directory where the output from this tutorial will be stored, and create the directory. We are going to set the output directory in the same location as the current script. + +.. code:: python + + output_dir = Path(__file__).parent / 'Output' / 'Tutorial_2_Output' + output_dir.mkdir(exist_ok=True, parents=True) + +##################################### +2.4 Creating the DataStructure Object +##################################### + +.. code:: python + + data_structure = nx.DataStructure() + +This line creates a DataStructure object, which will serve as the overall container for our `simplnx` data. + +############################# +2.5 Reading the Pipeline File +############################# + +SIMPLNX has an object called the "Pipeline" object that holds a linear list of filters. This object +has an API that allows the developer to query the pipeline for filters and also to insert filters +into the pipeline. We are going to add a line of code to read a pipeline directly from a ".d3dpipeline" file. + +.. code:: python + + pipeline_file_path = Path(__file__).parent / 'Pipelines' / 'lesson_2.d3dpipeline' + pipeline = nx.Pipeline().from_file(str(pipeline_file_path)) + +######################################## +2.6 Printing Pipeline Filter Information +######################################## + +One basic example of using the pipeline object is to loop over each filter in the pipeline and print its human name. This example can +be done as follows: + +.. code:: python + + for index, filter in enumerate(pipeline): + print(f"[{index}]: {filter.get_filter().human_name()}") + +This loop iterates over each filter in the pipeline and prints out its index and human name. + +The output should look like this: + +.. code:: text + + [0]: Create Geometry + [1]: Create Data Array + [2]: Write DREAM3D NX File + +###################################### +2.7 Inserting a Filter into a Pipeline +###################################### + +To extend or customize a data processing workflow, you might need to insert new filters into an existing pipeline. The following steps demonstrate how to do this. + +*********************************** +2.7.1 Defining the Filter Arguments +*********************************** + +Here, we define the arguments for the new filter. These arguments specify the configuration for the CreateDataGroup filter that we will add to the pipeline. + +.. code:: python + + create_data_group_args = { + "data_object_path": nx.DataPath("Small IN100/EBSD Data") + } + +************************** +2.7.2 Inserting the Filter +************************** + +We can insert the new filter into the pipeline at the specified position (index 2). The CreateDataGroupFilter is used to create the filter, and the arguments are passed to configure it. + +.. code:: python + + pipeline.insert(2, nx.CreateDataGroupFilter(), create_data_group_args) + +.. _2.7.3: + +************************************* +2.7.3 Executing the Modified Pipeline +************************************* + +Each time a pipeline is executed, it will return an :ref:`nx.IFilter.ExecuteResult ` object. This +object contains the errors and warnings that occurred while the filter was executing. + +A typical function that can be written to properly error check the 'result' value is the following: + +.. code:: python + + def check_pipeline_result(result: nx.Result) -> None: + """ + This function will check the `result` for any errors. If errors do exist then a + `RuntimeError` will be thrown. Your own code to modify this to return something + else that doesn't just stop your script in its tracks. + """ + if len(result.warnings) != 0: + for w in result.warnings: + print(f'Warning: ({w.code}) {w.message}') + + has_errors = len(result.errors) != 0 + if has_errors: + for err in result.errors: + print(f'Error: ({err.code}) {err.message}') + raise RuntimeError(result) + + print(f"Pipeline :: No errors running the pipeline") + +If you were to integrate this into your own code, then we would execute the pipeline and check the result like this: + +.. code:: python + + result = pipeline.execute(data_structure) + check_pipeline_result(result=result) + +This code executes the modified pipeline with the DataStructure object. The `check_pipeline_result function` is used to verify the execution result, printing out any errors or warnings from the pipeline execution. + +.. _2.7.4: + +********************************** +2.7.4 Saving the Modified Pipeline +********************************** + +We can save the modified pipeline configuration to a new pipeline file for future use. +Let's create the output pipeline file path using pathlib, and then call `to_file` to save the pipeline to a pipeline file. + +.. code:: python + + output_pipeline_file_path = output_dir / 'lesson_2a_modified_pipeline.d3dpipeline' + pipeline.to_file("Modified Pipeline", str(output_pipeline_file_path)) + +############################## +2.8 Modifying Pipeline Filters +############################## + +Sometimes you need to adjust the parameters of existing filters in your pipeline. Here’s how you can modify a filter's parameters. + +.. _2.8.1: + +************************************ +2.8.1 Modifying the Filter Arguments +************************************ + +We can modify the parameters of a given filter by writing and using a short method: + +.. code:: python + + def modify_pipeline_filter(pipeline: nx.Pipeline, index: int, key: str, value): + # The get_args method retrieves the current arguments, and set_args applies the modifications. + param_dict = pipeline[index].get_args() + param_dict[key] = value + pipeline[index].set_args(param_dict) + + modify_pipeline_filter(pipeline, 1, "numeric_type", nx.NumericType.int8) + +Here, we use the modify_pipeline_filter method to change the 2nd filter's NumericType parameter value to int8. + +************************************* +2.8.2 Executing the Modified Pipeline +************************************* + +Just like in :ref:`Section 2.7.3 <2.7.3>`, we can execute the modified pipeline and check the result using the check_pipeline_result method: + +.. code:: python + + result = pipeline.execute(data_structure) + check_pipeline_result(result=result) + +********************************** +2.8.3 Saving the Modified Pipeline +********************************** + +Just like in :ref:`Section 2.7.4 <2.7.4>`, we can save the modified pipeline to a new pipeline file for future use: + +.. code:: python + + output_pipeline_file_path = output_dir / 'lesson_2b_modified_pipeline.d3dpipeline' + pipeline.to_file("Modified Pipeline", str(output_pipeline_file_path)) + +######################### +2.9 Looping On a Pipeline +######################### + +In certain cases, it might be necessary to modify pipeline filters in a loop. One example where this is handy is when the same pipeline needs to be run on multiple image slices. + +Let's modify a pipeline in a loop to generate IPF maps using DREAM3D-NX. + +The Pipeline that we will modify is as follows: + 1. Read EDAX EBSD Data (.ang) + 2. Rotate Euler Reference Frame + 3. Rotate Sample Reference Frame + 4. Multi-Threshold Objects + 5. Generate IPF Colors + 6. Write Image (ITK) + 7. Write DREAM3D NX File + +Filter 1 is the ReadAngDataFilter which we will need to adjust the input file (https://www.dream3d.io/python_docs/OrientationAnalysis.html#OrientationAnalysis.ReadAngDataFilter). + +Filter 6 is the image writing filter where we need to adjust the output file (https://www.dream3d.io/python_docs/ITKImageProcessing.html#write-image-itk). + +Filter 7 is the write dream3d file filter where we need to adjust the output file (https://www.dream3d.io/python_docs/simplnx.html#write-dream3d-nx-file). + +************************* +2.9.1 Setting Up the Loop +************************* + +The `check_pipeline_result` method from :ref:`Section 2.7.3 <2.7.3>` and the `modify_pipeline_filter` method from :ref:`Section 2.8.1 <2.8.1>` can be used inside a loop to update file paths for the 1st, 6th, and 7th filters. The pipeline can be executed and saved (and the execution result checked) at the end of each iteration of the loop. + +.. code:: python + + # Loop over the EBSD pipeline + edax_ipf_colors_output_dir = output_dir / 'Edax_IPF_Colors' + edax_ipf_colors_output_dir.mkdir(exist_ok=True, parents=True) + for i in range(1, 6): + # Create the data structure + data_structure = nx.DataStructure() + + # Read the pipeline file + pipeline_file_path = Path(__file__).parent / 'Pipelines' / 'lesson_2_ebsd.d3dpipeline' + pipeline = nx.Pipeline().from_file(str(pipeline_file_path)) + + # Modify file paths for the 1st, 6th, and 7th filters + modify_pipeline_filter(pipeline, 0, "input_file", str(Path(__file__).parent / 'Data' / 'Small_IN100' / f'Slice_{i}.ang')) + modify_pipeline_filter(pipeline, 5, "file_name", str(edax_ipf_colors_output_dir / f'Small_IN100_Slice_{i}.png')) + modify_pipeline_filter(pipeline, 6, "export_file_path", str(edax_ipf_colors_output_dir.parent / f'Small_IN100_Slice_{i}.dream3d')) + + # Execute the modified pipeline + result = pipeline.execute(data_structure) + check_pipeline_result(result=result) + + # Output the modified pipeline + output_pipeline_file_path = edax_ipf_colors_output_dir / f'Small_IN100_Slice_{i}.d3dpipeline' + pipeline.to_file(f"Small_IN100_Slice_{i}", str(output_pipeline_file_path)) + +The code above will generate IPF maps for SmallIN100 slices 1-6. + +################## +2.10 Full Examples +################## + +Full examples of the concepts in this tutorial are located at: + +https://github.com/BlueQuartzSoftware/NXWorkshop/blob/develop/PythonTutorial/tutorial_2a.py +https://github.com/BlueQuartzSoftware/NXWorkshop/blob/develop/PythonTutorial/tutorial_2b.py +https://github.com/BlueQuartzSoftware/NXWorkshop/blob/develop/PythonTutorial/tutorial_2c.py \ No newline at end of file diff --git a/wrapping/python/docs/source/Tutorial_3.rst b/wrapping/python/docs/source/Tutorial_3.rst new file mode 100644 index 0000000000..0eea9ebe8d --- /dev/null +++ b/wrapping/python/docs/source/Tutorial_3.rst @@ -0,0 +1,730 @@ +.. _Tutorial_3: + +============================================== +Tutorial 3: Writing a Custom DREAM3D-NX Filter +============================================== + +In this tutorial, we will learn how to write a custom filter for DREAM3D-NX. We will use the `Create Python Plugin and/or Filters` filter to generate the skeleton code and then fill in the `default_tags`, `parameters`, `preflight_impl`, and `execute_impl` methods. + +########################################### +3.1 Generating the Filter's Python Skeleton +########################################### + +Python filters for `simplnx` are contained in Python plugins. `simplnx` loads Python plugins at startup and makes the filters in each plugin available for use in the application. + +The first step in creating a new Python plugin with a custom Python filter inside it is to generate the skeleton code for both. DREAM3D-NX provides a convenient filter called `Create Python Plugin and/or Filters` to help with this. + +Open the DREAM3D-NX application, and add the `Create Python Plugin and/or Filters` filter to the pipeline. + +3.1.1 Configuring and Executing the Skeleton Generation Filter +-------------------------------------------------------------- + +The `Create Python Plugin and/or Filters` filter has the following inputs: + +- **Use Existing Plugin**: Checking this checkbox will generate the skeleton code for new filters into an existing plugin. Leaving the checkbox unchecked will generate skeleton code for a new plugin AND generate skeleton code for filters inside that new plugin. The inputs that follow this checkbox will change depending on whether the checkbox is checked or not. + +When Creating a New Plugin (**Use Existing Plugin** is unchecked) +***************************************************************** + +- **Name of Plugin**: This field specifies the name of your plugin. This name will be used as the directory name for your plugin and as the identifier for your plugin within the generated skeleton code. + +- **Human Name of Plugin**: This is the user-friendly name of your plugin. It can be the same as the `Name of Plugin` or a more descriptive name. This is the name that will be used when your filter appears in the Filter List. + +- **Plugin Output Directory**: This specifies the directory where the generated plugin and filter files will be saved. You can select the directory by clicking the `Save` button. + +- **Filter Names (comma-separated)**: This field allows you to specify the programmatic names of the filters you want to create. Each filter name should be separated by a comma. For example, `FirstFilter,SecondFilter`. + +When Using an Existing Plugin (**Use Existing Plugin** is checked) +***************************************************************** + +- **Existing Plugin Location**: This specifies the directory of the existing plugin to which you want to add new filters. You can select the directory by clicking the `Browse` button. + +- **Filter Names (comma-separated)**: This field allows you to specify the programmatic names of the filters you want to create. Each filter name should be separated by a comma. For example, `FirstFilter,SecondFilter`. + +We are going to generate a new plugin and filters, so let's set the following inputs: + +- **Use Existing Plugin**: Unchecked +- **Name of Plugin**: ExamplePlugin +- **Human Name of Plugin**: Example Plugin +- **Plugin Output Directory**: /plugin/output/directory (please fill this in with an actual output directory!) +- **Filter Names (comma-separated)**: ArrayCalculationFilter + +These settings will generate a new plugin called `ExamplePlugin` at `/plugin/output/directory`. Inside the `ExamplePlugin`, the skeleton code for a new filter called `ArrayCalculationFilter` will be generated in a Python file called `ArrayCalculationFilter.py`. + +After configuring these inputs, execute the filter. Any existing files with the same names will be overwritten. + +Your newly generated plugin should have a file structure that looks like the following: + +.. figure:: Images/Tutorial_3/plugin_generated.png + +3.1.2 Loading The New Python Plugin +----------------------------------- + +Next, we need to load the newly generated Python plugin into DREAM3D-NX. Open DREAM3D-NX and then open the Preferences panel found in the file menu. Navigate to the Python tab. + +.. figure:: Images/Tutorial_3/plugin_loading_1.png + +Press the "plus" button on the right side to add the plugin to the list. You need to pick the internal plugin source directory, not the top-level directory, so the path should be something like `/plugin/output/directory/ExamplePlugin/src/ExamplePlugin`. + +.. figure:: Images/Tutorial_3/plugin_loading_2.png + +Press the OK button, and then DREAM3D-NX will automatically load the `ExamplePlugin` plugin for you. You should now be able to see your filter in the Filter List if you search for `ArrayCalculationFilter (Python)`. + +.. figure:: Images/Tutorial_3/plugin_loading_3.png + +Now, navigate to `/plugin/output/directory/ExamplePlugin/src/ExamplePlugin` on the file system and open `ArrayCalculationFilter.py`. + +Let's begin editing `ArrayCalculationFilter's` skeleton methods. + +################################ +3.2 Editing the Generated Filter +################################ + +We are going to edit this custom filter to have the filter take in two arrays, perform an element-wise calculation, and then store the result in an output array. + +Let's begin by editing the `human_name`, `default_tags`, `parameters`, `preflight_impl`, and `execute_impl` methods. All other methods should remain untouched. + +3.2.1 Editing the Human Name +---------------------------- + +The `human_name` method in a DREAM3D-NX filter returns a user-friendly name for the filter. This name is displayed in the DREAM3D-NX interface, making it easier for users to understand the purpose of the filter. Editing the `human_name` method allows developers to provide a clear and descriptive name that enhances the usability of the filter. + +.. code:: python + + def human_name(self) -> str: + """This returns the name of the filter as a user of DREAM3DNX would see it + :return: The filter's human name + :rtype: string + """ + return 'ArrayCalculationFilter (Python)' + +Let's update the human name so that it is easier to read: + +.. code:: python + + def human_name(self) -> str: + """This returns the name of the filter as a user of DREAM3DNX would see it + :return: The filter's human name + :rtype: string + """ + return 'Calculate Element-wise Array Result (Python)' + +This name does not have to match the filter's class name; it can be updated to display any name that you want. This human-readable name will be displayed in the DREAM3D-NX interface wherever the filter is listed, making it easy for users to identify the filter. + +We typically include the term `Python` in parenthesis at the end, just to make it clear to users in the GUI that this is a Python filter. + +3.2.2 Editing the Default Tags +------------------------------ + +The `default_tags` method allows the filter developer to define a set of tags for the filter. These tags can be used to categorize and search for your filter within the DREAM3D-NX interface. + +.. code:: python + + def default_tags(self) -> List[str]: + """This returns the default tags for this filter + :return: The default tags for the filter + :rtype: list + """ + return ['python', 'ArrayCalculationFilter'] + +In this example, the `default_tags` method returns a list of tags associated with the filter. These tags can be keywords that describe the filter's functionality, category, or any other relevant information. + +We can update the tags to include a few more terms: + +.. code:: python + + def default_tags(self) -> List[str]: + """This returns the default tags for this filter + :return: The default tags for the filter + :rtype: list + """ + return ['python', 'ArrayCalculationFilter', 'compute', 'generate'] + +Now when the user searches for `compute` or `generate` in the DREAM3D-NX interface, this filter will be matched and listed. + +3.2.3 Defining Filter Parameters +-------------------------------- + +Next, we need to define the parameters that our filter will accept. These parameters will be used by the filter during its execution. Parameters are essential as they allow users to input values that will be utilized in the filter's logic. + +The `parameters` method is where we define the parameters for our filter. Each parameter is given a key, which should be in snake_case, and a descriptive name in ALL_CAPS. This method returns an `nx.Parameters` object that collects these parameters. + +**NOTE**: The skeleton code will have example parameters and example keys, so please remove those so that your keys and `parameters` method look like this: + +.. code:: python + + """ + This section should contain the 'keys' that store each parameter. The value of the key should be snake_case. The name + of the value should be ALL_CAPITOL_KEY + """ + + def parameters(self) -> nx.Parameters: + """This function defines the parameters that are needed by the filter. Parameters collect the values from the user interface + and pack them up into a dictionary for use in the preflight and execute methods. + """ + params = nx.Parameters() + + return params + +We want this filter to take in two existing arrays, so that means we need to use two ArraySelectionParameters: + +.. code:: python + + """ + This section should contain the 'keys' that store each parameter. The value of the key should be snake_case. The name + of the value should be ALL_CAPITOL_KEY + """ + ARRAY_1_PATH_KEY = 'array_1_path' + ARRAY_2_PATH_KEY = 'array_2_path' + + def parameters(self) -> nx.Parameters: + """This function defines the parameters that are needed by the filter. Parameters collect the values from the user interface + and pack them up into a dictionary for use in the preflight and execute methods. + """ + params = nx.Parameters() + + params.insert(nx.ArraySelectionParameter(name=ArrayStatisticsFilter.ARRAY_1_PATH_KEY, human_name='Array 1', help_text='The 1st array that will be used in the statistics calculations', default_value=nx.DataPath(), allowed_types={nx.DataType.float32, nx.DataType.float64}, required_comps=[[1]])) + params.insert(nx.ArraySelectionParameter(name=ArrayStatisticsFilter.ARRAY_2_PATH_KEY, human_name='Array 2', help_text='The 2nd array that will be used in the statistics calculations', default_value=nx.DataPath(), allowed_types={nx.DataType.float32, nx.DataType.float64}, required_comps=[[1]])) + + return params + +Above, we define two ArraySelectionParameters with keys `ARRAY_1_PATH_KEY` and `ARRAY_2_PATH_KEY` (defined above the parameters method), human names `Array 1` and `Array 2`, descriptive help text, empty default values (no array will be selected by default), floating-point arrays as the only allowable type, and a required component dimension of 1. + +Here is what those two ArraySelectionParameters look like when launching the filter in the user interface: + +.. figure:: Images/Tutorial_3/parameters_1.png + +We also need to allow the user to pick a mathematical operation to perform element-wise on the arrays. To do this, we are going to add a ChoicesParameter: + +.. code:: python + + """ + This section should contain the 'keys' that store each parameter. The value of the key should be snake_case. The name + of the value should be ALL_CAPITOL_KEY + """ + ARRAY_1_PATH_KEY = 'array_1_path' + ARRAY_2_PATH_KEY = 'array_2_path' + MATH_OPERATION_KEY = 'math_operation' + + def parameters(self) -> nx.Parameters: + """This function defines the parameters that are needed by the filter. Parameters collect the values from the user interface + and pack them up into a dictionary for use in the preflight and execute methods. + """ + params = nx.Parameters() + + params.insert(nx.ArraySelectionParameter(name=ArrayStatisticsFilter.ARRAY_1_PATH_KEY, human_name='Array 1', help_text='The 1st array that will be used in the statistics calculations', default_value=nx.DataPath(), allowed_types={nx.DataType.float32, nx.DataType.float64}, required_comps=[[1]])) + params.insert(nx.ArraySelectionParameter(name=ArrayStatisticsFilter.ARRAY_2_PATH_KEY, human_name='Array 2', help_text='The 2nd array that will be used in the statistics calculations', default_value=nx.DataPath(), allowed_types={nx.DataType.float32, nx.DataType.float64}, required_comps=[[1]])) + + params.insert(nx.ChoicesParameter(name=ArrayStatisticsFilter.MATH_OPERATION_KEY, human_name='Element-wise Operation', help_text='The operation to perform element-wise on both arrays.', default_value=0, choices=['Add', 'Subtract', 'Multiply', 'Divide'])) + + return params + +Above, we added a ChoicesParameter with a key called `MATH_OPERATION_KEY` (defined above the parameters method), human name `Element-wise Operation`, descriptive help text, default value of 0 (the first item in the choice list), and four text choices. + +Here is what the parameters section looks like with the ChoicesParameter included: + +.. figure:: Images/Tutorial_3/parameters_2.png + +.. figure:: Images/Tutorial_3/parameters_3.png + +When the user adjusts the combo box selection, the index for the current combo box selection gets stored in the `args` dictionary and can be retrieved during preflight and execute. + +Finally, we want to create a new array to store the output, so to do that we will need to use a ArrayCreationParameter: + +.. code:: python + + """ + This section should contain the 'keys' that store each parameter. The value of the key should be snake_case. The name + of the value should be ALL_CAPITOL_KEY + """ + ARRAY_1_PATH_KEY = 'array_1_path' + ARRAY_2_PATH_KEY = 'array_2_path' + MATH_OPERATION_KEY = 'math_operation' + OUTPUT_ARRAY_KEY = 'output_array' + + def parameters(self) -> nx.Parameters: + """This function defines the parameters that are needed by the filter. Parameters collect the values from the user interface + and pack them up into a dictionary for use in the preflight and execute methods. + """ + params = nx.Parameters() + + params.insert(nx.ArraySelectionParameter(name=ArrayStatisticsFilter.ARRAY_1_PATH_KEY, human_name='Array 1', help_text='The 1st array that will be used in the statistics calculations', default_value=nx.DataPath(), allowed_types={nx.DataType.float32, nx.DataType.float64}, required_comps=[[1]])) + params.insert(nx.ArraySelectionParameter(name=ArrayStatisticsFilter.ARRAY_2_PATH_KEY, human_name='Array 2', help_text='The 2nd array that will be used in the statistics calculations', default_value=nx.DataPath(), allowed_types={nx.DataType.float32, nx.DataType.float64}, required_comps=[[1]])) + + params.insert(nx.ChoicesParameter(name=ArrayStatisticsFilter.MATH_OPERATION_KEY, human_name='Element-wise Operation', help_text='The operation to perform element-wise on both arrays.', default_value=0, choices=['Add', 'Subtract', 'Multiply', 'Divide'])) + + params.insert(nx.ArrayCreationParameter(name=ArrayStatisticsFilter.OUTPUT_ARRAY_KEY, human_name='Output Array', help_text='The output array that contains the calculation results.', default_value=nx.DataPath())) + + return params + +Above, we added a ArrayCreationParameter with a key called `OUTPUT_ARRAY_KEY` (defined above the parameters method), human name `Output Array`, descriptive help text, and an empty DataPath as the default value (empty by default). + +Here is what the parameters section looks like with the ArrayCreationParameter included: + +.. figure:: Images/Tutorial_3/parameters_4.png + +This is what the parameters look like when they have values: + +.. figure:: Images/Tutorial_3/parameters_5.png + +Grouping Parameters (optional) +****************************** +It's possible to group parameters using the Separator class: + +.. code:: python + + """ + This section should contain the 'keys' that store each parameter. The value of the key should be snake_case. The name + of the value should be ALL_CAPITOL_KEY + """ + ARRAY_1_PATH_KEY = 'array_1_path' + ARRAY_2_PATH_KEY = 'array_2_path' + MATH_OPERATION_KEY = 'math_operation' + OUTPUT_ARRAY_KEY = 'output_array' + + def parameters(self) -> nx.Parameters: + """This function defines the parameters that are needed by the filter. Parameters collect the values from the user interface + and pack them up into a dictionary for use in the preflight and execute methods. + """ + params = nx.Parameters() + + params.insert(params.Separator("Input Parameters")) # Group the input parameters + params.insert(nx.ArraySelectionParameter(name=ArrayStatisticsFilter.ARRAY_1_PATH_KEY, human_name='Array 1', help_text='The 1st array that will be used in the statistics calculations', default_value=nx.DataPath(), allowed_types={nx.DataType.float32, nx.DataType.float64}, required_comps=[[1]])) + params.insert(nx.ArraySelectionParameter(name=ArrayStatisticsFilter.ARRAY_2_PATH_KEY, human_name='Array 2', help_text='The 2nd array that will be used in the statistics calculations', default_value=nx.DataPath(), allowed_types={nx.DataType.float32, nx.DataType.float64}, required_comps=[[1]])) + + params.insert(nx.ChoicesParameter(name=ArrayStatisticsFilter.MATH_OPERATION_KEY, human_name='Element-wise Operation', help_text='The operation to perform element-wise on both arrays.', default_value=0, choices=['Add', 'Subtract', 'Multiply', 'Divide'])) + + params.insert(params.Separator("Output Parameters")) # Group the output parameters + params.insert(nx.ArrayCreationParameter(name=ArrayStatisticsFilter.OUTPUT_ARRAY_KEY, human_name='Output Array', help_text='The output array that contains the calculation results.', default_value=nx.DataPath())) + + return params + +Above, we are using the separator class to group the ArraySelectionParameters and ChoicesParameter into a group called "Input Parameters", and the ArrayCreationParameter into a group called "Output Parameters". Here's what the user interface looks like with the groupings: + +.. figure:: Images/Tutorial_3/parameters_6.png + +Now that we have added all of our parameters, it's time to fill out the `preflight_impl` method. + +3.2.4 Implementing the Preflight Method +--------------------------------------- + +The `preflight_impl` method is used to perform any necessary checks and setup before the filter is executed. This includes validating input parameters, preparing actions that will modify the data structure, and communicating with the user interface. + +**NOTE**: The skeleton code for `preflight_impl` will have example code in it, so please remove it so that the method looks like this: + +.. code:: python + + def preflight_impl(self, data_structure: nx.DataStructure, args: dict, message_handler: nx.IFilter.MessageHandler, should_cancel: nx.AtomicBoolProxy) -> nx.IFilter.PreflightResult: + """This method preflights the filter and ensures that all inputs are sanity-checked. It validates array sizes if known at preflight time and sets the tuple dimensions of an array when in doubt. + :returns: Preflight result containing actions, errors, warnings, and preflight values. + :rtype: nx.IFilter.PreflightResult + """ + + # Write your preflight code here. + +In the above `preflight_impl` method, there is a dictionary called `args` that contains all the parameter values that the user chose in the filter. Let's grab all of the parameter values from the `args` dictionary now: + +.. code:: python + + # Extract the values from the user interface from the 'args' dictionary + array_1_path: nx.DataPath = args[ArrayStatisticsFilter.ARRAY_1_PATH_KEY] # This gets the DataPath for Array 1 that the user chose + array_2_path: nx.DataPath = args[ArrayStatisticsFilter.ARRAY_2_PATH_KEY] # This gets the DataPath for Array 2 that the user chose + math_operation_choice_index: int = args[ArrayStatisticsFilter.MATH_OPERATION_KEY] # This gets the current index of the Element-wise Operation combo box that the user chose + output_array_path: nx.DataPath = args[ArrayStatisticsFilter.OUTPUT_ARRAY_KEY] # This gets the DataPath for the Output Array that the user chose + +`simplnx` automatically validates that all DataPaths coming from ArraySelectionParameters are valid and exist; any DataPath coming from ArraySelectionParameter that does not exist or is invalid will have an error automatically generated. + +`simplnx` also automatically validates that any indices coming from ChoicesParameters are within the proper bounds, so we don't need to manually validate that either. + +In the `preflight_impl` method, there is no way to get access to the actual incoming `Array 1` and `Array 2` data, so we will need to validate that the element-wise division operation will not divide by 0 in the `execute_impl` method instead. + +To be able to do an element-wise operation on two arrays, the two arrays need to have the same number of tuples. So let's validate that: + +.. code:: python + + # Validate that the number of tuples for both arrays is equal + array_1: nx.IDataArray = data_structure[array_1_path] # Retrieve the 'Array 1' DataArray object from the DataStructure + array_2: nx.IDataArray = data_structure[array_2_path] # Retrieve the 'Array 2' DataArray object from the DataStructure + array_1_num_tuples = np.prod(array_1.tdims) # Compute Array 1's total number of tuples by multiplying Array 1's tuple dimensions together + array_2_num_tuples = np.prod(array_2.tdims) # Compute Array 2's total number of tuples by multiplying Array 2's tuple dimensions together + if array_1_num_tuples != array_2_num_tuples: # Compare Array 1's and Array 2's total number of tuples, return an error if they are not equal + return nx.IFilter.PreflightResult(nx.OutputActions(), [nx.Error(-65020, f"Array 1's number of tuples ({array_1_num_tuples}) do not match Array 2's number of tuples ({array_2_num_tuples}).")]) + +The code above does the following: + +1. Retrieves the actual `Array 1` and `Array 2` DataArray objects from the DataStructure, using the DataPaths, `array_1_path` and `array_2_path`, that the filter user chose. +2. Computes the total number of tuples for each DataArray by multiplying the tuple dimensions together. +3. Compares the total number of tuples for both arrays to see if they are equal. If they aren't, the filter returns an error. + +Don't forget to add the numpy import statement at the top of the file: + +.. code:: python + + from typing import List + import simplnx as nx + import numpy as np # Needed to do array operations + +`simplnx` automatically validates that the component dimensions are equal to 1, so we do not need to manually compare both arrays' component dimensions for equality. This is because earlier we set the required component dimensions equal to 1 on both ArraySelectionParameters: + +.. code:: python + + # required_comps = [[1]] + params.insert(nx.ArraySelectionParameter(name=ArrayStatisticsFilter.ARRAY_1_PATH_KEY, human_name='Array 1', help_text='The 1st array that will be used in the statistics calculations', default_value=nx.DataPath(), allowed_types={nx.DataType.float32, nx.DataType.float64}, required_comps=[[1]])) + params.insert(nx.ArraySelectionParameter(name=ArrayStatisticsFilter.ARRAY_2_PATH_KEY, human_name='Array 2', help_text='The 2nd array that will be used in the statistics calculations', default_value=nx.DataPath(), allowed_types={nx.DataType.float32, nx.DataType.float64}, required_comps=[[1]])) + +We also need to create the output array. First, we are going to create an OutputActions object, which is a container that holds action objects that modify the DataStructure: + +.. code:: python + + # Create an OutputActions object to hold any DataStructure modifications that we are going to make + output_actions = nx.OutputActions() + +Next, we are going to append a CreateArrayAction to the OutputActions object: + +.. code:: python + + # Append a CreateArrayAction to the OutputActions object, this will create the output array in the DataStructure + output_actions.append_action(nx.CreateArrayAction(type=array_1.data_type, t_dims=array_1.tdims, c_dims=array_1.cdims, path=output_array_path)) + +This creates a new output array in the DataStructure at DataPath `output_array_path`. This newly created array will have the same data type, tuple dimensions, and component dimensions as `Array 1`. + +Finally, return a PreflightResult object from the method (which includes the output actions that you created): + +.. code:: python + + # Return the output_actions so that the changes are reflected in the DataStructure + return nx.IFilter.PreflightResult(output_actions=output_actions) + +The finished `preflight_impl` method should look like this: + +.. code:: python + + def preflight_impl(self, data_structure: nx.DataStructure, args: dict, message_handler: nx.IFilter.MessageHandler, should_cancel: nx.AtomicBoolProxy) -> nx.IFilter.PreflightResult: + """This method preflights the filter and should ensure that all inputs are sanity checked as best as possible. Array + sizes can be checked if the array sizes are actually known at preflight time. Some filters will not be able to report output + array sizes during preflight (segmentation filters for example). If in doubt, set the tuple dimensions of an array to [1]. + :returns: + :rtype: nx.IFilter.PreflightResult + """ + + # Extract the values from the user interface from the 'args' dictionary + array_1_path: nx.DataPath = args[ArrayStatisticsFilter.ARRAY_1_PATH_KEY] # This gets the DataPath for Array 1 that the user chose + array_2_path: nx.DataPath = args[ArrayStatisticsFilter.ARRAY_2_PATH_KEY] # This gets the DataPath for Array 2 that the user chose + math_operation_choice_index: int = args[ArrayStatisticsFilter.MATH_OPERATION_KEY] # This gets the current index of the Element-wise Operation combo box that the user chose + output_array_path: nx.DataPath = args[ArrayStatisticsFilter.OUTPUT_ARRAY_KEY] # This gets the DataPath for the Output Array that the user chose + + # Validate that the number of tuples for both arrays is equal + array_1: nx.IDataArray = data_structure[array_1_path] # Retrieve the 'Array 1' DataArray object from the DataStructure + array_2: nx.IDataArray = data_structure[array_2_path] # Retrieve the 'Array 2' DataArray object from the DataStructure + array_1_num_tuples = np.prod(array_1.tdims) # Compute Array 1's total number of tuples by multiplying Array 1's tuple dimensions together + array_2_num_tuples = np.prod(array_2.tdims) # Compute Array 2's total number of tuples by multiplying Array 2's tuple dimensions together + if array_1_num_tuples != array_2_num_tuples: # Compare Array 1's and Array 2's total number of tuples, return an error if they are not equal + return nx.IFilter.PreflightResult(nx.OutputActions(), [nx.Error(-65020, f"Array 1's number of tuples ({array_1_num_tuples}) do not match Array 2's number of tuples ({array_2_num_tuples}).")]) + + # Create an OutputActions object to hold any DataStructure modifications that we are going to make + output_actions = nx.OutputActions() + + # Append a CreateArrayAction to the OutputActions object, this will create the output array in the DataStructure + output_actions.append_action(nx.CreateArrayAction(type=array_1.data_type, t_dims=array_1.tdims, c_dims=array_1.cdims, path=output_array_path)) + + # Return the output_actions so that the changes are reflected in the DataStructure + return nx.IFilter.PreflightResult(output_actions=output_actions) + +Sending Messages To The Console (optional) +****************************************** + +Sometimes during preflight, you may need to communicate a message to the console. To do this, you can use the `message_handler` object in the `preflight_impl` method. Here's an example of how to send a message to the console before creating the output array: + +.. code:: python + + message_handler(nx.IFilter.Message(nx.IFilter.Message.Type.Info, f'Creating output array at path {output_array_path}!')) + output_actions = nx.OutputActions() + output_actions.append_action(nx.CreateArrayAction(type=array_1.data_type, t_dims=array_1.tdims, c_dims=array_1.cdims, path=output_array_path)) + +In the code above, we are sending a message to the console to communicate that we are creating the output array at the given `output_array_path`. + +3.2.5 Implementing the Execute Method +------------------------------------- + +The `execute_impl` method is used to run the actual filter algorithm and report results. This method performs the main computation and modifications to the data structure, and provides feedback to the user interface. + +**NOTE**: The skeleton code for `execute_impl` will have example code in it, so please remove it so that the method looks like this: + +.. code:: python + + def execute_impl(self, data_structure: nx.DataStructure, args: dict, message_handler: nx.IFilter.MessageHandler, should_cancel: nx.AtomicBoolProxy) -> nx.IFilter.ExecuteResult: + """ This method actually executes the filter algorithm and reports results. + :returns: Execution result containing the status of the filter execution. + :rtype: nx.IFilter.ExecuteResult + """ + + # Write your execute code here + +Just like in the `preflight_impl` method, the `execute_impl` method has an `args` dictionary that contains all the parameter values that the user chose in the filter. So let's grab all the parameter values from the `args` dictionary the same way: + +.. code:: python + + # Extract the values from the user interface from the 'args' dictionary + array_1_path: nx.DataPath = args[ArrayStatisticsFilter.ARRAY_1_PATH_KEY] # This gets the DataPath for Array 1 that the user chose + array_2_path: nx.DataPath = args[ArrayStatisticsFilter.ARRAY_2_PATH_KEY] # This gets the DataPath for Array 2 that the user chose + math_operation_choice_index: int = args[ArrayStatisticsFilter.MATH_OPERATION_KEY] # This gets the current index of the Element-wise Operation combo box that the user chose + output_array_path: nx.DataPath = args[ArrayStatisticsFilter.OUTPUT_ARRAY_KEY] # This gets the DataPath for the Output Array that the user chose + +Also similar to the `preflight_impl` method, we need to grab the actual array objects from the DataStructure. This time, however, we are going to retrieve a numpy view of those arrays by using the `npview` method: + +.. code:: python + + array_1: np.array = data_structure[array_1_path].npview() # Retrieve the 'Array 1' DataArray object as a numpy view from the DataStructure + array_2: np.array = data_structure[array_2_path].npview() # Retrieve the 'Array 2' DataArray object as a numpy view from the DataStructure + output_array: np.array = data_structure[output_array_path].npview() # Retrieve the 'Output Array' DataArray object as a numpy view from the DataStructure + +If you do not use the `npview` method, you will end up retrieving the `simplnx` view of the arrays. We instead want numpy views of the arrays so that we can perform numpy operations on them. + +Next, we are going to perform the correct numpy operation, which is determined by the current index of the Element-wise Operation combo box: + +.. code:: python + + if math_operation_choice_index == 0: + # Add arrays element-wise + message_handler(nx.IFilter.Message(nx.IFilter.Message.Type.Info, f'Adding arrays element-wise...')) + output_array[:] = np.add(array_1, array_2) + elif math_operation_choice_index == 1: + # Subtract arrays element-wise + message_handler(nx.IFilter.Message(nx.IFilter.Message.Type.Info, f'Subtracting arrays element-wise...')) + output_array[:] = np.subtract(array_1, array_2) + elif math_operation_choice_index == 2: + # Multiply arrays element-wise + message_handler(nx.IFilter.Message(nx.IFilter.Message.Type.Info, f'Multiplying arrays element-wise...')) + output_array[:] = np.multiply(array_1, array_2) + else: # Divide arrays element-wise + # Check for division by zero + zero_indices = np.where(array_2 == 0)[0] + if zero_indices.size > 0: + return nx.Result([nx.Error(-2011, f"Division by zero detected at indices: {zero_indices}")]) + # Divide arrays element-wise + message_handler(nx.IFilter.Message(nx.IFilter.Message.Type.Info, f'Dividing arrays element-wise...')) + output_array[:] = np.divide(array_1, array_2) + +In the code above, we are calling the proper numpy operation (np.add, np.subtract, np.multiply, np.divide) based on the Element-wise Operation combo box index that the user chose. Note, just like in `preflight_impl`, we are able to send a message to the console describing which operation we are currently running. + +If the user chose to divide, we are also checking if there are any "divide by 0" errors before we execute np.divide and returning those indices as a filter error. If there are no "divide by 0" errors, then the filter will continue and divide the arrays element-wise like usual. + +Detecting Filter Cancellations (optional) +***************************************** + +If your filter has any long-running operations (say a giant loop that takes more than a few seconds to finish), you may want to detect if the user has pressed the pipeline's cancel button and exit the filter: + +.. code:: python + + if should_cancel: + return nx.Result() + +The code above checks the `should_cancel` variable that is part of the `execute_impl` parameters. If `should_cancel` is True, then the filter immediately exits. Although the filter we are writing right now does not need to check for cancel (these numpy methods run quickly), this is how you use it. + +Finally, return a Result object. The Result object can contain errors/warnings, but since we already handled errors above then we can just return an empty Result object to signify no errors: + +.. code:: python + + return nx.Result() + +The finished `execute_impl` method should look like this: + +.. code:: python + + def execute_impl(self, data_structure: nx.DataStructure, args: dict, message_handler: nx.IFilter.MessageHandler, should_cancel: nx.AtomicBoolProxy) -> nx.IFilter.ExecuteResult: + """ This method actually executes the filter algorithm and reports results. + :returns: + :rtype: nx.IFilter.ExecuteResult + """ + + # Extract the values from the user interface from the 'args' dictionary + array_1_path: nx.DataPath = args[ArrayCalculationFilter.ARRAY_1_PATH_KEY] # This gets the DataPath for Array 1 that the user chose + array_2_path: nx.DataPath = args[ArrayCalculationFilter.ARRAY_2_PATH_KEY] # This gets the DataPath for Array 2 that the user chose + math_operation_choice_index: int = args[ArrayCalculationFilter.MATH_OPERATION_KEY] # This gets the current index of the Element-wise Operation combo box that the user chose + output_array_path: nx.DataPath = args[ArrayCalculationFilter.OUTPUT_ARRAY_KEY] # This gets the DataPath for the Output Array that the user chose + + array_1: np.array = data_structure[array_1_path].npview() # Retrieve the 'Array 1' DataArray object as a numpy view from the DataStructure + array_2: np.array = data_structure[array_2_path].npview() # Retrieve the 'Array 2' DataArray object as a numpy view from the DataStructure + output_array: np.array = data_structure[output_array_path].npview() # Retrieve the 'Output Array' DataArray object as a numpy view from the DataStructure + + if math_operation_choice_index == 0: + # Add arrays element-wise + message_handler(nx.IFilter.Message(nx.IFilter.Message.Type.Info, f'Adding arrays element-wise...')) + output_array[:] = np.add(array_1, array_2) + elif math_operation_choice_index == 1: + # Subtract arrays element-wise + message_handler(nx.IFilter.Message(nx.IFilter.Message.Type.Info, f'Subtracting arrays element-wise...')) + output_array[:] = np.subtract(array_1, array_2) + elif math_operation_choice_index == 2: + # Multiply arrays element-wise + message_handler(nx.IFilter.Message(nx.IFilter.Message.Type.Info, f'Multiplying arrays element-wise...')) + output_array[:] = np.multiply(array_1, array_2) + else: # Divide arrays element-wise + # Check for division by zero + zero_indices = np.where(array_2 == 0)[0] + if zero_indices.size > 0: + return nx.Result([nx.Error(-2011, f"Division by zero detected at indices: {zero_indices}")]) + # Divide arrays element-wise + message_handler(nx.IFilter.Message(nx.IFilter.Message.Type.Info, f'Dividing arrays element-wise...')) + output_array[:] = np.divide(array_1, array_2) + + return nx.Result() + +3.2.6 Full Example: ArrayCalculationFilter.py +--------------------------------------------- + +.. code:: python + + from typing import List + import simplnx as nx + import numpy as np + + class ArrayCalculationFilter: + + # ----------------------------------------------------------------------------- + # These methods should not be edited + # ----------------------------------------------------------------------------- + def uuid(self) -> nx.Uuid: + """This returns the UUID of the filter. Each filter has a unique UUID value + :return: The Filter's Uuid value + :rtype: string + """ + return nx.Uuid('f5b5281d-48bd-4081-a29c-766bb9fb4e7a') + + def class_name(self) -> str: + """The returns the name of the class that implements the filter + :return: The name of the implementation class + :rtype: string + """ + return 'ArrayCalculationFilter' + + def name(self) -> str: + """The returns the name of filter + :return: The name of the filter + :rtype: string + """ + return 'ArrayCalculationFilter' + + def clone(self): + """Clones the filter + :return: A new instance of the filter + :rtype: ArrayCalculationFilter + """ + return ArrayCalculationFilter() + + # ----------------------------------------------------------------------------- + # These methods CAN (and probably should) be updated. For instance, the + # human_name() is what users of the filter will see in the DREAM3D-NX GUI. You + # might want to consider putting spaces between workd, using proper capitalization + # and putting "(Python)" at the end of the name (or beginning if you want the + # filter list to group your filters togther) + # ----------------------------------------------------------------------------- + def human_name(self) -> str: + """This returns the name of the filter as a user of DREAM3DNX would see it + :return: The filter's human name + :rtype: string + """ + return 'Calculate Element-wise Array Result (Python)' + + def default_tags(self) -> List[str]: + """This returns the default tags for this filter + :return: The default tags for the filter + :rtype: list + """ + return ['python', 'ArrayCalculationFilter', 'compute'] + + + """ + This section should contain the 'keys' that store each parameter. The value of the key should be snake_case. The name + of the value should be ALL_CAPITOL_KEY + """ + ARRAY_1_PATH_KEY = 'array_1_path' + ARRAY_2_PATH_KEY = 'array_2_path' + MATH_OPERATION_KEY = 'math_operation' + OUTPUT_ARRAY_KEY = 'output_array' + + def parameters(self) -> nx.Parameters: + """This function defines the parameters that are needed by the filter. Parameters collect the values from the user interface + and pack them up into a dictionary for use in the preflight and execute methods. + """ + params = nx.Parameters() + + params.insert(params.Separator("Input Parameters")) + params.insert(nx.ArraySelectionParameter(name=ArrayCalculationFilter.ARRAY_1_PATH_KEY, human_name='Array 1', help_text='The 1st array that will be used in the statistics calculations', default_value=nx.DataPath(), allowed_types={nx.DataType.float32, nx.DataType.float64}, required_comps=[[1]])) + params.insert(nx.ArraySelectionParameter(name=ArrayCalculationFilter.ARRAY_2_PATH_KEY, human_name='Array 2', help_text='The 2nd array that will be used in the statistics calculations', default_value=nx.DataPath(), allowed_types={nx.DataType.float32, nx.DataType.float64}, required_comps=[[1]])) + + params.insert(nx.ChoicesParameter(name=ArrayCalculationFilter.MATH_OPERATION_KEY, human_name='Element-wise Operation', help_text='The operation to perform element-wise on both arrays.', default_value=0, choices=['Add', 'Subtract', 'Multiply', 'Divide'])) + + params.insert(params.Separator("Output Parameters")) + params.insert(nx.ArrayCreationParameter(name=ArrayCalculationFilter.OUTPUT_ARRAY_KEY, human_name='Output Array', help_text='The output array that contains the calculation results.', default_value=nx.DataPath())) + + return params + + def preflight_impl(self, data_structure: nx.DataStructure, args: dict, message_handler: nx.IFilter.MessageHandler, should_cancel: nx.AtomicBoolProxy) -> nx.IFilter.PreflightResult: + """This method preflights the filter and should ensure that all inputs are sanity checked as best as possible. Array + sizes can be checked if the array sizes are actually known at preflight time. Some filters will not be able to report output + array sizes during preflight (segmentation filters for example). If in doubt, set the tuple dimensions of an array to [1]. + :returns: + :rtype: nx.IFilter.PreflightResult + """ + + # Extract the values from the user interface from the 'args' dictionary + array_1_path: nx.DataPath = args[ArrayCalculationFilter.ARRAY_1_PATH_KEY] # This gets the DataPath for Array 1 that the user chose + array_2_path: nx.DataPath = args[ArrayCalculationFilter.ARRAY_2_PATH_KEY] # This gets the DataPath for Array 2 that the user chose + math_operation_choice_index: int = args[ArrayCalculationFilter.MATH_OPERATION_KEY] # This gets the current index of the Element-wise Operation combo box that the user chose + output_array_path: nx.DataPath = args[ArrayCalculationFilter.OUTPUT_ARRAY_KEY] # This gets the DataPath for the Output Array that the user chose + + # Validate that the number of tuples for both arrays is equal + array_1: nx.IDataArray = data_structure[array_1_path] # Retrieve the 'Array 1' DataArray object from the DataStructure + array_2: nx.IDataArray = data_structure[array_2_path] # Retrieve the 'Array 2' DataArray object from the DataStructure + array_1_num_tuples = np.prod(array_1.tdims) # Compute Array 1's total number of tuples by multiplying Array 1's tuple dimensions together + array_2_num_tuples = np.prod(array_2.tdims) # Compute Array 2's total number of tuples by multiplying Array 2's tuple dimensions together + if array_1_num_tuples != array_2_num_tuples: # Compare Array 1's and Array 2's total number of tuples, return an error if they are not equal + return nx.IFilter.PreflightResult(nx.OutputActions(), [nx.Error(-2010, f"Array 1's number of tuples ({array_1_num_tuples}) do not match Array 2's number of tuples ({array_2_num_tuples}).")]) + + # Create an OutputActions object to hold the CreateArrayAction that will create the output array + output_actions = nx.OutputActions() + message_handler(nx.IFilter.Message(nx.IFilter.Message.Type.Info, f'Creating output array at path {output_array_path}!')) + output_actions.append_action(nx.CreateArrayAction(type=array_1.data_type, t_dims=array_1.tdims, c_dims=array_1.cdims, path=output_array_path)) + + # Return the output_actions so that the changes are reflected in the DataStructure + return nx.IFilter.PreflightResult(output_actions=output_actions) + + def execute_impl(self, data_structure: nx.DataStructure, args: dict, message_handler: nx.IFilter.MessageHandler, should_cancel: nx.AtomicBoolProxy) -> nx.IFilter.ExecuteResult: + """ This method actually executes the filter algorithm and reports results. + :returns: + :rtype: nx.IFilter.ExecuteResult + """ + + # Extract the values from the user interface from the 'args' dictionary + array_1_path: nx.DataPath = args[ArrayCalculationFilter.ARRAY_1_PATH_KEY] # This gets the DataPath for Array 1 that the user chose + array_2_path: nx.DataPath = args[ArrayCalculationFilter.ARRAY_2_PATH_KEY] # This gets the DataPath for Array 2 that the user chose + math_operation_choice_index: int = args[ArrayCalculationFilter.MATH_OPERATION_KEY] # This gets the current index of the Element-wise Operation combo box that the user chose + output_array_path: nx.DataPath = args[ArrayCalculationFilter.OUTPUT_ARRAY_KEY] # This gets the DataPath for the Output Array that the user chose + + array_1: np.array = data_structure[array_1_path].npview() # Retrieve the 'Array 1' DataArray object as a numpy view from the DataStructure + array_2: np.array = data_structure[array_2_path].npview() # Retrieve the 'Array 2' DataArray object as a numpy view from the DataStructure + output_array: np.array = data_structure[output_array_path].npview() # Retrieve the 'Output Array' DataArray object as a numpy view from the DataStructure + + if math_operation_choice_index == 0: + # Add arrays element-wise + message_handler(nx.IFilter.Message(nx.IFilter.Message.Type.Info, f'Adding arrays element-wise...')) + output_array[:] = np.add(array_1, array_2) + elif math_operation_choice_index == 1: + # Subtract arrays element-wise + message_handler(nx.IFilter.Message(nx.IFilter.Message.Type.Info, f'Subtracting arrays element-wise...')) + output_array[:] = np.subtract(array_1, array_2) + elif math_operation_choice_index == 2: + # Multiply arrays element-wise + message_handler(nx.IFilter.Message(nx.IFilter.Message.Type.Info, f'Multiplying arrays element-wise...')) + output_array[:] = np.multiply(array_1, array_2) + else: # Divide arrays element-wise + # Check for division by zero + zero_indices = np.where(array_2 == 0)[0] + if zero_indices.size > 0: + return nx.Result([nx.Error(-2011, f"Division by zero detected at indices: {zero_indices}")]) + # Divide arrays element-wise + message_handler(nx.IFilter.Message(nx.IFilter.Message.Type.Info, f'Dividing arrays element-wise...')) + output_array[:] = np.divide(array_1, array_2) + + return nx.Result() \ No newline at end of file diff --git a/wrapping/python/docs/source/User_API.rst b/wrapping/python/docs/source/User_API.rst index cf5e7b9810..0d077fbdff 100644 --- a/wrapping/python/docs/source/User_API.rst +++ b/wrapping/python/docs/source/User_API.rst @@ -1,8 +1,8 @@ -.. _UserAPIDocs: - SIMPLNX Python API =================== +.. _UserAPIDocs: + Error & Warning Reporting -------------------------- @@ -11,7 +11,7 @@ Error & Warning Reporting .. _Result: -.. py:class:: Result +.. py:class:: IFilter.ExecuteResult The object that encapsulates any warnings or errors from either preflighting or executing a simplnx.Filter object. It can be queried for the list of errors or warnings and thus printed if needed. @@ -23,11 +23,17 @@ Error & Warning Reporting input_type=0, output_orientation_array_name='Quaternions', output_type=2) - if len(result.errors) != 0: - print('Errors: {}', result.errors) - print('Warnings: {}', result.warnings) + if len(result.warnings) != 0: + for w in result.warnings: + print(f'Warning: ({w.code}) {w.message}') + + has_errors = len(result.errors) != 0 + if has_errors: + for err in result.errors: + print(f'Error: ({err.code}) {err.message}') + raise RuntimeError(result) else: - print("No errors running the ConvertOrientations") + print(f"{filter.name()} :: No errors running the filter") Creating Geometries ------------------ @@ -273,9 +279,8 @@ General Parameters .. _ArrayThresholdsParameter: .. py:class:: ArrayThresholdsParameter - This parameter holds a ArrayThresholdSet_ object and is used specifically for the :ref:`simplnx.MultiThresholdObjects() ` filter. + This parameter holds a ArrayThresholdSet_ object and is used specifically for the :ref:`simplnx.MultiThresholdObjectsFilter() ` filter. This parameter should not be directly invoked but instead it's ArrayThresholdSet_ is invoked and used. - .. _ArrayThresholdSet: .. py:class:: ArrayThresholdSet @@ -287,7 +292,7 @@ General Parameters .. _ArrayThreshold: .. py:class:: ArrayThresholdSet.ArrayThreshold - This class holds the values that are used for comparison in the :ref:`simplnx.MultiThresholdObjects() ` filter. + This class holds the values that are used for comparison in the :ref:`simplnx.MultiThresholdObjectsFilter() ` filter. :ivar array_path: The :ref:`DataPath ` to the array to use for this ArrayThreshold :ivar comparison: Int. The comparison operator to use. 0=">", 1="<", 2="=", 3="!=" @@ -312,7 +317,7 @@ General Parameters threshold_set = nx.ArrayThresholdSet() threshold_set.thresholds = [threshold_1, threshold_2] threshold_set.union_op = nx.IArrayThreshold.UnionOperator.And - result = nx.MultiThresholdObjects.execute(data_structure=data_structure, + result = nx.MultiThresholdObjectsFilter.execute(data_structure=data_structure, array_thresholds=threshold_set, created_data_path="Mask", created_mask_type=nx.DataType.boolean)