Skip to content

Commit 3d19003

Browse files
committed
mni works on at least 1 subject
1 parent 338f1f8 commit 3d19003

File tree

4 files changed

+176
-37
lines changed

4 files changed

+176
-37
lines changed

petdeface/__init__.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
"""
2+
PET Deface - A nipype PET and MR defacing pipeline for BIDS datasets.
3+
4+
This package provides tools for defacing PET and MR images using FreeSurfer's MiDeFace.
5+
"""
6+
7+
import sys
8+
from pathlib import Path
9+
10+
# Add the package directory to sys.path when running as script
11+
if __name__ == "__main__" or (
12+
len(sys.argv) > 0 and sys.argv[0].endswith("petdeface.py")
13+
):
14+
# Running as script - add current directory to path
15+
package_dir = Path(__file__).parent
16+
if str(package_dir) not in sys.path:
17+
sys.path.insert(0, str(package_dir))
18+
19+
# Import main components
20+
try:
21+
from .petdeface import PetDeface, deface, cli, main
22+
from .mideface import ApplyMideface, Mideface
23+
from .pet import WeightedAverage
24+
from .qa import run_qa
25+
from .utils import run_validator
26+
from .noanat import copy_default_anat_to_subject, remove_default_anat
27+
except ImportError:
28+
# Fallback for when running as script
29+
try:
30+
from petdeface import PetDeface, deface, cli, main
31+
from mideface import ApplyMideface, Mideface
32+
from pet import WeightedAverage
33+
from qa import run_qa
34+
from utils import run_validator
35+
from noanat import copy_default_anat_to_subject, remove_default_anat
36+
except ImportError:
37+
# Last resort - import from current directory
38+
import os
39+
40+
sys.path.insert(0, os.path.dirname(__file__))
41+
from petdeface import PetDeface, deface, cli, main
42+
from mideface import ApplyMideface, Mideface
43+
from pet import WeightedAverage
44+
from qa import run_qa
45+
from utils import run_validator
46+
from noanat import copy_default_anat_to_subject, remove_default_anat
47+
48+
# Version info
49+
try:
50+
from importlib.metadata import version
51+
52+
__version__ = version("petdeface")
53+
except ImportError:
54+
__version__ = "unknown"
55+
56+
__bids_version__ = "1.8.0"
57+
58+
# Main exports
59+
__all__ = [
60+
"PetDeface",
61+
"deface",
62+
"cli",
63+
"main",
64+
"ApplyMideface",
65+
"Mideface",
66+
"WeightedAverage",
67+
"run_qa",
68+
"run_validator",
69+
"copy_default_anat_to_subject",
70+
"remove_default_anat",
71+
"__version__",
72+
"__bids_version__",
73+
]

petdeface/noanat.py

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Functionality for handling cases where anatomical images are not available."""
2+
23
import nibabel
34
import numpy
45
import shutil
@@ -318,17 +319,19 @@ def remove_default_anat(
318319
remaining_files = set(anat_dir.iterdir()) - set(created_files)
319320
if not remaining_files:
320321
created_dirs.append(anat_dir)
321-
322-
# Remove the files
323-
for file_path in created_files:
324-
if file_path.exists():
325-
file_path.unlink()
326-
print(f"Removed file: {file_path}")
327-
328-
# Remove the directories (only if they're empty)
329-
for dir_path in created_dirs:
330-
if dir_path.exists() and not any(dir_path.iterdir()):
331-
dir_path.rmdir()
332-
print(f"Removed directory: {dir_path}")
333-
334-
print("Cleanup completed successfully")
322+
if os.getenv("PETDEFACE_DEBUG", "").lower() != "true":
323+
# Remove the files
324+
for file_path in created_files:
325+
if file_path.exists():
326+
file_path.unlink()
327+
print(f"Removed file: {file_path}")
328+
329+
# Remove the directories (only if they're empty)
330+
for dir_path in created_dirs:
331+
if dir_path.exists() and not any(dir_path.iterdir()):
332+
dir_path.rmdir()
333+
print(f"Removed directory: {dir_path}")
334+
335+
print("Cleanup completed successfully")
336+
else:
337+
print("PETDEFACE_DEBUG=true, leaving template T1w's in place")

petdeface/petdeface.py

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,34 @@
2626
from importlib.metadata import version
2727

2828

29-
try:
30-
from mideface import ApplyMideface
31-
from mideface import Mideface
29+
# Import local modules - handle both script and module execution
30+
import sys
31+
import os
32+
from pathlib import Path
33+
34+
# Determine if we're running as a script (including through debugger)
35+
is_script = (
36+
__name__ == "__main__"
37+
or len(sys.argv) > 0
38+
and sys.argv[0].endswith("petdeface.py")
39+
or "debugpy" in sys.modules
40+
)
41+
42+
if is_script:
43+
# Running as script - add current directory to path for local imports
44+
current_dir = Path(__file__).parent
45+
if str(current_dir) not in sys.path:
46+
sys.path.insert(0, str(current_dir))
47+
48+
# Import using absolute imports (script mode)
49+
from mideface import ApplyMideface, Mideface
3250
from pet import WeightedAverage
3351
from qa import run_qa
3452
from utils import run_validator
3553
from noanat import copy_default_anat_to_subject, remove_default_anat
36-
except ModuleNotFoundError:
37-
from .mideface import ApplyMideface
38-
from .mideface import Mideface
54+
else:
55+
# Running as module - use relative imports
56+
from .mideface import ApplyMideface, Mideface
3957
from .pet import WeightedAverage
4058
from .qa import run_qa
4159
from .utils import run_validator
@@ -1321,23 +1339,12 @@ def main(): # noqa: max-complexity: 12
13211339

13221340
try:
13231341
# Determine the defaced directory based on placement
1324-
if args.placement == "adjacent":
1325-
defaced_dir = args.bids_dir.parent / f"{args.bids_dir.name}_defaced"
1326-
elif args.placement == "inplace":
1327-
defaced_dir = args.bids_dir
1328-
elif args.placement == "derivatives":
1329-
defaced_dir = args.bids_dir / "derivatives" / "petdeface"
1330-
else:
1331-
defaced_dir = (
1332-
args.output_dir
1333-
if args.output_dir
1334-
else args.bids_dir / "derivatives" / "petdeface"
1335-
)
13361342

13371343
# Run QA
13381344
qa_result = run_qa(
13391345
faced_dir=str(args.bids_dir),
1340-
defaced_dir=str(defaced_dir),
1346+
defaced_dir=str(args.output_dir),
1347+
output_dir=str(args.bids_dir / "derivatives" / "petdeface" / "qa"),
13411348
subject=(
13421349
" ".join(args.participant_label) if args.participant_label else None
13431350
),

petdeface/qa.py

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import os
33
import tempfile
44
import shutil
5+
import sys
56
from glob import glob
67
import nilearn
78
from nilearn import plotting
@@ -17,15 +18,36 @@
1718
import seaborn as sns
1819
from PIL import Image, ImageDraw
1920
from nipype import Workflow, Node
20-
from nireports.interfaces.reporting.base import SimpleBeforeAfterRPT
2121
from tempfile import TemporaryDirectory
2222
from pathlib import Path
2323

24+
# Handle imports for both script and module execution (including debugger)
25+
is_script = (
26+
__name__ == "__main__"
27+
or len(sys.argv) > 0
28+
and sys.argv[0].endswith("qa.py")
29+
or "debugpy" in sys.modules
30+
)
31+
32+
if is_script:
33+
# Running as script - add current directory to path for local imports
34+
current_dir = Path(__file__).parent
35+
if str(current_dir) not in sys.path:
36+
sys.path.insert(0, str(current_dir))
37+
38+
# Import nireports - this should work in both script and module mode
39+
from nireports.interfaces.reporting.base import SimpleBeforeAfterRPT
40+
2441

2542
def preprocess_single_subject(s, output_dir):
2643
"""Preprocess a single subject's images (for parallel processing)."""
2744
temp_dir = os.path.join(output_dir, "temp_3d_images")
2845

