Validation · Mira image analysis
How we tested Mira's image analysis
Mira analyses microscope pictures inside your browser, and the pictures never leave your computer. It can also run published AI models that find cells and nuclei. We wanted to answer two questions. First, does each AI model find the same cells in the browser as in its original Python version? Second, how close are Mira's guided tasks (counting cells, measuring a wound and measuring stained area) to what experts marked by hand on public data sets? All numbers on this page were measured by us. When we tuned a setting on the same pictures that we then scored, we say so.
Summary
Two things to keep in mind. The AI accuracy numbers were measured with the original Python programs. Since the browser versions give the same results (see "AI parity"), the numbers also apply to Mira. The wound and stained-area fixes were found and tuned on these same data sets, so those results show how well the tuning worked. They are not an independent test.
Data we used
| Data set | What it contains | What we used it for | Licence |
|---|---|---|---|
| BBBC039v1 (Caicedo et al. 2019, Broad Bioimage Benchmark Collection) | U2OS cells stained with Hoechst, 16-bit fluorescence pictures of 520 × 696 pixels, with every nucleus outlined by experts. The test part has 50 pictures | AI parity and accuracy, and cell counting | CC0 |
| Kromp et al. 2020 (Scientific Data) | 37 annotated fluorescence pictures of tissue sections and cell lines, from 10× to 63× magnification | AI accuracy on harder pictures that are new to the models | Published with the paper; we do not share copies |
| BBBC019v2 (Zaritsky et al. 2013) | Wound-healing (scratch) assays in DIC and bright-field, with a hand-drawn cell mask for every picture (12 HEK293 and 54 SN15 pictures) | Scratch / wound area | CC BY 3.0 |
| IHC TMA data set (Wang et al., Zenodo 7647846) | 256 × 256 pixel pieces of lung-cancer tissue microarrays, where pathologists marked every positive and negative nucleus. 25 pieces each for Ki67, P53, cyclin D1 and CDK4 | Measure stained area (DAB) | CC BY 4.0 |
Eight of these pictures come with Mira as examples, unchanged. The credits are in public/examples/mira/NOTICE.txt.
AI parity: browser compared with the original program
To run the models in a browser, we converted each network to ONNX and run it with ONNX Runtime Web (on WebGPU, or WebAssembly if WebGPU is not available). The steps before and after the network are written in Mira's own code. These include the StarDist overlap removal, the Cellpose flow tracking and size model, and the InstanSeg clustering. To compare, we matched objects that overlap with IoU above 0.5 and averaged the IoU in both directions. We only offer a model in Mira if every picture scores at least 0.98 and the average is at least 0.99.
| Model | Original version | Pictures | Objects (Python → browser) | Mean IoU | Worst picture |
|---|---|---|---|---|---|
| StarDist versatile_fluo | StarDist 0.9.2 (TensorFlow) | 20 | 2,054 → 2,054 | 0.99995 | 0.99896 |
| Cellpose nuclei (automatic diameter) | Cellpose 3.1.1.2 (PyTorch) | 20 | 2,061 → 2,061 | 1.00000 | 1.00000 |
| Cellpose cyto3 (automatic diameter) | Cellpose 3.1.1.2 (PyTorch) | 20 | 2,122 → 2,122 | 1.00000 | 1.00000 |
| InstanSeg brightfield_nuclei | InstanSeg v0.1.1 (TorchScript) | 20 | 3,837 → 3,837 | 0.99999 | 0.99994 |
For StarDist and Cellpose we used 20 BBBC039 test pictures. For InstanSeg we used its own example tile in five versions (as it is, flipped two ways, rotated, and as a 2 × 2 mosaic) plus 15 BBBC039 and Kromp pictures recoloured to look like bright-field. This only tests parity, not real tissue. The automatic cell diameters in the browser were the same as in Python to two decimals on every picture. We also checked on the Python side that the ONNX networks match the original networks on 25 pictures each (Cellpose flows within 2.1 × 10⁻⁴ and identical masks; StarDist and InstanSeg identical labels).
AI accuracy on labelled test sets
| Model | Test set | Pictures | AP@0.5 | F1@0.5 | AP@0.75 | Mean IoU of matches | Count error |
|---|---|---|---|---|---|---|---|
| StarDist versatile_fluo | BBBC039 test* | 50 | 0.906 | 0.951 | 0.854 | 0.912 | 8.5 % |
| StarDist versatile_fluo | Kromp 2020 | 37 | 0.732 | 0.845 | 0.517 | 0.825 | 25.8 % |
| Cellpose nuclei | BBBC039 test | 50 | 0.898 | 0.946 | 0.864 | 0.933 | 10.6 % |
| Cellpose nuclei | Kromp 2020 | 37 | 0.817 | 0.899 | 0.581 | 0.828 | 8.1 % |
| Cellpose cyto3 | BBBC039 test | 50 | 0.918 | 0.957 | 0.879 | 0.931 | 5.7 % |
| Cellpose cyto3 | Kromp 2020 | 37 | 0.815 | 0.898 | 0.602 | 0.836 | 11.2 % |
| InstanSeg brightfield | Not measured, because neither labelled set is bright-field. Mira tells users that this model is "not yet validated on a labelled dataset". | ||||||
AP@0.5 = TP / (TP + FP + FN) at IoU 0.5. Count error is the average difference in the number of objects. All runs used automatic settings. * StarDist versatile_fluo was trained on nuclei from the Data Science Bowl 2018, which includes U2OS pictures similar to BBBC039, so its BBBC039 score is not fully independent. Kromp 2020 was not part of its training. We did not check whether Cellpose was trained on these pictures. Kromp 2020 AP@0.5 by magnification (10× / 20× / 40× / 63×): StarDist 0.90 / 0.70 / 0.82 / 0.65, Cellpose nuclei 0.93 / 0.78 / 0.85 / 0.82, Cellpose cyto3 0.94 / 0.74 / 0.83 / 0.79.
Speed
| Model | Network | Total per picture |
|---|---|---|
| StarDist versatile_fluo | 0.29 s | 0.6 s |
| InstanSeg brightfield_nuclei | 3.1 s | 3.4 s |
| Cellpose nuclei (automatic diameter, three network passes) | 23 s | 25 s |
| Cellpose cyto3 (automatic diameter, three network passes) | 39 s | 41 s |
These are median times over the 20 parity pictures (mostly 520 × 696 pixels), using ONNX Runtime Web with WebAssembly and 4 threads in Node on an Apple M2 laptop. The total includes the steps before and after the network. During the cyto3 run another job was using the processor, so its times are probably a bit too high. If you type in the typical cell diameter, Cellpose needs only one network pass instead of three. We have not finished timing inside the browser (WebGPU) yet.
Counting cells on real nuclei pictures
Three BBBC039 test pictures come with Mira as examples. We ran "Count cells" on them with its default threshold method (Otsu threshold and watershed splitting). Cells touching the edge were kept so the count can be compared with the expert masks.
| Picture | Expert count | Mira (threshold method) | Difference |
|---|---|---|---|
| Sparse (IXMtest_I23_s5) | 36 | 34 | −6 % |
| Typical (IXMtest_C23_s2) | 119 | 107 | −10 % |
| Crowded (IXMtest_I12_s1) | 202 | 182 | −10 % |
The threshold method joins some nuclei that touch each other, so it counts a little too low. For crowded pictures, "Find cells with AI" works better (StarDist reaches F1 0.95 on this test set). These counts are checked every time the tests run, in mira/src/tasks/realExamples.test.ts.
Scratch / wound area compared with expert masks (BBBC019)
We compared the gap that Mira finds (with default settings) with the expert gap, which is 100 % minus the cell area in the hand-drawn mask. We did this for all 66 pictures that have a mask.
| Version | HEK293 (12): mean |Δ gap| / mean IoU | SN15 (54): mean |Δ gap| / mean IoU |
|---|---|---|
| Before: Otsu cutoff on the local texture | 2.6 points / 0.87 | 28.7 points / 0.49 |
| Now: Otsu limited to 5 × the noise level, with over-exposed areas left out | 4.0 points / 0.86 | 2.9 points / 0.76 |
The old method did badly on wounds that were almost closed. In one case it reported an 80 % gap where the experts saw 10 %. The reason is that Otsu always splits the histogram into two parts, so when there is hardly any gap, it splits the texture of the cells themselves. We tried 11 variants of the fix on these same pictures and picked the best one.
| Example picture in Mira | Expert gap | Mira |
|---|---|---|
| SN15 position 10, start | 37.2 % | 35.3 % |
| SN15 position 10, later | 11.7 % | 5.7 % |
| HEK293 picture | 33.6 % | 32.4 % |
For the SN15 pair, Mira gives 84 % closure while the experts give 69 %. So for late, almost closed wounds Mira still finds too little gap. Please look at the overlay on late time points and increase "Gap sensitivity" if the gap looks too small.
Stained area (DAB) compared with pathologists' masks
For the four nuclear stains, we compared Mira's DAB "% area" with the area of the positive nuclei that the pathologists marked (25 pictures per stain). Mira first separates the brown DAB colour from the blue haematoxylin (colour deconvolution) and then applies a threshold.
| Cutoff on the DAB channel (optical density): mean |Δ area| in points / IoU | CDK4 | Cyclin D1 | P53 | Ki67 |
|---|---|---|---|---|
| Otsu only (before) | 17.8 / 0.35 | 14.9 / 0.40 | 11.7 / 0.36 | 6.7 / 0.63 |
| Otsu, but at least 0.2 OD (default now) | 11.5 / 0.35 | 4.1 / 0.42 | 3.8 / 0.42 | 6.7 / 0.63 |
| Fixed 0.3 OD (set by hand) | 21.6 / 0.31 | 2.6 / 0.60 | 3.1 / 0.54 | 2.5 / 0.69 |
Because Otsu always splits the histogram, pictures with no brown stain at all used to show some stained area. Adding a lower limit of 0.2 OD fixed this. We took this value from QuPath, where it is the usual DAB positivity level, so it was not fitted to this data. For purely nuclear stains such as Ki67, a fixed cutoff of 0.3 OD comes closer to the pathologists. CDK4 also stains the cytoplasm, which the masks do not include, so "% area" stays higher than the positive-nucleus area for this stain.
Example pictures in Mira (the picture with the median positive area for each stain): Ki67 5.5 % stained compared with 15.8 % positive-nucleus area (too low), and P53 6.9 % compared with 8.1 %. Note that "% area" is not the same as a Ki67 index (positive nuclei divided by all nuclei). Mira cannot count positive nuclei yet.
Limits
- The wound and stained-area settings were tuned on the same data sets we scored them on. We have not tested them on an independent set yet.
- For InstanSeg we only measured parity with the original. Its accuracy on real bright-field tissue is still unknown.
- "Click to fix" with micro-SAM is not in Mira, because we could not show that the browser version matches the original.
- Speed was measured in Node with WebAssembly on one laptop. Browsers, WebGPU and other computers will give different times.
- Cellpose's training data has a CC BY-NC licence. Mira shows this before the model is downloaded (fine for academic research, but check before any commercial use).
- Colony counting and western-blot measurements have not been tested on labelled public data yet.
How to repeat our tests
- In
mira/, runnpx vitest run. This runs the 109 unit tests, including the example pictures (src/tasks/realExamples.test.ts). - Wound area: download BBBC019v2 and run
MIRA_BBBC019=<folder> npx vitest run src/tasks/woundValidation.test.ts. - Stained area: download the IHC TMA data set and run
MIRA_IHC=<folder> npx vitest run src/tasks/ihcValidation.test.ts. AddMIRA_IHC_FLOORS=0,0.2to compare cutoffs. - AI parity: make reference results with StarDist 0.9.2, Cellpose 3.1.1.2 and InstanSeg, then run
MIRA_AI_REF=<folder> MIRA_AI_N=20 npx vitest run src/ai/validation/e2e.parity.test.ts.
The full report is in mira/docs/AI-VALIDATION.md, and the model licences are in public/models/THIRD-PARTY-NOTICES.txt.