Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Iterating over multiple pairs of PET and T1w files #13

Merged
merged 7 commits into from
Aug 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[flake8]
select = B,B9,C,D,DAR,E,F,N,RST,S,W
ignore = E203,E501,RST201,RST203,RST301,W503
max-line-length = 80
max-complexity = 10
docstring-convention = google
per-file-ignores = tests/*:S101
rst-roles = class,const,func,meth,mod,ref
rst-directives = deprecated
30 changes: 30 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: check-yaml
- id: check-toml
- id: check-added-large-files
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: local
hooks:
- id: black
name: black
entry: black
language: system
types: [python]
require_serial: true
- id: flake8
name: flake8
entry: flake8
language: system
types: [python]
require_serial: true
- id: isort
name: isort
entry: isort
require_serial: true
language: system
types_or: [cython, pyi, python]
args: ["--filter-files"]
40 changes: 20 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
# petdeface
A nipype implementation of an anatomical MR and PET defacing pipeline for BIDS datasets. This is a working prototype,
in active development denoted by the 0.x.x version number. However, it is functional and can be used to deface PET and
MR data as well as co-register the two modalities. Use is encouraged and feedback via Github issues or email to
[email protected] is more than welcome. As is often the case, this medical research software is constrained
MR data as well as co-register the two modalities. Use is encouraged and feedback via Github issues or email to
[email protected] is more than welcome. As is often the case, this medical research software is constrained
to testing on data that its developers have access to.

This software can be installed via source or via pip from PyPi with `pip install petdeface`
This software can be installed via source or via pip from PyPi with `pip install petdeface`

## Requirements

### Non-Python Dependencies

- FreeSurfer and MiDeFAce >= 7.3.2
- https://surfer.nmr.mgh.harvard.edu/
- https://surfer.nmr.mgh.harvard.edu/
- https://surfer.nmr.mgh.harvard.edu/fswiki/MiDeFace

### Python Dependencies
Expand All @@ -25,9 +25,9 @@ This software can be installed via source or via pip from PyPi with `pip install

## Usage
```bash
usage: petdeface.py [-h] [--output_dir OUTPUT_DIR] [--anat_only]
[--subject SUBJECT] [--session SESSION] [--docker]
[--n_procs N_PROCS] [--skip_bids_validator] [--version]
usage: petdeface.py [-h] [--output_dir OUTPUT_DIR] [--anat_only]
[--subject SUBJECT] [--session SESSION] [--docker]
[--n_procs N_PROCS] [--skip_bids_validator] [--version]
[--placement PLACEMENT] [--remove_existing] input_dir

PetDeface
Expand All @@ -49,9 +49,9 @@ options:
--skip_bids_validator
--version, -v show program's version number and exit
--placement PLACEMENT, -p PLACEMENT
Where to place the defaced images. Options are
Where to place the defaced images. Options are
'adjacent': next to the input_dir (default) in a folder appended with _defaced
'inplace': defaces the dataset in place, e.g. replaces faced PET and T1w images
'inplace': defaces the dataset in place, e.g. replaces faced PET and T1w images
w/ defaced at input_dir
'derivatives': does all of the defacing within the derivatives folder in input_dir.
--remove_existing, -r Remove existing output files in output_dir.
Expand All @@ -76,24 +76,24 @@ pip install petdeface-<X.X.X>-py3-none-any.whl # where X.X.X is the version numb

## Citations

1. Dale A, Fischl B, Sereno MI. Cortical Surface-Based Analysis: I. Segmentation and Surface Reconstruction.
1. Dale A, Fischl B, Sereno MI. Cortical Surface-Based Analysis: I. Segmentation and Surface Reconstruction.
Neuroimage. 1999;9(2):179–94. doi:10.1006/nimg.1998.0395.
2. Fischl B. FreeSurfer. Neuroimage. 2012 Aug 15;62(2):774-81. doi: 10.1016/j.neuroimage.2012.01.021.
Epub 2012 Jan 10. PMID: 22248573; PMCID: PMC3685476.
3. Stefano Cerri, Douglas N. Greve, Andrew Hoopes, Henrik Lundell, Hartwig R. Siebner, Mark Mühlau, Koen Van Leemput,
An open-source tool for longitudinal whole-brain and white matter lesion segmentation,
NeuroImage: Clinical, Volume 38, 2023, 103354, ISSN 2213-1582, https://doi.org/10.1016/j.nicl.2023.103354.
(https://www.sciencedirect.com/science/article/pii/S2213158223000438)
4. Gorgolewski, Krzysztof J. ; Esteban, Oscar ; Burns, Christopher ; Ziegler, Erik ; Pinsard, Basile ; Madison, Cindee ;
Waskom, Michael ; Ellis, David Gage ; Clark, Dav ; Dayan, Michael ; Manhães-Savio, Alexandre ;
Notter, Michael Philipp ; Johnson, Hans ; Dewey, Blake E ; Halchenko, Yaroslav O. ; Hamalainen, Carlo ;
4. Gorgolewski, Krzysztof J. ; Esteban, Oscar ; Burns, Christopher ; Ziegler, Erik ; Pinsard, Basile ; Madison, Cindee ;
Waskom, Michael ; Ellis, David Gage ; Clark, Dav ; Dayan, Michael ; Manhães-Savio, Alexandre ;
Notter, Michael Philipp ; Johnson, Hans ; Dewey, Blake E ; Halchenko, Yaroslav O. ; Hamalainen, Carlo ;
Keshavan, Anisha ; Clark, Daniel ; Huntenburg, Julia M. ; Hanke, Michael ; Nichols, B. Nolan ; Wassermann , Demian ;
Eshaghi, Arman ; Markiewicz, Christopher ; Varoquaux, Gael ; Acland, Benjamin ; Forbes, Jessica ; Rokem, Ariel ;
Kong, Xiang-Zhen ; Gramfort, Alexandre ; Kleesiek, Jens ; Schaefer, Alexander ; Sikka, Sharad ;
Perez-Guevara, Martin Felipe ; Glatard, Tristan ; Iqbal, Shariq ; Liu, Siqi ; Welch, David ; Sharp, Paul ;
Eshaghi, Arman ; Markiewicz, Christopher ; Varoquaux, Gael ; Acland, Benjamin ; Forbes, Jessica ; Rokem, Ariel ;
Kong, Xiang-Zhen ; Gramfort, Alexandre ; Kleesiek, Jens ; Schaefer, Alexander ; Sikka, Sharad ;
Perez-Guevara, Martin Felipe ; Glatard, Tristan ; Iqbal, Shariq ; Liu, Siqi ; Welch, David ; Sharp, Paul ;
Warner, Joshua ; Kastman, Erik ; Lampe, Leonie ; Perkins, L. Nathan ; Craddock, R. Cameron ; Küttner, René ;
Bielievtsov, Dmytro ; Geisler, Daniel ; Gerhard, Stephan ; Liem, Franziskus ; Linkersdörfer, Janosch ;
Margulies, Daniel S. ; Andberg, Sami Kristian ; Stadler, Jörg ; Steele, Christopher John ; Broderick, William ;
Cooper, Gavin ; Floren, Andrew ; Huang, Lijie ; Gonzalez, Ivan ; McNamee, Daniel ; Papadopoulos Orfanos, Dimitri ;
Pellman, John ; Triplett, William ; Ghosh, Satrajit (2016). Nipype: a flexible, lightweight and extensible
Bielievtsov, Dmytro ; Geisler, Daniel ; Gerhard, Stephan ; Liem, Franziskus ; Linkersdörfer, Janosch ;
Margulies, Daniel S. ; Andberg, Sami Kristian ; Stadler, Jörg ; Steele, Christopher John ; Broderick, William ;
Cooper, Gavin ; Floren, Andrew ; Huang, Lijie ; Gonzalez, Ivan ; McNamee, Daniel ; Papadopoulos Orfanos, Dimitri ;
Pellman, John ; Triplett, William ; Ghosh, Satrajit (2016). Nipype: a flexible, lightweight and extensible
neuroimaging data processing framework in Python. 0.12.0-rc1. Zenodo. 10.5281/zenodo.50186
90 changes: 59 additions & 31 deletions petdeface/mideface.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,26 @@
import os
from bids import BIDSLayout
from nipype.interfaces.base import (
TraitedSpec,
CommandLineInputSpec,
CommandLine,
File,
Directory,
traits,
)
from nipype.interfaces.base import CommandLine
from nipype.interfaces.base import CommandLineInputSpec
from nipype.interfaces.base import Directory
from nipype.interfaces.base import File
from nipype.interfaces.base import TraitedSpec
from nipype.interfaces.base import traits


class MidefaceInputSpec(CommandLineInputSpec):
in_file = File(
desc="Volume to deface",
exists=True,
argstr="--i %s",
)
in_file = File(desc="Volume to deface", exists=True, argstr="--i %s")
out_file = File(
desc="Defaced input",
argstr="--o %s",
name_source=["in_file"],
name_source="in_file",
name_template="%s_defaced",
output_name="out_file",
keep_extension=True,
)
out_facemask = File(
desc="Facemask",
argstr="--facemask %s",
name_source="in_file",
name_template="%s_facemask",
keep_extension=True,
)
odir = Directory(
desc="Output directory (turns on PostHeadSurf)",
Expand Down Expand Up @@ -62,7 +58,8 @@ class MidefaceInputSpec(CommandLineInputSpec):
argstr="--xmask-synthseg %d",
)
fill_const = traits.Tuple(
traits.Float, traits.Float,
traits.Float,
traits.Float,
desc="Fill constants",
argstr="--fill-const %f %f",
)
Expand Down Expand Up @@ -137,28 +134,59 @@ class MidefaceInputSpec(CommandLineInputSpec):
desc="Set Xvfb display number for taking pics",
argstr="--display %d",
)
apply = traits.Str(
desc="Apply midface output to a second volume",
argstr="--apply %s",
)
check = traits.Tuple(
File, File,
File,
File,
desc="Check whether a volume has been defaced",
argstr="--check %s %s",
)


class MidefaceOutputSpec(TraitedSpec):
out_file = File(desc="Defaced input")
out_facemask = File(desc="Facemask")
out_file = File(desc="Defaced input", exists=True)
out_facemask = File(desc="Facemask", exists=True)


class Mideface(CommandLine):
_cmd = "mideface"
input_spec = MidefaceInputSpec
output_spec = MidefaceOutputSpec

def _list_outputs(self):
outputs = self.output_spec().get()
outputs["out_file"] = os.path.abspath(self.inputs.out_file)
outputs["out_facemask"] = os.path.abspath(self.inputs.out_facemask)
return outputs


class ApplyMidefaceInputSpec(CommandLineInputSpec):
in_file = File(
desc="Volume to deface",
exists=True,
position=1,
argstr="%s",
)
facemask = File(
desc="Facemask",
exists=True,
position=2,
argstr="%s",
)
lta_file = File(
desc="Registration matrix lta file",
exists=True,
position=3,
argstr="%s",
)
out_file = File(
desc="Defaced input",
position=4,
argstr="%s",
name_source="in_file",
name_template="%s_defaced",
keep_extension=True,
)


class ApplyMidefaceOutputSpec(TraitedSpec):
out_file = File(desc="Defaced input", exists=True)


class ApplyMideface(CommandLine):
_cmd = "mideface --apply"
input_spec = ApplyMidefaceInputSpec
output_spec = ApplyMidefaceOutputSpec
101 changes: 59 additions & 42 deletions petdeface/pet.py
Original file line number Diff line number Diff line change
@@ -1,42 +1,59 @@
def create_weighted_average_pet(pet_file, bids_dir):

import json
from niworkflows.interfaces.bids import ReadSidecarJSON
import nibabel as nib
import numpy as np
import os
from pathlib import Path

"""
Create a time-weighted average of dynamic PET data using mid-frames

Arguments
---------
pet_file: string
path to input dynamic PET volume
bids_dir: string
path to BIDS directory containing the PET file
"""

img = nib.load(pet_file)
data = img.get_fdata()

meta = ReadSidecarJSON(in_file = pet_file,
bids_dir = bids_dir,
bids_validate = False).run()

frames_start = np.array(meta.outputs.out_dict['FrameTimesStart'])
frames_duration = np.array(meta.outputs.out_dict['FrameDuration'])

frames = range(data.shape[-1])

new_pth = os.getcwd()

mid_frames = frames_start + frames_duration/2
wavg = np.trapz(data[..., frames], dx=np.diff(mid_frames[frames]), axis=3)/np.sum(mid_frames)

out_name = Path(pet_file.replace('_pet.', '_desc-wavg_pet.')).name
out_file = os.path.join(new_pth, out_name)
nib.save(nib.Nifti1Image(wavg, img.affine), out_file)

return out_file
import os

import nibabel as nib
import numpy as np
from nipype.interfaces.base import BaseInterface
from nipype.interfaces.base import BaseInterfaceInputSpec
from nipype.interfaces.base import File
from nipype.interfaces.base import TraitedSpec
from nipype.utils.filemanip import split_filename
from niworkflows.interfaces.bids import ReadSidecarJSON


class WeightedAverageInputSpec(BaseInterfaceInputSpec):
pet_file = File(exists=True, desc="Dynamic PET", mandatory=True)


class WeightedAverageOutputSpec(TraitedSpec):
out_file = File(exists=True, desc="Time-weighted average of dynamic PET")


class WeightedAverage(BaseInterface):
"""Create a time-weighted average of dynamic PET data using mid-frames."""

input_spec = WeightedAverageInputSpec
output_spec = WeightedAverageOutputSpec

def _run_interface(self, runtime):
pet_file = self.inputs.pet_file
bids_dir = os.path.dirname(pet_file)

img = nib.load(pet_file)
data = img.get_fdata()

meta = ReadSidecarJSON(
in_file=pet_file, bids_dir=bids_dir, bids_validate=False
).run()

frames_start = np.array(meta.outputs.out_dict["FrameTimesStart"])
frames_duration = np.array(meta.outputs.out_dict["FrameDuration"])

mid_frames = frames_start + frames_duration / 2
wavg = np.trapz(data, x=mid_frames) / (mid_frames[-1] - mid_frames[0])

_, base, ext = split_filename(pet_file)
out_name = base.replace("_pet", "_desc-wavg_pet")
out_file = out_name + ext
nib.save(nib.Nifti1Image(wavg, img.affine), out_file)

def _list_outputs(self):
outputs = self._outputs().get()
pet_file = self.inputs.pet_file
_, base, ext = split_filename(pet_file)

out_name = base.replace("_pet", "_desc-wavg_pet")
out_file = os.path.abspath(out_name + ext)

outputs["out_file"] = out_file

return outputs
Loading