File size: 3,957 Bytes
73e760d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47c4bf8
73e760d
 
 
47c4bf8
 
 
 
 
 
73e760d
 
47c4bf8
 
73e760d
 
 
 
 
 
47c4bf8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73e760d
 
 
47c4bf8
73e760d
 
47c4bf8
 
73e760d
 
47c4bf8
 
 
 
 
 
 
 
 
73e760d
 
 
 
 
47c4bf8
73e760d
 
47c4bf8
 
73e760d
 
 
 
 
47c4bf8
 
73e760d
 
 
 
 
 
 
47c4bf8
73e760d
 
 
 
 
 
 
 
 
 
 
 
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
117
118
119
120
"""
High-level Python API for Rootscope.

    from rootscope import predict_tif, predict_folder

    df = predict_tif("image.tif", out_dir="results/", gpu=True)
    df = predict_folder("my_tifs/", out_dir="results/", gpu=True)

Both return a pandas DataFrame of per-cell predictions and also write
per-image CSVs + labeled overlay PNGs into ``out_dir``.
"""

from pathlib import Path

import pandas as pd

from . import predict as _predict
from .weights import is_v4, resolve_cnn_weights, resolve_model_dir


def _load(model_dir=None, cnn_weights=None):
    """Resolve weights for the selected model version.

    v4 returns ``(model_dir, None, None, None, cnn)``: its classifiers are
    loaded inside the v4 stage (one bundle per seed, each carrying its own
    scaler and feature list), so there is nothing to pre-load here.
    """
    mdir = resolve_model_dir(model_dir)
    cnn = resolve_cnn_weights(cnn_weights)
    if is_v4():
        return mdir, None, None, None, cnn
    models_dict, scalers, feature_cols, le = _predict.load_models(str(mdir))
    if not models_dict:
        raise RuntimeError(f"No usable models found in {mdir}.")
    return models_dict, scalers, feature_cols, le, cnn


def _gpu_available():
    try:
        import torch
        return bool(torch.cuda.is_available())
    except Exception:  # noqa: BLE001
        return False


def _run_one(tif, loaded, out_dir, gpu, um_per_px, max_rounds, label_cells):
    if gpu is None:                      # library default: use a GPU if there is one
        gpu = _gpu_available()
    models_dict, scalers, feature_cols, le, cnn = loaded
    if is_v4():
        return _predict.predict_single_tif_v4(
            str(tif), model_dir=str(models_dict), um_per_px=um_per_px, gpu=gpu,
            out_dir=str(out_dir), max_rounds=max_rounds,
            cnn_weights=str(cnn) if cnn else None, label_cells=label_cells,
        )
    return _predict.predict_single_tif(
        str(tif), models_dict, scalers, feature_cols, le,
        um_per_px=um_per_px, gpu=gpu, out_dir=str(out_dir),
        max_rounds=max_rounds, cnn_weights=str(cnn) if cnn else None,
        label_cells=label_cells,
    )


def predict_tif(
    tif,
    out_dir="results",
    gpu=None,
    model_dir=None,
    cnn_weights=None,
    um_per_px=None,
    max_rounds=None,
    label_cells=False,
):
    """Segment + predict cell types for a single TIFF. Returns a DataFrame.

    ``um_per_px=None`` reads the pixel size from the TIFF's OME metadata and
    fails loudly if it is absent -- a wrong scale distorts every size feature.
    ``max_rounds=None`` uses the version's own setting (6 for v4, 10 for v2).
    """
    loaded = _load(model_dir, cnn_weights)
    return _run_one(tif, loaded, out_dir, gpu, um_per_px,
                    _predict.default_rounds(max_rounds), label_cells)


def predict_folder(
    tif_dir,
    out_dir="results",
    gpu=None,
    model_dir=None,
    cnn_weights=None,
    um_per_px=None,
    max_rounds=None,
    label_cells=False,
    pattern="*.tif",
):
    """Segment + predict for every TIFF in a folder. Returns a combined
    DataFrame and writes ``all_predictions.csv`` into ``out_dir``."""
    loaded = _load(model_dir, cnn_weights)
    rounds = _predict.default_rounds(max_rounds)
    tif_paths = sorted(Path(tif_dir).glob(pattern))
    if not tif_paths:
        raise FileNotFoundError(f"No files matching {pattern} in {tif_dir}")

    tables = []
    for tp in tif_paths:
        try:
            df = _run_one(tp, loaded, out_dir, gpu, um_per_px, rounds, label_cells)
            if df is not None:
                tables.append(df)
        except Exception as e:  # noqa: BLE001
            print(f"  FAILED on {tp.name}: {e}")

    if not tables:
        return None
    combined = pd.concat(tables, ignore_index=True)
    out = Path(out_dir)
    out.mkdir(parents=True, exist_ok=True)
    combined.to_csv(out / "all_predictions.csv", index=False)
    return combined