Repository: nipy/nipype

Issue:
CAT12 interface?
Would it be possible to add CAT12 toolbox for SPM12 support? Currently, it seems only VBM8 is supported?


Discussion:
Dear all, I'd also need the CAT12 module - has this already been started somewhere - if not could someone please guide me on how to do this?
Hi @barbrakr, I think the [Create interfaces](https://miykael.github.io/nipype_tutorial/notebooks/advanced_create_interfaces.html) tutorial is a good place to start. I would suggest writing your interface independently, *i.e.* in a new file:

```Python
from nipype.interfaces.base import TraitedSpec
from nipype.interfaces.spm.base import SPMCommandInputSpec, SPMCommand

class Cat12InputSpec(SPMCommandInputSpec):
    ...

class Cat12OutputSpec(TraitedSpec):
    ...

class Cat12(SPMCommand):
    ...

# Simple test
if __name__ == '__main__':
    cat12 = Cat12()
    # Add some inputs that you know should work, here.
    cat12.inputs.in_file = ...
    ...
    cat12.run()
```

Then you can easily test it just by running `python cat12.py`.

I would suggest browsing the [nipype.interfaces.spm](https://github.com/nipy/nipype/tree/master/nipype/interfaces/spm) directory to get a sense of how other commands are wrapped. In fact, it might be easiest to find an interface that is very similar to what you want to write, copying it and swapping out names that are specific to the old command for analogous names with the new command.

When you're ready for feedback, choose a module in `nipype.interfaces.spm` to add it to, and [open a pull request](https://github.com/nipy/nipype/blob/master/CONTRIBUTING.md). Sometimes it's easiest to ask questions with some code for context, so feel free to do this at whatever stage of readiness.
GitHub is really awesome - thank you so much @effigies for your detailed expert description - I'll get crunching on this!
Hey @barbrakr and @lrq3000, just wanted to ask if you have already started on writing an interface for CAT12? I just had a Q&A with Peer Herholz on [Neurostars, which might be interesting for you?](https://neurostars.org/t/finding-cat12-produced-suddirectories-in-bids-conform-dataset/16944)
Hi @JohannesWiesner - no, sorry I actually never got round to writing this - although I am sure that people within the community would find it quite useful!
Idem for me unfortunately.

Le lun. 5 oct. 2020 à 18:01, barbrakr <notifications@github.com> a écrit :

> Hi @JohannesWiesner <https://github.com/JohannesWiesner> - no, sorry I
> actually never got round to writing this - although I am sure that people
> within the community would find it quite useful!
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/nipy/nipype/issues/2783#issuecomment-703726859>, or
> unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AAIRFXWTSSL52XFHV56EGQDSJHUUTANCNFSM4GE42RFA>
> .
>

As discussed with @lrq3000 and @barbrakr, I provide two links to repositories that I created:

https://github.com/JohannesWiesner/nisupply
https://github.com/JohannesWiesner/pycat

`nisupply` and `pycat` (which builds up on `nisupply` ) do not offer an interface to CAT12 itself but a least make it a little bit more easy to handle files that were produced by CAT12. The main motivation behind `nisupply` was, that CAT12 and probably also other current tools produce files and directory structures that do not conform to BIDS, which again does not allow to use repos that need the files to be BIDS-conform in order to work (like `nipype`). Pycat currently allows obtaining information from the .xml files (such as obtaining the TIV for each participant). It also contains a function called `check_sample_homogeneity` which is supposed to imitate CAT12’s check_sample module but currently does not provided the exact logic that CAT12 has (The plan would be to exactly imitate what CAT12 is doing and to use plotly/dash to imitate the interface from CAT12). 
Hi,

I already made my own CAT12 Nipype Interface following the @effigies suggestions.
I will try to make a PR as soon as possible. 


Resolving patch:
diff --git a/nipype/interfaces/cat12/__init__.py b/nipype/interfaces/cat12/__init__.py
--- a/nipype/interfaces/cat12/__init__.py
+++ b/nipype/interfaces/cat12/__init__.py
@@ -1,4 +1,4 @@
-from .preprocess import CAT12Segment
+from .preprocess import CAT12Segment, CAT12SANLMDenoising
 from .surface import (
     ExtractAdditionalSurfaceParameters,
     ExtractROIBasedSurfaceMeasures,
diff --git a/nipype/interfaces/cat12/preprocess.py b/nipype/interfaces/cat12/preprocess.py
--- a/nipype/interfaces/cat12/preprocess.py
+++ b/nipype/interfaces/cat12/preprocess.py
@@ -593,6 +593,155 @@ def _list_outputs(self):
         return outputs
 
 
+class CAT12SANLMDenoisingInputSpec(SPMCommandInputSpec):
+
+    in_files = InputMultiPath(
+        ImageFileSPM(exists=True),
+        field="data",
+        desc="Images for filtering.",
+        mandatory=True,
+        copyfile=False,
+    )
+
+    spm_type = traits.Enum(
+        "float32",
+        "uint16",
+        "uint8",
+        "same",
+        field="spm_type",
+        usedefault=True,
+        desc="Data type of the output images. 'same' matches the input image type.",
+    )
+
+    intlim = traits.Int(
+        field="intlim",
+        default_value=100,
+        usedefault=True,
+        desc="intensity limitation (default = 100)",
+    )
+
+    filename_prefix = traits.Str(
+        field="prefix",
+        default_value="sanlm_",
+        usedefault=True,
+        desc="Filename prefix. Specify  the  string  to be prepended to the filenames of the filtered image file(s).",
+    )
+
+    filename_suffix = traits.Str(
+        field="suffix",
+        default_value="",
+        usedefault=True,
+        desc="Filename suffix. Specify  the  string  to  be  appended  to the filenames of the filtered image file(s).",
+    )
+
+    addnoise = traits.Float(
+        default_value=0.5,
+        usedefault=True,
+        field="addnoise",
+        desc="""Strength of additional noise in noise-free regions.
+        Add  minimal  amount  of noise in regions without any noise to avoid image segmentation problems.
+        This parameter defines the strength of additional noise as percentage of the average signal intensity.""",
+    )
+
+    rician = traits.Bool(
+        True,
+        field="rician",
+        usedefault=True,
+        desc="""Rician noise
+        MRIs  can  have  Gaussian  or  Rician  distributed  noise with uniform or nonuniform variance across the image.
+        If SNR is high enough (>3)  noise  can  be  well  approximated by Gaussian noise in the foreground. However, for
+        SENSE reconstruction or DTI data a Rician distribution is expected. Please note that the Rician noise estimation
+        is sensitive for large signals in the neighbourhood and can lead to artefacts, e.g. cortex can be affected by
+        very high values in the scalp or in blood vessels.""",
+    )
+
+    replace_nan_and_inf = traits.Bool(
+        True,
+        field="replaceNANandINF",
+        usedefault=True,
+        desc="Replace NAN by 0, -INF by the minimum and INF by the maximum of the image.",
+    )
+
+    noisecorr_strength = traits.Enum(
+        "-Inf",
+        2,
+        4,
+        field="nlmfilter.optimized.NCstr",
+        usedefault=True,
+        desc="""Strength of Noise Corrections
+        Strength  of  the  (sub-resolution)  spatial  adaptive    non local means (SANLM) noise correction. Please note
+        that the filter strength is automatically  estimated.  Change this parameter only for specific conditions. The
+        "light" option applies half of the filter strength of the adaptive  "medium"  cases,  whereas  the  "strong"
+        option  uses  the  full  filter  strength,  force sub-resolution filtering and applies an additional  iteration.
+        Sub-resolution  filtering  is  only  used  in  case  of  high image resolution below 0.8 mm or in case of the
+        "strong" option. light = 2, medium = -Inf, strong = 4""",
+    )
+
+
+class CAT12SANLMDenoisingOutputSpec(TraitedSpec):
+
+    out_file = File(desc="out file")
+
+
+class CAT12SANLMDenoising(SPMCommand):
+    """
+    Spatially adaptive non-local means (SANLM) denoising filter
+
+    This  function  applies  an spatial adaptive (sub-resolution) non-local means denoising filter
+    to  the  data.  This  filter  will  remove  noise  while  preserving  edges. The filter strength is
+    automatically estimated based on the standard deviation of the noise.
+
+    This   filter   is  internally  used  in  the  segmentation  procedure  anyway.  Thus,  it  is  not
+    necessary (and not recommended) to apply the filter before segmentation.
+    ______________________________________________________________________
+    Christian Gaser, Robert Dahnke
+    Structural Brain Mapping Group (http://www.neuro.uni-jena.de)
+    Departments of Neurology and Psychiatry
+    Jena University Hospital
+    ______________________________________________________________________
+
+    Examples
+    --------
+    >>> from nipype.interfaces import cat12
+    >>> c = cat12.CAT12SANLMDenoising()
+    >>> c.inputs.in_files = 'anatomical.nii'
+    >>> c.run() # doctest: +SKIP
+    """
+
+    input_spec = CAT12SANLMDenoisingInputSpec
+    output_spec = CAT12SANLMDenoisingOutputSpec
+
+    def __init__(self, **inputs):
+        _local_version = SPMCommand().version
+        if _local_version and "12." in _local_version:
+            self._jobtype = "tools"
+            self._jobname = "cat.tools.sanlm"
+
+        SPMCommand.__init__(self, **inputs)
+
+    def _format_arg(self, opt, spec, val):
+        """Convert input to appropriate format for spm"""
+        if opt == "in_files":
+            if isinstance(val, list):
+                return scans_for_fnames(val)
+            else:
+                return scans_for_fname(val)
+        if opt == "spm_type":
+            type_map = {"same": 0, "uint8": 2, "uint16": 512, "float32": 16}
+            val = type_map[val]
+        return super(CAT12SANLMDenoising, self)._format_arg(opt, spec, val)
+
+    def _list_outputs(self):
+        outputs = self._outputs().get()
+        outputs["out_file"] = fname_presuffix(
+            self.inputs.in_files[0],
+            newpath=os.getcwd(),
+            prefix=self.inputs.filename_prefix,
+            suffix=self.inputs.filename_suffix,
+        )
+        return outputs
+
+
 class Cell2Str(Cell):
     def __str__(self):
         """Convert input to appropriate format for cat12"""


Test patch:
diff --git a/nipype/interfaces/cat12/tests/test_auto_CAT12SANLMDenoising.py b/nipype/interfaces/cat12/tests/test_auto_CAT12SANLMDenoising.py
new file mode 100644
--- /dev/null
+++ b/nipype/interfaces/cat12/tests/test_auto_CAT12SANLMDenoising.py
@@ -0,0 +1,72 @@
+# AUTO-GENERATED by tools/checkspecs.py - DO NOT EDIT
+from ..preprocess import CAT12SANLMDenoising
+
+
+def test_CAT12SANLMDenoising_inputs():
+    input_map = dict(
+        addnoise=dict(
+            field="addnoise",
+            usedefault=True,
+        ),
+        filename_prefix=dict(
+            field="prefix",
+            usedefault=True,
+        ),
+        filename_suffix=dict(
+            field="suffix",
+            usedefault=True,
+        ),
+        in_files=dict(
+            copyfile=False,
+            field="data",
+            mandatory=True,
+        ),
+        intlim=dict(
+            field="intlim",
+            usedefault=True,
+        ),
+        matlab_cmd=dict(),
+        mfile=dict(
+            usedefault=True,
+        ),
+        noisecorr_strength=dict(
+            field="nlmfilter.optimized.NCstr",
+            usedefault=True,
+        ),
+        paths=dict(),
+        replace_nan_and_inf=dict(
+            field="replaceNANandINF",
+            usedefault=True,
+        ),
+        rician=dict(
+            field="rician",
+            usedefault=True,
+        ),
+        spm_type=dict(
+            field="spm_type",
+            usedefault=True,
+        ),
+        use_mcr=dict(),
+        use_v8struct=dict(
+            min_ver="8",
+            usedefault=True,
+        ),
+    )
+    inputs = CAT12SANLMDenoising.input_spec()
+
+    for key, metadata in list(input_map.items()):
+        for metakey, value in list(metadata.items()):
+            assert getattr(inputs.traits()[key], metakey) == value
+
+
+def test_CAT12SANLMDenoising_outputs():
+    output_map = dict(
+        out_file=dict(
+            extensions=None,
+        ),
+    )
+    outputs = CAT12SANLMDenoising.output_spec()
+
+    for key, metadata in list(output_map.items()):
+        for metakey, value in list(metadata.items()):
+            assert getattr(outputs.traits()[key], metakey) == value