File size: 4,985 Bytes
e4a7008
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
"""Build a MedCLIPSeg dataset for the neurofilament task (no manual masks needed).

For every CZI with an IMARIS _Statistics folder, writes:
  data/Neurofilament/<split>_Folder/img/<name>.png     (neurofilament MIP, RGB)
  data/Neurofilament/<split>_Folder/label/<name>.png   (IMARIS-reconstruction mask)
  data/Neurofilament/Prompts_Folder/<split>_text.xlsx  (Image, Ground Truth, Description)

The mask is the k-NN reconstruction of IMARIS's traced skeleton from its segment
coordinates (dilated) — the same approximate label used elsewhere. Splits are by
ANIMAL so validation/test are unseen animals. Then fine-tune the bundled model:

  cd reference_medclipseg
  python train.py --config-file configs/Neurofilament.yaml   # needs a CUDA GPU

Honest note: this label is approximate (reconstructed, not IMARIS's exact voxel
mask), and the target is IMARIS's over-traced tangle — see README.
"""
import argparse, glob, os, sys
import numpy as np
import pandas as pd
from scipy import ndimage as ndi
from scipy.spatial import cKDTree
from skimage.transform import resize
from skimage.io import imsave

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import processing as P

ROOT = "/Users/slf20757/Desktop/Dr. Fernandes"
SIZE = 224
PROMPT = "a fluorescence microscopy image of neurofilament nerve fibers"
# animal -> split
VAL = {"C1"}
TEST = {"C2b"}


def seg_xyz(sd, stem):
    def cols(name, nc):
        r = []
        for line in open(os.path.join(sd, f"{stem}_{name}.csv"), errors="ignore"):
            p = line.split(",")
            try:
                r.append([float(p[i]) for i in range(nc)])
            except (ValueError, IndexError):
                pass
        return np.array(r)
    return cols("Segment_Position", 3)


def skel_mask(pos, voxel, shape_yx, k=3):
    dz, dy, dx = voxel; ny, nx = shape_yx
    col = pos[:, 0] / dx * (SIZE / nx); row = pos[:, 1] / dy * (SIZE / ny)
    pts = np.stack([row, col], 1)
    tree = cKDTree(pts); dist, idx = tree.query(pts, k=min(k + 1, len(pts)))
    radius = 2.0 / dx * (SIZE / nx)
    m = np.zeros((SIZE, SIZE), bool)
    for i in range(len(pts)):
        for j, d in zip(idx[i, 1:], dist[i, 1:]):
            if d <= radius:
                p0, p1 = pts[i], pts[j]
                n = max(int(np.abs(p1 - p0).max()) + 1, 2)
                t = np.linspace(0, 1, n)[:, None]; line = p0 + t * (p1 - p0)
                m[np.clip(line[:, 0].astype(int), 0, SIZE - 1),
                  np.clip(line[:, 1].astype(int), 0, SIZE - 1)] = True
    return ndi.binary_dilation(m, iterations=1)


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--out", default="data/Neurofilament")
    ap.add_argument("--limit", type=int, default=0, help="cap images (for testing)")
    args = ap.parse_args()
    rows = {"Train": [], "Val": [], "Test": []}
    n = 0
    for csv in sorted(glob.glob(os.path.join(ROOT, "*", "*_Statistics",
                                             "*_Filament_Length_(sum).csv"))):
        sd = os.path.dirname(csv)
        stem = os.path.basename(csv).replace("_Filament_Length_(sum).csv", "")
        animal = os.path.relpath(csv, ROOT).split(os.sep)[0]
        czi = os.path.join(ROOT, animal, stem.replace(" filament", "") + ".czi")
        if not (os.path.exists(czi) and
                os.path.exists(os.path.join(sd, f"{stem}_Segment_Position_X.csv"))):
            continue
        split = "Val" if animal in VAL else "Test" if animal in TEST else "Train"
        img = P.load_image(czi); nf, _ = P.guess_channels(img)
        vol = img.data[nf]; ny, nx = vol.shape[1], vol.shape[2]
        mip = resize(P.channel_preview(vol), (SIZE, SIZE), preserve_range=True).astype(np.uint8)
        mask = (skel_mask(seg_xyz(sd, stem), img.voxel, (ny, nx)) * 255).astype(np.uint8)
        name = stem.replace(" ", "_") + ".png"
        for sub in ("img", "label"):
            os.makedirs(os.path.join(args.out, f"{split}_Folder", sub), exist_ok=True)
        imsave(os.path.join(args.out, f"{split}_Folder", "img", name),
               np.stack([mip] * 3, -1))
        imsave(os.path.join(args.out, f"{split}_Folder", "label", name), mask)
        rows[split].append({"Image": name, "Ground Truth": name, "Description": PROMPT})
        n += 1
        print(f"[{split}] {stem}: mask px={int((mask>0).sum())}", flush=True)
        del img, vol
        if args.limit and n >= args.limit:
            break

    os.makedirs(os.path.join(args.out, "Prompts_Folder"), exist_ok=True)
    for split, r in rows.items():
        if r:
            pd.DataFrame(r).to_excel(
                os.path.join(args.out, "Prompts_Folder", f"{split}_text.xlsx"),
                index=False)
    print(f"\nDone. Train={len(rows['Train'])} Val={len(rows['Val'])} "
          f"Test={len(rows['Test'])} -> {args.out}")
    print("Now fine-tune (GPU): cd reference_medclipseg && "
          "python train.py --config-file configs/Neurofilament.yaml")


if __name__ == "__main__":
    main()