Skip to content

Commit 7b469aa

Browse files
Simple Defacing Report (#59)
* rename to qa * added SimpleBeforeAfterRPT image view for T1w * refactoring to work with 3D images in a tmp dir * added qa to run at end of petdeface --------- Co-authored-by: mnoergaard <[email protected]>
1 parent 01ea5f6 commit 7b469aa

File tree

5 files changed

+1615
-45
lines changed

5 files changed

+1615
-45
lines changed

petdeface/petdeface.py

Lines changed: 71 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,13 @@
3232
from mideface import Mideface
3333
from pet import WeightedAverage
3434
from utils import run_validator
35+
from qa import run_qa
3536
except ModuleNotFoundError:
3637
from .mideface import ApplyMideface
3738
from .mideface import Mideface
3839
from .pet import WeightedAverage
3940
from .utils import run_validator
41+
from .qa import run_qa
4042

4143

4244
# collect version from pyproject.toml
@@ -297,7 +299,7 @@ def deface(args: Union[dict, argparse.Namespace]) -> None:
297299
write_out_dataset_description_json(args.bids_dir)
298300

299301
# remove temp outputs - this is commented out to enable easier testing for now
300-
if str(os.getenv("DEBUG", "false")).lower() != "true":
302+
if str(os.getenv("PETDEFACE_DEBUG", "false")).lower() != "true":
301303
shutil.rmtree(os.path.join(output_dir, "petdeface_wf"))
302304

303305
return {"subjects": subjects}
@@ -671,21 +673,21 @@ def wrap_up_defacing(
671673
should_exclude = False
672674
for excluded_subject in participant_label_exclude:
673675
# Handle both cases: excluded_subject with or without 'sub-' prefix
674-
if excluded_subject.startswith('sub-'):
676+
if excluded_subject.startswith("sub-"):
675677
subject_pattern = f"/{excluded_subject}/"
676678
subject_pattern_underscore = f"/{excluded_subject}_"
677679
else:
678680
subject_pattern = f"/sub-{excluded_subject}/"
679681
subject_pattern_underscore = f"/sub-{excluded_subject}_"
680-
682+
681683
if subject_pattern in entry or subject_pattern_underscore in entry:
682684
should_exclude = True
683685
break
684-
686+
685687
# Skip excluded subject files, but copy everything else (including dataset-level files)
686688
if should_exclude:
687689
continue
688-
690+
689691
copy_path = entry.replace(str(path_to_dataset), str(final_destination))
690692
pathlib.Path(copy_path).parent.mkdir(
691693
parents=True, exist_ok=True, mode=0o775
@@ -730,7 +732,7 @@ def wrap_up_defacing(
730732
desc="defaced",
731733
return_type="file",
732734
)
733-
if str(os.getenv("DEBUG", "false")).lower() != "true":
735+
if str(os.getenv("PETDEFAC_DEBUG", "false")).lower() != "true":
734736
for extraneous in derivatives:
735737
os.remove(extraneous)
736738

@@ -741,15 +743,16 @@ def wrap_up_defacing(
741743
"placement must be one of ['adjacent', 'inplace', 'derivatives']"
742744
)
743745

744-
# clean up any errantly leftover files with globe in destination folder
745-
leftover_files = [
746-
pathlib.Path(defaced_nii)
747-
for defaced_nii in glob.glob(
748-
f"{final_destination}/**/*_defaced*.nii*", recursive=True
749-
)
750-
]
751-
for leftover in leftover_files:
752-
leftover.unlink()
746+
if not os.getenv("PETDEFACE_DEBUG"):
747+
# clean up any errantly leftover files with glob in destination folder
748+
leftover_files = [
749+
pathlib.Path(defaced_nii)
750+
for defaced_nii in glob.glob(
751+
f"{final_destination}/**/*_defaced*.nii*", recursive=True
752+
)
753+
]
754+
for leftover in leftover_files:
755+
leftover.unlink()
753756

754757
print(f"completed copying dataset to {final_destination}")
755758

@@ -770,7 +773,9 @@ def move_defaced_images(
770773
:param move_files: delete defaced images in "working" directory, e.g. move them to the destination dir instead of copying them there, defaults to False
771774
:type move_files: bool, optional
772775
"""
773-
# update paths in mapping dict
776+
# Create a new mapping with destination paths
777+
dest_mapping = {}
778+
774779
for defaced, raw in mapping_dict.items():
775780
# get common path and replace with final_destination to get new path
776781
common_path = os.path.commonpath([defaced.path, raw.path])
@@ -791,15 +796,13 @@ def move_defaced_images(
791796
]
792797
)
793798
)
794-
mapping_dict[defaced] = new_path
799+
dest_mapping[defaced] = new_path
795800

796801
# copy defaced images to new location
797-
for defaced, raw in mapping_dict.items():
798-
if pathlib.Path(raw).exists() and pathlib.Path(defaced).exists():
799-
shutil.copy(defaced.path, raw)
800-
else:
801-
pathlib.Path(raw).parent.mkdir(parents=True, exist_ok=True)
802-
shutil.copy(defaced.path, raw)
802+
for defaced, dest_path in dest_mapping.items():
803+
if pathlib.Path(defaced).exists():
804+
pathlib.Path(dest_path).parent.mkdir(parents=True, exist_ok=True)
805+
shutil.copy(defaced.path, dest_path)
803806

804807
if move_files:
805808
os.remove(defaced.path)
@@ -1056,6 +1059,12 @@ def cli():
10561059
required=False,
10571060
default=[],
10581061
)
1062+
parser.add_argument(
1063+
"--open-browser",
1064+
action="store_true",
1065+
default=False,
1066+
help="Open browser automatically after QA report generation",
1067+
)
10591068

10601069
arguments = parser.parse_args()
10611070
return arguments
@@ -1256,6 +1265,45 @@ def main(): # noqa: max-complexity: 12
12561265
)
12571266
petdeface.run()
12581267

1268+
# Generate QA reports if requested
1269+
print("\n" + "=" * 60)
1270+
print("Generating QA reports...")
1271+
print("=" * 60)
1272+
1273+
try:
1274+
# Determine the defaced directory based on placement
1275+
if args.placement == "adjacent":
1276+
defaced_dir = args.bids_dir.parent / f"{args.bids_dir.name}_defaced"
1277+
elif args.placement == "inplace":
1278+
defaced_dir = args.bids_dir
1279+
elif args.placement == "derivatives":
1280+
defaced_dir = args.bids_dir / "derivatives" / "petdeface"
1281+
else:
1282+
defaced_dir = (
1283+
args.output_dir
1284+
if args.output_dir
1285+
else args.bids_dir / "derivatives" / "petdeface"
1286+
)
1287+
1288+
# Run QA
1289+
qa_result = run_qa(
1290+
faced_dir=str(args.bids_dir),
1291+
defaced_dir=str(defaced_dir),
1292+
subject=(
1293+
" ".join(args.participant_label) if args.participant_label else None
1294+
),
1295+
open_browser=args.open_browser,
1296+
)
1297+
1298+
print("\n" + "=" * 60)
1299+
print("QA reports generated successfully!")
1300+
print(f"Reports available at: {qa_result['output_dir']}")
1301+
print("=" * 60)
1302+
1303+
except Exception as e:
1304+
print(f"\nError generating QA reports: {e}")
1305+
print("QA report generation failed, but defacing completed successfully.")
1306+
12591307

12601308
if __name__ == "__main__":
12611309
main()

0 commit comments

Comments
 (0)