Writing a DREAM.3D Filter using Python Language¶
If you are just wanting to use DREAM3D from a python script or write new filters using python language then this is the best solution for you.
Requirements¶
The prebuilt python is currently in testing and only supports Windows environments. + Windows Python Environment + Python 3.8/3.9 + Ability to install from a custom URL source.
Installation¶
(base) C:\Users\johnsmith> conda config --add channels conda-forge
(base) C:\Users\johnsmith> conda config --set channel_priority strict
(base) C:\Users\johnsmith> conda create -n d3d_embed python=3.8
(base) C:\Users\johnsmith> conda activate d3d_embed
(d3d_embed) C:\Users\johnsmith>
(d3d_embed) C:\Users\johnsmith>conda install -c http://dream3d.bluequartz.net/binaries/conda dream3d-conda
In order for the DREAM.3D UI Application to pick up your filters you will need to tell DREAM3D where to look for your filters. This is done by setting the SIMPL_PYTHONPATH environment variable. In this example we have created a new folder called PyD3D and then we are going to set the SIMPL_PYTHONPATH environment variable to point to this folder. Note that the SIMPL_PYTHONPATH can have multiple folders listed. Each folder should be delimited by a ';' (Windows) or a ':' (Unix/Linux/macOS)
(d3d_embed) C:\Users\johnsmith>mkdir PyD3D
(d3d_embed) C:\Users\johnsmith>set SIMPL_PYTHONPATH=C:\Users\johnsmith\PyD3D
Writing DREAM3D filters in Python¶
Inside SIMPL/Source/SIMPLib/Python
, there are two examples of filters written in Python, initialize_data.py
and ExampleFilter.py
.
Filters must derive from the abstract base class dream3d.Filter
and implement its methods similiary to C++ filters. Then inside the Python file, a global variable filters
must exist which should be a list of types where each type is a filter.
To use Python filters in DREAM3D or PipelineRunner, create an environment variable SIMPL_PYTHONPATH
by exporting in from the anaconda prompt like this:
(d3d_embed) C:\Users\johnsmith> set SIMPL_PYTHONPATH=C:\Users\johnsmith\DREAM3D-Dev\SIMPL\Source\SIMPLib\Python
If you have multiple directories that you want DREAM3D to search for python filters set the SIMPL_PYTHONPATH
to a list of directories. For Windows use a ;
to separate the directories. For Linux/Unix/macOS use the :
to separate each directory. If there are spaces anywhere in the path you MUST use double quotes around the path.
If using the DREAM3D GUI, Python filters can be reloaded at runtime. DREAM3D will clear all existing Python filters and attempt to reload them from directories in SIMPL_PYTHONPATH
. Currently loaded pipelines with Python filters will be preserved if the relevant filters still exist in those directories.
If you want to use a Python filter with simpl.FilterPipeline
, you must first wrap in a simpl.PythonFilter
.
Major Parts of a DREAM.3D Filter¶
Filter Parameters¶
This function defines the various inputs that your filter will need to run. These include external file/folder locations, various scalar or vector values, paths to specific data arrays and any other information that your filter will need to run. DREAM.3D has many types of FilterParameters to accomplish this task. Please take a look through the source codes to find a filter parameter that works for your needs.
def setup_parameters(self) -> List[FilterParameter]:
Preflight¶
DREAM.3D has the idea of a preflight function whose responsibilty is to sanity check all of the inputs to a filter and report errors or warnings if those inputs do not meet your specifications. For instance, if a filter requires an input file, the data_check() function would be the place to ensure that the file does exist. If if an integer falls within a specified bounds.
def data_check(self, dca: DataContainerArray, delegate: Union[FilterDelegateCpp, FilterDelegatePy] = FilterDelegatePy()) -> Tuple[int, str]:
Execute¶
The execute() function is where the actual filter algorithm will be implemented. You are free to have other functions as needed but this is the main entry point to the filter's algorithm. The function signature is:
def _execute_impl(self, dca: DataContainerArray, delegate: Union[FilterDelegateCpp, FilterDelegatePy] = FilterDelegatePy()) -> Tuple[int, str]:
Making Filters Unique¶
Each filter is assigned a UUID in code that uniquely identifies the filter to DREAM.3D. It is extremely important to generate a UUID and NOT simply copy/paste from another filter. A very quick way to generate a random UUID from python is to run the following code either in an interactive terminal or through a python file:
(pyd3d) C:\Users\johnsmith>python
>>> import uuid
>>> uuid.uuid4()
UUID('f8035905-9882-4423-a2ab-65bc37a77c7e')
Take the output from those commands and implement the "uuid()" function in your filter.
@staticmethod
def uuid() -> str:
return '{f8035905-9882-4423-a2ab-65bc37a77c7e}'
Misc Other Functions¶
A few of the other required functions should be filled in for your specific filter. These include: + 'name' + 'group_name' + 'sub_group_name' + 'human_label' where the 'human_label' is the most important as that is what will show up to the user in the DREAM.3D user interface.
Creating a Python Filter (An Example)¶
Use the file SIMPL/Source/SIMPLib/Python/Example.py as the base file. Copy the contents into a new python file.
Change the Class Name¶
Do a search and replace on ExampleFilter
and change it to the name of your filter.
Change the UUID¶
(pyd3d) C:\Users\johnsmith>python
>>> import uuid
>>> uuid.uuid4()
UUID('f8035905-9882-4423-a2ab-65bc37a77c7e')
copy the result and replace the UUID value in your source.
Names and Descriptions¶
Update the methods name
, group_name
, sub_group_name
, human_label
, version
and compiled_library_name
to return the proper strings for your filter.
Adding Parameters¶
This section is best done as an example. Let's say that we want our filter to take in a floating point value called Temperature
from the user. There are several pieces of code that we need to write.
- Create a variable in the
__init__
section, for instance:
self.temperature_param:float = 0.0
- Create a getter and setting method for the variable
def _set_temperature_param(self, value: float) -> None:
self.temperature_param = value`
def _get_temperature_param(self) -> float:
return self.temperature_param
- Decide on the type of SIMPL Filter Parameter that you will use to get the value from the user interface into the filter. For this example case we are going to use the
FloatFilterParameter
. There are many other classes of FilterParameters. (Insert list somewhere)
from dream3d.simpl import FloatFilterParameter
Lastly create an entry in the list of FilterParameters for the filter connecting the self.temperature_param
to the DREAM.3D application. If your filter will have more than one filter parameter then you will create a comma delimited list in this section.
def setup_parameters(self) -> List[FilterParameter]:
return [
FloatFilterParameter('Integer', 'temperature_param', self.temperature_param, FilterParameter.Category.Parameter,
self._set_temperature_param, self._get_temperature_param, -1)
]