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/index_template.rst b/wrapping/python/docs/index_template.rst index 34e0013c96..023b7de630 100644 --- a/wrapping/python/docs/index_template.rst +++ b/wrapping/python/docs/index_template.rst @@ -2,7 +2,7 @@ compiling additional plugins, and you want the python docs generated, you will need to add those to the list below -DREAM3D-NX Python Docs (v1.2.7) +DREAM3D-NX Python Docs (v1.3.0) ================================= Installation @@ -47,6 +47,7 @@ How to use SIMPLNX from Python Overview DataObjects Geometry + Reference_Frame_Notes .. toctree:: :maxdepth: 2 @@ -54,7 +55,7 @@ How to use SIMPLNX from Python Python_Introduction User_API - Reference_Frame_Notes + Tutorial_1 .. toctree:: :maxdepth: 1 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/Tutorial_1.rst b/wrapping/python/docs/source/Tutorial_1.rst new file mode 100644 index 0000000000..b33903bd56 --- /dev/null +++ b/wrapping/python/docs/source/Tutorial_1.rst @@ -0,0 +1,298 @@ +.. Tutorial 1: + +===================================== +Tutorial 1: Basic Python Integration +===================================== + +################################### +Anaconda Virtual Environment Setup +################################### + +.. code:: shell + + conda config --add channels conda-forge + conda config --set channel_priority strict + conda create -n nxpython python=3.12 + conda activate nxpython + conda install -c bluequartzsoftware dream3dnx + +################################### +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. + + +################################### +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 + + +################################### +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. + + +################################################ +First Steps: Create a Group in the DataStructure +################################################ + +As in the user interface of DREAM3D-NX, you as the developer can execute any of filter 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 an 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 + + +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 + + + +################################################ +Result Objects +################################################ + +Each time a filter is executed, it will return an `nx.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) + + 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) + + +################################################ +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:`CreateDataArray` filter. Adding into the current python source file... + +.. code:: python + + result = nx.CreateDataArray().execute(data_structure=data_structure, + component_count=1, + initialization_value_str="0", + numeric_type_index=nx.NumericType.float32, + output_array_path=nx.DataPath("Top Level Group/2D Array"), + tuple_dimensions=[[5,4]]) + nxutility.check_filter_result( nx.CreateDataArray(), 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. + +################################################ +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 + + +################################################ +Complete Source Code +################################################ + +.. code:: python + + import simplnx as nx + import numpy as np + import matplotlib.pyplot as plt + import nxutility + + # Create the DataStructure instance + data_structure = nx.DataStructure() + + result = nx.CreateDataGroup.execute(data_structure=data_structure, + data_object_path=nx.DataPath("Top Level Group")) + + # Loop to create a bunch of DataGroups. + 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) + + # Execute the filter + result = nx.CreateDataArray().execute(data_structure=data_structure, + component_count=1, + initialization_value_str="0", + numeric_type_index=nx.NumericType.float32, + output_array_path=nx.DataPath("Top Level Group/2D Array"), + tuple_dimensions=[[4,5]]) + nxutility.check_filter_result( nx.CreateDataArray(), result) + print(f'{data_structure.hierarchy_to_str()}') + + # Try to get the array from the DataStructure + try: + array_view = data_structure["Top Level Group/2D Array"].npview() + except AttributeError as attrerr: + print(f'{attrerr}') + quit(1) # This is pretty harsh! Maybe something more elegant to unwind from this error + + # 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}') + + # Show the result + plt.imshow(array_view) + plt.title("Random Data") + plt.axis('off') # to turn off axes + plt.show() +