Classifier packets
A classifier packet is a single file (*.roicat_classifier) holding a
trained ROI classifier together with the embedder and preprocessing settings it
was trained with. Loading a packet reproduces the training-time inference
pipeline on any machine. The class is
ClassifierPackage.
Contents
classifier: The trained model, stored as ONNX.
embedder: The
roicat.ROInetmodel.py,params.json, and weights.preprocessing: Raw ROI image size,
forward_pass_version, and theum_per_pixelof the training data.label_names: Class names corresponding to the classifier’s output indices.
metadata: The network’s identity (see below), the classifier’s class values in
label_namesorder, a SHA-256 for every other member, a schema version, and the ROICaT version that wrote the file.
The embedder’s model.py travels with its weights, so packets keep loading
after ROICaT’s own network definitions change. File size is dominated by the
network weights: roughly 100 MB for the current ROInet releases.
embedder/params.json is the network bundle’s own params.json verbatim, so
it also carries the hyperparameters and paths of the training run that produced
the network. That is kept as provenance; the bundled make_model ignores the
keys it does not need.
Which network is inside
Every packet records the network it holds:
>>> packet.embedder_identity
{'release': 'classification',
'bundle_md5': '357a8d9b630ec79f3e015d0056a4c2d5',
'download_url': 'https://osf.io/c8m3b/download',
'forward_pass_version': 'head',
'torchvision_model': 'convnext_tiny',
'pre_head_fc_sizes': [256, 128],
'post_head_fc_sizes': [128]}
bundle_md5 is hashed from the downloaded bundle, so it reports what was
loaded rather than what was requested. release is 'unknown' for a network
that is not one of ROICaT’s published releases, which is not an error.
A classifier is only valid with the network it was trained on, and the number of
features alone does not identify a network: the tracking release emits 256
features at forward_pass_version='head' but 128 at 'latent', the same as
the classification release.
Where packets fit
Classification splits into work done once and work done repeatedly:
Once per classifier: label ROIs (A1, B1a, B1b), then train (B2).
B2writes the packet.Once per new dataset: run inference (B3), often on a different machine or by a different person.
B3reads the packet.
The packet is the handoff between the two. Tracking does not use packets.
Predictions are only valid if inference embeds images the same way training did.
Without a packet, that agreement is restated by hand at inference time: the
ROInet_embedder arguments download_url,
download_hash, and forward_pass_version, plus the raw image size and the
scale-normalization constants. A mismatch in any of them gives wrong labels and
no error. The packet records these values when it is written and replays them
when it is loaded.
Only ROInet_embedder can be packed. To deploy a network
you trained yourself, publish it as a bundle (model.py + params.json +
weights) and load it through ROInet_embedder.
An embedder driven with generate_dataloader(transforms=...) cannot be packed.
A custom chain replaces the packet’s own preprocessing without being recorded
anywhere, so packing raises instead of silently recording the wrong configuration.
Configure preprocessing through
Preprocessor_ROI_images arguments instead.
Recovering from the refusal takes three steps: re-run generate_dataloader()
without the argument, then generate_latents(), then refit the classifier.
Clearing the flag alone leaves the classifier fit on latents from the custom
chain, and the packet then builds with the wrong preprocessor recorded.
Writing a packet
At the end of training. See the B2 notebook
for the surrounding workflow, and the API reference for
ClassifierPackage,
Auto_LogisticRegression, and
roicat.data_importing.
import numpy as np
import roicat
## `data` is a Data_roicat / Data_suite2p object with ROI_images and class labels
roinet = roicat.ROInet.ROInet_embedder(
device='cpu',
dir_networkFiles='/tmp',
download_url='https://osf.io/c8m3b/download', ## the classification release
download_hash='357a8d9b630ec79f3e015d0056a4c2d5',
forward_pass_version='head',
)
roinet.generate_dataloader(
ROI_images=data.ROI_images,
um_per_pixel=data.um_per_pixel,
)
roinet.generate_latents()
autoclassifier = roicat.classification.classifier.Auto_LogisticRegression(
X=np.array(roinet.latents).astype(np.float32),
y=np.concatenate(data.class_labels_index).astype(np.int64),
params_LogisticRegression={'C': [1e-13, 1e3]},
)
autoclassifier.fit()
packet = roicat.classification.ClassifierPackage(
classifier=autoclassifier,
embedder=roinet,
label_names=[str(l) for l in autoclassifier.model_best.classes_],
size_images_in=data.ROI_images[0].shape[1:], ## raw ROI image (height, width)
um_per_pixel_training=data.um_per_pixel[0],
)
packet.save('/path/to/mouse_1.roicat_classifier')
Loading a packet
On any machine and any dataset. See the B3 notebook.
import numpy as np
import roicat
packet = roicat.classification.ClassifierPackage.load('/path/to/mouse_1.roicat_classifier')
label_ids, probs = packet.predict(
roi_images=np.concatenate(data.ROI_images, axis=0), ## RAW images, (n_rois, height, width)
um_per_pixel=data.um_per_pixel[0], ## resolution of THESE images
)
labels = [packet.label_names[i] for i in label_ids]
label_ids are positions in label_names, always in range(n_classes).
They are not the class values the classifier was fit on, which need not be
0, 1, 2, ... — training data in which one class never appears gives
classes_ == [0, 2]. The packet records the class values so predict can
translate. probs columns are in the same order.
um_per_pixel describes the data being classified and is required on every
call; it does not have to match the training data’s resolution.
predict() takes a single
value, so sessions of differing resolution must be predicted one at a time. The
raw image height and width, however, must equal the packet’s
size_images_in.
Loading raises roicat.classification.package.PackageIntegrityError if
any recorded hash, the schema version, or the classifier’s expected input width
disagrees with the file.
Predicted labels can be applied to tracking results with the UCID helpers in
roicat.util; see Inputs and Outputs.
API reference: roicat.classification.package.ClassifierPackage,
save(),
load(),
predict().