46+
# Debug: Print what files we're processing
47+
print(f"Preprocessing subject {s['id']}:")
48+
print(f" Original: {s['orig_path']}")
49+
print(f" Defaced: {s['defaced_path']}")
50+
2951
# Extract BIDS suffix from original path to preserve meaningful naming
3052
orig_basename = os.path.basename(s["orig_path"])
3153
defaced_basename = os.path.basename(s["defaced_path"])
@@ -484,7 +506,13 @@ def create_comparison_html(
484506
("original", orig_img, orig_basename, "hot"), # Colored for original
485507
("defaced", defaced_img, defaced_basename, "gray"), # Grey for defaced
486508
]:
487-
# save image to temp folder for later loading
509+
# Debug: Print what we're processing
510+
print(f"Processing {label} image: {basename}")
511+
print(f" Image shape: {img.shape}")
512+
print(f" Image data type: {img.get_data_dtype()}")
513+
print(
514+
f" Image min/max: {img.get_fdata().min():.3f}/{img.get_fdata().max():.3f}"
515+
)
488516

489517
# Create single sagittal slice using matplotlib directly
490518
img_data = img.get_fdata()
@@ -819,6 +847,13 @@ def get_unique_key(file_path):
819847
defaced_map = {get_unique_key(f): f for f in defaced_files}
820848
common_keys = sorted(set(orig_map.keys()) & set(defaced_map.keys()))
821849

850+
# Debug: Print what files are being found
851+
print(f"Found {len(orig_files)} files in original directory")
852+
print(f"Found {len(defaced_files)} files in defaced directory")
853+
print(f"Found {len(common_keys)} common files")
854+
for key in common_keys:
855+
print(f" {key}: {orig_map[key]} -> {defaced_map[key]}")
856+
822857
subjects = []
823858
for key in common_keys:
824859
orig_path = orig_map[key]
@@ -833,19 +868,40 @@ def get_unique_key(file_path):
833868
else:
834869
subject_id = sub_id
835870

871+
# Check if this is a T1w file (prioritize T1w over PET)
872+
is_t1w = "T1w" in orig_path or "T1w" in defaced_path
873+
836874
subjects.append(
837875
{
838876
"id": subject_id,
839877
"orig_path": orig_path,
840878
"defaced_path": defaced_path,
879+
"is_t1w": is_t1w,
841880
}
842881
)
843882

844-
if not subjects:
883+
# Sort subjects to prioritize T1w files over PET files
884+
subjects.sort(key=lambda x: (not x["is_t1w"], x["id"]))
885+
886+
# For each subject, only keep the T1w file if available, otherwise keep the first file
887+
filtered_subjects = []
888+
seen_subjects = set()
889+
890+
for subject in subjects:
891+
subject_id = subject["id"]
892+
if subject_id not in seen_subjects:
893+
filtered_subjects.append(subject)
894+
seen_subjects.add(subject_id)
895+
elif subject["is_t1w"]:
896+
# Replace the existing entry with the T1w version
897+
filtered_subjects = [s for s in filtered_subjects if s["id"] != subject_id]
898+
filtered_subjects.append(subject)
899+
900+
if not filtered_subjects:
845901
print("No matching NIfTI files found in both datasets.")
846902
exit(1)
847903

848-
return subjects
904+
return filtered_subjects
849905

850906

851907
def create_side_by_side_index_html(subjects, output_dir, size="compact"):

0 commit comments

Comments
 (0)