ImagingCell Imaging

Microscopy Image Analysis API: A Weekend Tutorial for Biologists

Tutorial for running cell segmentation on microscopy images via API. Upload, segment, analyze — all without installing CUDA.

SciRouter Team
April 11, 2026
11 min read

This tutorial is for biologists who know their way around a pipette and a microscope but have limited coding experience. By the end of one weekend you will have a working Python script that takes a microscopy image, sends it to a hosted segmentation API, and produces a clean CSV of cell counts and per-cell intensity statistics. No GPU, no heavy installation, no deep-learning background required.

The whole thing runs against the SciRouter imaging API, which hosts Cellpose-SAM and related segmentation models on managed GPUs. You call it from Python, which you can install on a MacBook or Windows laptop in about ten minutes.

Note
Prerequisites: Python 3.10 or newer, a text editor you are comfortable with (VS Code is a safe choice), and a SciRouter API key. If you do not have one yet, the free tier is enough for the whole tutorial.

Step 1 — install the tools

Open a terminal and install the few libraries you will need:

bash
pip install requests numpy pillow pandas

That is the entire install. requests handles the API call, numpy handles arrays, pillow reads the image, and pandas writes the output CSV.

Step 2 — encode the image

The imaging endpoint takes images as base64-encoded strings in the JSON payload. Here is the helper to convert a PNG or TIFF file to a base64 string:

python
import base64
from pathlib import Path

def encode_image(path: str) -> str:
    data = Path(path).read_bytes()
    return base64.b64encode(data).decode("ascii")

Step 3 — call the segmentation endpoint

Now post the encoded image to the Cellpose endpoint with a couple of parameters:

python
import requests

API_URL = "https://scirouter-gateway-production.up.railway.app/v1/imaging/cellpose"
API_KEY = "sk-sci-your-api-key-here"

def segment(image_path: str, model: str = "cyto3", diameter: int = 30):
    payload = {
        "model": model,
        "diameter": diameter,
        "image_base64": encode_image(image_path),
        "return_masks": True,
        "return_stats": True,
    }
    headers = {"Authorization": f"Bearer {API_KEY}"}
    r = requests.post(API_URL, json=payload, headers=headers, timeout=300)
    r.raise_for_status()
    return r.json()

The response contains a masks array (a 2D array of instance labels, one label per cell) and a stats list with per-cell area, centroid, and intensity values.

Step 4 — count cells

Cell counting is the simplest thing you can do with the output:

python
import numpy as np

def count_cells(result: dict) -> int:
    masks = np.array(result["masks"])
    # mask value 0 is background, every other value is one cell
    return int(masks.max())

result = segment("field_01.tif", diameter=30)
print(f"cells detected: {count_cells(result)}")

Step 5 — build a CSV of per-cell metrics

The per-cell stats in the response map neatly into a pandas DataFrame. Write it out as a CSV and you have something you can open in Excel, R, or GraphPad:

python
import pandas as pd

def stats_to_csv(result: dict, out_path: str):
    df = pd.DataFrame(result["stats"])
    df.to_csv(out_path, index=False)
    print(f"wrote {len(df)} rows to {out_path}")

stats_to_csv(result, "field_01_stats.csv")

The CSV columns typically include cell id, centroid x and y, area in pixels, perimeter, mean intensity, and a few shape descriptors. The exact list depends on the endpoint version — check the Cellpose tool page for the current schema.

Step 6 — process a whole folder

Looping over a directory lets you batch-process an entire experiment:

python
from pathlib import Path

def process_folder(folder: str, out_dir: str):
    Path(out_dir).mkdir(parents=True, exist_ok=True)
    summary = []
    for img_path in sorted(Path(folder).glob("*.tif")):
        try:
            result = segment(str(img_path))
            n_cells = count_cells(result)
            out_csv = Path(out_dir) / f"{img_path.stem}_stats.csv"
            stats_to_csv(result, str(out_csv))
            summary.append({"image": img_path.name, "n_cells": n_cells})
            print(f"{img_path.name}: {n_cells} cells")
        except Exception as e:
            print(f"{img_path.name}: FAILED — {e}")
    pd.DataFrame(summary).to_csv(Path(out_dir) / "summary.csv", index=False)

process_folder("raw_images/", "results/")

This prints a per-image count, writes a CSV per image, and drops a summary CSV at the top. For a typical 96-well plate with one image per well, the whole batch runs in a few minutes.

Choosing the right parameters

Two parameters matter most:

  • model — pick cyto3 for whole-cell segmentation, nuclei for DAPI or Hoechst, ortissuenet for multiplexed tissue imaging.
  • diameter — the approximate cell size in pixels. Measure once on a representative image and use that value for the whole experiment.
Warning
If your cell counts look wildly wrong across the board, the diameter is almost certainly off. Re-measure before changing anything else.

Visual sanity check

Before trusting the batch output, overlay the masks on one image and eyeball the result:

python
from PIL import Image
import numpy as np

def overlay_masks(image_path: str, result: dict, out_path: str):
    img = np.array(Image.open(image_path).convert("RGB"))
    masks = np.array(result["masks"])
    # tint detected cells red
    overlay = img.copy()
    overlay[masks > 0] = [255, 0, 0]
    blended = (0.6 * img + 0.4 * overlay).astype(np.uint8)
    Image.fromarray(blended).save(out_path)

overlay_masks("field_01.tif", result, "field_01_overlay.png")

Troubleshooting

Masks come back mostly empty

Your diameter is probably too small, so the model rejects anything larger. Double the diameter and try again.

Cells merged into one big blob

The diameter is probably too large. Halve it and try again.

Request times out

Your image is too large. Downsample to 1024x1024 or tile it manually, then segment each tile and stitch.

When to switch to the workspace UI

Scripting is great for batches. For parameter tuning on a new dataset, the browser imaging workspace is faster. Drag and drop an image, drag sliders for diameter and thresholds, and see masks update in real time. Once you find parameters that work, copy them into your script.

Bottom line

In about a weekend, a biologist with light Python skills can go from raw microscopy images to clean per-cell statistics, with reproducible code and no local GPU. The hosted API handles the hard parts — model weights, GPU scheduling, mask post-processing — and you focus on your biology.

Open the imaging workspace →

Frequently Asked Questions

Do I need to know machine learning to use this API?

No. The whole point of the hosted endpoint is that you do not need to understand the underlying model. If you can write a for loop and make an HTTPS request, you can use it.

Does the API store my images?

Input images are used only for the lifetime of the request. SciRouter does not keep copies of the image or the returned masks beyond the response.

How do I handle multi-channel images?

Send one channel as the nuclear or primary input and another as the cytoplasmic or membrane input. The Cellpose API accepts a channels parameter that tells the model which channel plays which role. See the Cellpose tool page for the exact schema.

What file formats are supported?

PNG, JPEG, and TIFF. For TIFF, the first Z and first channel are used by default. For multi-channel or multi-Z stacks, split them client-side with tifffile or Pillow before uploading.

What is a reasonable image size?

Up to about 2048x2048 in a single request. Larger fields should be tiled and processed in chunks. The imaging workspace does this automatically for you.

How fast is it?

A 1024x1024 image typically returns masks in under 10 seconds on a warm endpoint, including upload and download time over a broadband connection.

Try this yourself

500 free credits. No credit card required.