7.5. Specifying Dependencies

This section describes how you can tackle different situations when writing your custom Marker and / or Preprocessor and take care of the dependencies for them.

You might have already come across listing out dependencies for your custom Markers and / or custom Preprocessors, if not, check them out first. If you have already gone through them, you are already familiar with using class attribute _DEPENDENCIES to keep track of its dependencies. junifer is a bit more sophisticated about them and we will see here how you can make the best use of them.

7.5.1. Handling dependencies that come as Python packages

You have already seen this case handled by having a class attribute _DEPENDENCIES whose value is a set of all the package names that the component depends on. For example, for RSSETSMarker, we have:

_DEPENDENCIES: ClassVar[Set[str]] = {"nilearn"}

The type annotation is for documentation and static type checking purposes. Although not required, we highly recommend you use them, your future self and others who use it will thank you.

7.5.2. Handling external dependencies from toolboxes

You can also specify dependencies of external toolboxes like AFIN, FSL and ANTs, by having a class attribute like so:

_EXT_DEPENDENCIES: ClassVar[List[Dict[str, Union[str, List[str]]]]] = [
    {
        "name": "afni",
        "commands": ["3dReHo", "3dAFNItoNIFTI"],
    },
]

The above example is taken from the class which computes regional homogeneity (ReHo) using AFNI. The general pattern is that you need to have the value of _EXT_DEPENDENCIES as a list of dictionary with two keys:

  • name (str) : lowercased name of the toolbox

  • commands (list of str) : actual names of the commands you need to use

This is simple but powerful as we will see in the following sub-sections.

7.5.3. Handling conditional dependencies

You might encounter situations where your Marker or Preprocessor needs to have option for the user to either use a dependency that comes as a package or use a dependency that relies on external toolboxes. With the foundation we laid above, it is really simple to solve it while having validation before running and letting the user know if some dependency is missing.

Let’s look at an actual implementation, in this case SpaceWarper, so that it shows the problem a bit better and how we solve it:

class SpaceWarper(BasePreprocessor):
    # docstring
    _CONDITIONAL_DEPENDENCIES: ClassVar[List[Dict[str, Union[str, Type]]]] = [
        {
            "using": "fsl",
            "depends_on": FSLWarper,
        },
        {
            "using": "ants",
            "depends_on": ANTsWarper,
        },
    ]

    def __init__(
        self, using: str, reference: str, on: Union[List[str], str]
    ) -> None:
        # validation and setting up
        ...

Here, you see a new class attribute _CONDITIONAL_DEPENDENCIES which is a list of dictionaries with two keys:

  • using (str) : lowercased name of the toolbox

  • depends_on (object) : a class which implements the particular tool’s use

It is mandatory to have the using positional argument in the constructor in this case as the validation starts with this and moves further. It is also mandatory to only allow the value of using argument to be one of them specified in the using key of _CONDITIONAL_DEPENDENCIES entries.

For brevity, we only show the FSLWarper here but ANTsWarper looks very similar. FSLWarper looks like this (only the relevant part is shown here):

class FSLWarper:
    # docstring

    _EXT_DEPENDENCIES: ClassVar[List[Dict[str, Union[str, List[str]]]]] = [
        {
            "name": "fsl",
            "commands": ["flirt", "applywarp"],
        },
    ]

    _DEPENDENCIES: ClassVar[Set[str]] = {"numpy", "nibabel"}

    def preprocess(
        self,
        input: Dict[str, Any],
        extra_input: Dict[str, Any],
    ) -> Dict[str, Any]:
        # implementation
        ...

Here you can see the familiar _DEPENDENCIES and _EXT_DEPENDENCIES class attributes. The validation process starts by looking up the using value of the _CONDITIONAL_DEPENDENCIES entries and then retrieves the object pointed by depends_on. After that, the _DEPENDENCIES and _EXT_DEPENDENCIES class attributes are checked.

This might be a bit too much to get it right away so feel free to check the code for a better understanding. You can also check ALFFBase for a Marker having this pattern.