File size: 3,515 Bytes
4f08626
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#!/usr/bin/env python3
"""
Debug script to inspect CXRSegDataset output.
Checks whether the Heart channel (idx 2, class_id 51) is all-zeros.
"""

import sys
import os
import argparse

# Make the vendored smp package importable
sys.path.insert(0, os.path.join(os.path.dirname(__file__),
                                "segmentation_models_pytorch"))

from segmentation_models_pytorch.datasets.cxr_seg_dataset import CXRSegDataset


def main():
    parser = argparse.ArgumentParser(
        description="Inspect CXRSegDataset mask channels for empty Heart/Aorta masks")
    parser.add_argument("--data_dir", type=str, required=True,
                        help="Path to lidc_TotalSeg directory")
    parser.add_argument("--num_samples", type=int, default=50,
                        help="Number of samples to inspect (default: 50)")
    args = parser.parse_args()

    # Channel name lookup (matches TARGET_GROUPS insertion order)
    channel_names = list(CXRSegDataset.TARGET_GROUPS.keys())
    HEART_CH = 2   # "Heart"
    AORTA_CH = 3   # "Aorta"

    print(f"Channel mapping: {dict(enumerate(channel_names))}")
    print(f"Heart  -> channel {HEART_CH} (class IDs {CXRSegDataset.TARGET_GROUPS['Heart']})")
    print(f"Aorta  -> channel {AORTA_CH} (class IDs {CXRSegDataset.TARGET_GROUPS['Aorta']})")
    print()

    # Instantiate dataset without albumentations — uses fallback tensor conversion
    dataset = CXRSegDataset(root_dir=args.data_dir, transform=None)
    total = len(dataset)
    print(f"Dataset contains {total} samples total.")

    n = min(args.num_samples, total)
    if n == 0:
        print("No samples found. Exiting.")
        return

    print(f"Inspecting the first {n} samples ...\n")

    empty_heart = 0
    empty_aorta = 0

    for idx in range(n):
        batch = dataset[idx]
        mask = batch["mask"]  # shape: (10, 256, 256)

        sums = [mask[ch].sum().item() for ch in range(mask.shape[0])]

        heart_sum = sums[HEART_CH]
        aorta_sum = sums[AORTA_CH]

        if heart_sum == 0:
            empty_heart += 1
        if aorta_sum == 0:
            empty_aorta += 1

        # Per-sample details for the first 10 and any with empty Heart
        if idx < 10 or heart_sum == 0:
            tag = " *** EMPTY Heart ***" if heart_sum == 0 else ""
            sample_info = batch.get("_debug_path", dataset.samples[idx]["mask_path"])
            print(f"[{idx:3d}] {os.path.basename(sample_info)}{tag}")
            for ch, name in enumerate(channel_names):
                print(f"       ch{ch} {name:16s}: sum={sums[ch]:10.1f}")
            print()

    # ---- Summary ----
    print("=" * 60)
    print(f"SUMMARY  (first {n} samples)")
    print(f"  Empty Heart masks : {empty_heart} / {n}")
    print(f"  Empty Aorta masks : {empty_aorta} / {n}")
    print()

    if empty_heart == n:
        print("!! ALL Heart masks are empty — likely a data-loading bug.")
        print("   Check: is class_id 51 present in labels_found for these patients?")
        # Quick diagnosis: check labels_found for first sample
        lf = dataset.samples[0]["labels_found"]
        print(f"   Sample 0 labels_found includes 51? -> {51 in lf}")
        print(f"   Sample 0 labels_found (first 20): {sorted(lf)[:20]}")
    elif empty_heart > 0:
        print(f"!! {empty_heart} samples have empty Heart masks (partial issue).")
    else:
        print("All samples have non-empty Heart masks. Channel 2 looks fine.")

    print("=" * 60)


if __name__ == "__main__":
    main()