|
37 | 37 | sys.path.insert(0, str(current_dir)) |
38 | 38 |
|
39 | 39 | # Import using absolute imports (script mode) |
40 | | - from mideface import ApplyMideface, Mideface |
| 40 | + from mideface import ApplyMideface, Mideface, TemplateFacemask |
41 | 41 | from pet import WeightedAverage |
42 | 42 | from qa import run_qa |
43 | 43 | from utils import run_validator |
@@ -436,39 +436,84 @@ def init_single_subject_wf( |
436 | 436 | if determine_in_docker(): |
437 | 437 | preview_pics = False |
438 | 438 |
|
439 | | - deface_t1w = Node( |
440 | | - Mideface( |
441 | | - in_file=pathlib.Path(t1w_file), |
442 | | - pics=preview_pics, |
443 | | - odir=".", |
444 | | - code=f"{anat_string}", |
445 | | - ), |
446 | | - name=f"deface_t1w_{anat_string}", |
| 439 | + # Check if this is a template anatomical image created from PET averaging |
| 440 | + is_template_from_pet = use_template_anat == "pet" and "desc-totallyat1w" in str( |
| 441 | + t1w_file |
447 | 442 | ) |
448 | | - t1w_wf.connect( |
449 | | - [ |
450 | | - ( |
451 | | - deface_t1w, |
452 | | - datasink, |
453 | | - [ |
454 | | - ("out_file", f"{anat_string.replace('_', '.')}.anat"), |
455 | | - ( |
456 | | - "out_facemask", |
457 | | - f"{anat_string.replace('_', '.')}.anat.@defacemask", |
458 | | - ), |
459 | | - ( |
460 | | - "out_before_pic", |
461 | | - f"{anat_string.replace('_', '.')}.anat.@before", |
462 | | - ), |
463 | | - ( |
464 | | - "out_after_pic", |
465 | | - f"{anat_string.replace('_', '.')}.anat.@after", |
466 | | - ), |
467 | | - ], |
| 443 | + |
| 444 | + if is_template_from_pet: |
| 445 | + # For PET-averaged templates, we need to create a facemask without defacing the template |
| 446 | + # We'll use TemplateFacemask to generate the facemask without defacing the template |
| 447 | + from mideface import TemplateFacemask |
| 448 | + |
| 449 | + # Create a node that generates facemask but doesn't deface the template |
| 450 | + template_facemask = Node( |
| 451 | + TemplateFacemask( |
| 452 | + in_file=pathlib.Path(t1w_file), |
| 453 | + no_pics=True, # No preview pics for templates |
| 454 | + no_post=True, # No post-processing for templates |
| 455 | + odir=".", |
| 456 | + code=f"{anat_string}_template", |
468 | 457 | ), |
469 | | - ] |
470 | | - ) |
471 | | - t1w_workflows[t1w_file] = {"workflow": t1w_wf, "anat_string": anat_string} |
| 458 | + name=f"deface_t1w_{anat_string}", |
| 459 | + ) |
| 460 | + |
| 461 | + # Only connect the facemask output, not the defaced template |
| 462 | + # This way we get the facemask for PET defacing but don't deface the template itself |
| 463 | + t1w_wf.connect( |
| 464 | + [ |
| 465 | + ( |
| 466 | + template_facemask, |
| 467 | + datasink, |
| 468 | + [ |
| 469 | + ( |
| 470 | + "out_facemask", |
| 471 | + f"{anat_string.replace('_', '.')}.anat.@defacemask", |
| 472 | + ), |
| 473 | + ], |
| 474 | + ), |
| 475 | + ] |
| 476 | + ) |
| 477 | + else: |
| 478 | + # Normal defacing for real anatomical images |
| 479 | + deface_t1w = Node( |
| 480 | + Mideface( |
| 481 | + in_file=pathlib.Path(t1w_file), |
| 482 | + pics=preview_pics, |
| 483 | + odir=".", |
| 484 | + code=f"{anat_string}", |
| 485 | + ), |
| 486 | + name=f"deface_t1w_{anat_string}", |
| 487 | + ) |
| 488 | + t1w_wf.connect( |
| 489 | + [ |
| 490 | + ( |
| 491 | + deface_t1w, |
| 492 | + datasink, |
| 493 | + [ |
| 494 | + ("out_file", f"{anat_string.replace('_', '.')}.anat"), |
| 495 | + ( |
| 496 | + "out_facemask", |
| 497 | + f"{anat_string.replace('_', '.')}.anat.@defacemask", |
| 498 | + ), |
| 499 | + ( |
| 500 | + "out_before_pic", |
| 501 | + f"{anat_string.replace('_', '.')}.anat.@before", |
| 502 | + ), |
| 503 | + ( |
| 504 | + "out_after_pic", |
| 505 | + f"{anat_string.replace('_', '.')}.anat.@after", |
| 506 | + ), |
| 507 | + ], |
| 508 | + ), |
| 509 | + ] |
| 510 | + ) |
| 511 | + |
| 512 | + t1w_workflows[t1w_file] = { |
| 513 | + "workflow": t1w_wf, |
| 514 | + "anat_string": anat_string, |
| 515 | + "is_template_from_pet": is_template_from_pet, |
| 516 | + } |
472 | 517 |
|
473 | 518 | workflow = Workflow(name=name) |
474 | 519 | if anat_only: |
@@ -1111,7 +1156,7 @@ def cli(): |
1111 | 1156 | ) |
1112 | 1157 | parser.add_argument( |
1113 | 1158 | "--use_template_anat", |
1114 | | - help="Use template anatomical image when no T1w is available for PET scans. Options: 't1', 'mni', or 'pet'", |
| 1159 | + help="Use template anatomical image when no T1w is available for PET scans. Options: 't1' (included T1w template), 'mni' (MNI template), or 'pet' (averaged PET image).", |
1115 | 1160 | type=str, |
1116 | 1161 | required=False, |
1117 | 1162 | default=False, |
@@ -1354,29 +1399,29 @@ def main(): # noqa: max-complexity: 12 |
1354 | 1399 |
|
1355 | 1400 | def collect_subject_files(file_list: list) -> dict: |
1356 | 1401 | """Collect files that share the same subject ID into a dictionary. |
1357 | | - |
| 1402 | +
|
1358 | 1403 | Parameters |
1359 | 1404 | ---------- |
1360 | 1405 | file_list : list |
1361 | 1406 | List of file paths to process |
1362 | | - |
| 1407 | +
|
1363 | 1408 | Returns |
1364 | 1409 | ------- |
1365 | 1410 | dict |
1366 | 1411 | Dictionary mapping subject IDs to lists of their files |
1367 | 1412 | Format: {"sub-*": ["file1", "file2", ...]} |
1368 | 1413 | """ |
1369 | 1414 | subject_files = {} |
1370 | | - |
| 1415 | + |
1371 | 1416 | for file_path in file_list: |
1372 | 1417 | # Extract subject ID using regex - matches 'sub-' followed by alphanumeric chars until underscore |
1373 | | - match = re.search(r'sub-[a-zA-Z0-9]+(?=_)', str(file_path)) |
| 1418 | + match = re.search(r"sub-[a-zA-Z0-9]+(?=_)", str(file_path)) |
1374 | 1419 | if match: |
1375 | 1420 | sub_id = match.group(0) |
1376 | 1421 | if sub_id not in subject_files: |
1377 | 1422 | subject_files[sub_id] = [] |
1378 | 1423 | subject_files[sub_id].append(str(file_path)) |
1379 | | - |
| 1424 | + |
1380 | 1425 | return subject_files |
1381 | 1426 |
|
1382 | 1427 |
|
|
0 commit comments