Video Annotation¶
Define spatial environments and regions by annotating video frames directly using an interactive napari interface.
Overview¶
The video annotation workflow allows you to:
- Draw environment boundaries from video frames (e.g., arena walls)
- Define holes/obstacles within the environment (excluded areas)
- Create named regions for behavioral analysis (e.g., reward zones, nest areas)
- Draw named walls that block movement, such as a divider or a barrier
- Place named landmarks: reward ports, cue cards, monitors and objects
- Apply calibration to convert pixel coordinates to real-world units (cm)
- Save an editable project and resume from its embedded reference frame
- Suggest an outline from inside/outside clicks, then correct its preview
This is particularly useful when you have tracking data and want to define the spatial structure from the same video used for tracking.
Quick Start¶
from neurospatial.annotation import annotate_video
# Launch interactive annotation
result = annotate_video(
"experiment.mp4",
bin_size=2.0, # pixels; calibrating in the editor converts it to cm (see below)
project_path="arena.annotation.npz",
)
# Access results
env = result.environment # Discretized Environment, holding all of the below
regions = result.regions # Named Regions
walls = result.walls # Named walls, {name: LineString}
landmarks = result.landmarks # Named Landmarks
calibration = result.calibration # Pixel-to-cm calibration used, or None
env.plot() # Check the result against the video frame
Complete Workflow¶
Step 1: Launch Annotation¶
from neurospatial.annotation import annotate_video
# Basic annotation (pixel coordinates)
result = annotate_video(
"experiment.mp4",
frame_index=0, # Which frame to display
bin_size=2.0, # Grid resolution for environment
mode="both", # Annotate boundary + regions
)
Step 2: Draw Annotations¶
When the napari viewer opens:
- Draw the environment boundary (cyan polygon):
- Click points to define vertices
- Press
Enterto complete the polygon -
This defines the spatial extent of your environment
-
Press
Mto cycle to hole mode (red polygon): - Draw any obstacles or excluded areas inside the boundary
-
Holes are subtracted from the environment
-
Press
Magain for region mode (yellow polygon): - Enter a name for each region before drawing
-
Draw polygons for reward zones, nest areas, etc.
-
Press
Magain for wall mode (magenta path): - The tool switches to paths: click along the wall, press
Enterto finish - Name each wall, such as
divider; analyses that report blocked connections name the wall that closes them -
A wall is clipped to the boundary, so it may start on or just past the boundary's edge
-
Press
Magain for landmark mode (orange): - Name the landmark, then choose the napari tool by what it is: an ellipse around a reward port or cue marks a point at its center, a path along a monitor or a card marks a line, and a polygon or rectangle around an object marks its area
-
Landmarks may lie outside the boundary, as a cue on the room wall does
-
Click "Done" to validate the annotations and return results
Done validates the complete result before closing the viewer. If a boundary,
region, wall or landmark is invalid, the viewer stays open and explains what
to correct. Incomplete shapes are kept for correction as well. Edit the affected
vertices, then click Done again. Escape keeps napari's normal finish/cancel
behavior while drawing; it does not finish the annotation session.
Save Project saves work in progress without requiring a valid final boundary. Use it before taking a break, even if a polygon is unfinished.
Step 3: Use the Results¶
# The environment is ready to use
print(f"Environment has {env.n_bins} bins")
# Access regions
for name, region in result.regions.items():
print(f"Region '{name}': {region.kind}")
# Walls and landmarks are already part of the environment
print(env.arena.wall_names, list(env.landmarks))
# Use with tracking data
bin_indices = env.bin_sequence(times, positions)
Keyboard Shortcuts¶
| Key | Action |
|---|---|
M |
Cycle annotation mode (environment → hole → region → wall → landmark) |
E |
Environment mode |
R |
Region mode |
3 |
Move shape mode |
4 |
Edit vertices mode |
Delete |
Remove selected shape |
Escape |
Finish/cancel the current napari drawing operation |
Ctrl/Cmd + S |
Save Project |
Ctrl/Cmd + Z |
Undo (a brush stroke while painting an outline) |
| Platform redo shortcut | Redo (typically Ctrl + Y or Cmd + Shift + Z) |
The dock also has Undo and Redo buttons. Accepted assisted outlines and calibration changes participate in the same history as hand-drawn shapes.
Save and Resume a Project¶
Pass project_path= to keep an editable draft alongside the exported analysis
result. A project contains the RGB reference frame, pixel coordinates and names
of shapes, their roles and hole associations, calibration, and output settings.
It can retain incomplete or invalid geometry for later correction.
from neurospatial.annotation import annotate_project, annotate_video
result = annotate_video(
"experiment.mp4",
bin_size=2.0,
project_path="arena.annotation.npz",
autosave_interval=30.0,
)
# Resume later, using the reference frame stored in the project.
result = annotate_project(
"arena.annotation.npz",
project_path="arena.annotation.npz",
)
Use Save Project or Ctrl/Cmd + S for an immediate draft save. If no
path has been chosen, Save Project asks for one. Autosave runs every 30 seconds
once a destination is available; set autosave_interval=None to disable it.
Draft files are written atomically, so a failed save does not replace the
previous file. A resumed project uses its embedded image even if the original
video has moved. Click Done when the draft is ready to export an
AnnotationResult.
Done and the window close button both require valid output. Saving an invalid draft keeps the editor open; it does not export a partial analysis result. If the result can be saved but has problems worth checking, such as a region extending outside the environment, a Check Annotations dialog lists them before the editor closes: choose Keep Editing to fix them, or Return Results to continue.
Start annotate_video with a new project path; if the file already exists, use
annotate_project to resume it, or pass overwrite=True to start over. When
resuming a path, draft saves default to that same file; set project_path= to
save to another destination. Like other neurospatial writers,
AnnotationProject.save refuses an existing file unless overwrite=True.
You can also inspect or prepare drafts without launching a viewer. The example below uses a synthetic frame; replace it with your reference RGB image and pixel vertices in your own work.
from pathlib import Path
from tempfile import TemporaryDirectory
import numpy as np
from neurospatial.annotation import AnnotationProject, AnnotationShape
frame = np.zeros((24, 32, 3), dtype=np.uint8)
boundary = AnnotationShape(
np.array([[2, 2], [29, 2], [29, 21], [2, 21]], dtype=float),
role="environment",
name="arena",
)
draft = AnnotationProject(frame, shapes=[boundary], bin_size=2.0)
with TemporaryDirectory() as directory:
path = Path(directory) / "arena.annotation.npz"
draft.save(path)
restored = AnnotationProject.load(path)
assert np.array_equal(restored.frame, frame)
assert restored.shapes[0].identifier == boundary.identifier
annotate_project also accepts an AnnotationProject object directly. Project
coordinates stay in pixels; calibration is applied when producing the final
environment, regions, walls and landmarks.
Assisted Outlines¶
Try the Sample Arena¶
From a checkout with the animation dependencies installed, this launcher opens
a synthetic arena with an obstacle and reward area. Run from the checkout
containing examples/try_annotation.py; if using a separate worktree, change
into that worktree first:
Click Mark points, then left-click the tan floor. An outline appears
automatically; add inside/outside clicks to refine it, then Accept outline.
Use Paint / erase for manual corrections. To compare providers on the
same frame, launch again with --provider sam2 after the optional setup below.
--provider cpu selects color region growing, which runs on the CPU.
--provider sam2 selects the neural segmentation model, which runs on CUDA,
then Apple silicon, then the CPU, whichever is available first; --device
chooses one.
Add --image /path/to/frame.png or --video /path/to/movie.mp4 --frame-index 100
to use your own recording. --save-project arena.annotation.npz enables saves
and autosave to a new project file. Add --check with either provider to run a
sample prediction without opening a viewer.
Select and Correct an Outline¶
Open Project and Assistance and use Assisted outline to suggest a boundary or region from clicks. Choose the provider for your video: the default CPU region grower works when the floor color differs clearly from its surroundings; SAM 2 (optional, below) handles dark, low-contrast or multi-arm scenes.
- Under Accept as, choose environment, hole or region and type a name; these mirror Annotation Type and Next Shape Name in Annotation Controls.
- Prompt the outline:
- SAM 2: click Draw box and drag a box closely around the arena or region; the box alone produces an outline. A box is the most reliable SAM prompt: on a dark six-arm maze, a box found five or six of the six arm ends across six frames of one session, while a single center click found two to five.
- Either provider: click Mark points, then left-click inside the desired area, adding an inside point in each disconnected area that should be included. Region growing needs an inside point; a box keeps its growth inside the box.
- Right-click to add outside points (an orange ×; inside points are green dots). Without a convenient right button, set Left-click adds to Outside. Alt-click near a point to remove it, or use Undo last point. To move or select several points, use napari's point selection tool; Delete selected removes selected prompts. Click Mark points again to resume clicking. Dragging still pans the image.
- The outline updates automatically after each change. Keep clicking to refine it; the point tool stays active. The default CPU provider keeps areas connected to inside points whose colors fall within Color tolerance. Increase the tolerance to include a wider range of colors; this also updates the preview.
- Polygon detail starts at 1.4 pixels to reduce jagged edges and the number of vertices. Set it to 0 for the exact pixel outline; increasing the tolerance simplifies it further. The cyan paths show the exact polygons that will be accepted, including every hole. This updates immediately without rerunning inference. The vertex count helps you balance detail against editing complexity.
- Optionally click Paint / erase to correct the mask with napari's painting tools. Painting foreground includes pixels; erasing excludes them. The polygon paths update after each stroke. Changing polygon detail preserves painted edits; further prompt or fragment-cleanup changes replace them.
- Click Accept outline to add editable polygon geometry. Its holes and disconnected components are retained. Use Discard to remove a preview, or Clear prompts before trying a different selection. Accepting an outline clears its prompts, including the box, so the next object starts with a fresh set.
Remove stray fragments is enabled by default. It keeps every connected foreground region containing an inside point, including holes within those regions, and removes disconnected pieces you have not marked. Add an inside point to any separate region you want to retain. The status shows how many fragments were removed. Turn the option off to see the original prediction; switching it updates the preview without rerunning the model. If none of your inside points fall within the predicted foreground, the preview is left intact and the status asks you to refine it. Cleanup does not fill holes or bridge gaps. Manual paint/erase corrections made afterward are retained when you accept.
The detail slider preserves holes and disconnected components; it does not fill gaps or join fragments. Returning it to zero restores the exact current mask outline. The accepted polygons and their pixel tolerance are retained by project save/load and Undo/Redo. Any separately configured export simplification still applies when producing the final result.
The box prompt and the region of interest are different rectangles. The box tells SAM what to outline and should fit the object closely. The region of interest only crops the image SAM sees, and can be generous.
For a small arena within a larger image, expand Region of interest, click Draw region of interest, and drag a rectangle around the inference area. SAM then uses that crop for its image embedding; no image resizing is introduced by the annotation tool. Keep every inside and outside point within the crop. Move/remove outlying points or enlarge the crop if prompted. Use Use full image to return to the full frame. An incomplete or deleted ROI requires a new rectangle or an explicit return to the full image. Changing the ROI resets SAM refinement and invalidates pending predictions. Accepted polygons always use original-frame coordinates, so cropping does not change calibration or committed annotations.
The outline is accepted with the role and name under Accept as. An environment boundary must be connected; if the preview has several components, refine it or accept those areas as named regions. Draw walls and landmarks directly with their existing drawing tools. Replacing an existing environment outline asks for confirmation.
Review the result against the arena walls and obstacles before clicking Done. Color similarity can include shadows or nearby objects, so the editable preview is part of the workflow. While a new prediction runs, the previous outline is dimmed and cannot be accepted. Rapid clicks are combined into the latest request; inference runs in the background. SAM 2 starts computing the frame's image features as soon as the panel opens, so the first prompt does not wait for them. For manual updates, turn off Update as I prompt, then click Update outline when ready. This works with both providers.
With SAM 2, adding an inside or outside click refines the last displayed model prediction using its previous low-resolution logits. A single click is ambiguous, so the first prediction asks SAM for several candidate masks and selects the highest-scoring one; several clicks, a box, or a refinement request a single mask, following Meta's guidance. Moving or removing an existing point starts a fresh prediction from the remaining prompts. Clear prompts, Discard, accepting an outline, and inference errors also reset refinement. Repeating Update outline with unchanged prompts reuses the same model result.
Refinement uses the model's raw prediction before fragment cleanup or manual
painting. Further clicks therefore replace manual preview edits. Temporary model
state is not saved in projects; resumed sessions start fresh. Accepted annotations
record the number of refinement steps in their metadata. Direct
SegmentAnythingProvider.predict(image, prompt) calls remain independent of
earlier prompts and only reuse image embeddings.
The same segmentation tools work without napari. Prompts use pixel (x, y)
coordinates, while masks use [row, column] indexing. Polygon boundaries follow
pixel edges: a pixel centered at (x, y) covers a unit square extending half a
pixel in each direction. The conversion preserves exact mask area, holes and
every component when simplify_tolerance=0.
import numpy as np
from neurospatial.annotation import (
RegionGrowProvider,
SegmentationPrompt,
mask_to_polygons,
)
image = np.zeros((12, 18, 3), dtype=np.uint8)
image[2:10, 5:15] = [180, 170, 140] # Arena surface
image[4:7, 8:11] = 0 # Obstacle inside it
prompt = SegmentationPrompt(
points=np.array([[6.0, 3.0]]),
labels=np.array([1]), # 1 = inside, 0 = outside
)
proposal = RegionGrowProvider(tolerance=0.1).predict(image, prompt)
polygons = mask_to_polygons(proposal.mask)
assert len(polygons) == 1
assert len(polygons[0].interiors) == 1
assert polygons[0].area == proposal.mask.sum()
SegmentationProposal owns a read-only boolean mask, an optional score, and
JSON provenance. A custom SegmentationProvider implements
predict(image, prompt) and returns a proposal. Pass it as
segmentation_provider= to annotate_video or annotate_project.
Optional SAM 2 Provider¶
SAM 2 is Meta's image segmentation model. Use it when the CPU region grower cannot separate the arena from its surroundings, as in dark or low-contrast video and multi-arm mazes. neurospatial supports SAM 2.1 Large only: the smaller SAM 2 models flood dark arenas with background. Setup takes four steps.
-
On a Mac, check that Python is an Apple-silicon build. PyTorch has no packages for Intel Python on macOS, and an Intel Python under Rosetta fails with a confusing "no matching platform tag" error:
If it prints
x86_64, create your environment from an arm64 Python, for example Homebrew's (/opt/homebrew/bin/python3) oruv python install. Intel Macs cannot run SAM 2 locally. -
Install PyTorch and SAM 2 into the environment that has neurospatial. With an NVIDIA GPU, first install PyTorch with CUDA using the selector at pytorch.org. Then install Meta's package from its repository; the PyPI package named
sam2is a different, unrelated project:pip install "sam-2 @ git+https://github.com/facebookresearch/sam2.git@2b90b9f5ceec907a1c18123530e92e794ad901a4"Without an NVIDIA GPU (on a Mac, or CPU only), put
SAM2_BUILD_CUDA=0beforepipto skip SAM 2's optional CUDA extension. -
Download the SAM 2.1 Large checkpoint (about 900 MB) once, to any folder:
-
Load it, then open the editor. Loading first reports any setup problem before the viewer opens. It takes about 5 s, and uses a CUDA GPU, then Apple silicon, then the CPU, whichever is available first. Each frame's image features then take about 0.2 s on Apple silicon against about 2 s on the CPU.
from neurospatial.annotation import SegmentAnythingProvider, annotate_video
provider = SegmentAnythingProvider.from_sam2("~/models/sam2.1_hiera_large.pt")
result = annotate_video(
"experiment.mp4",
bin_size=2.0,
project_path="arena.annotation.npz",
segmentation_provider=provider,
)
If something is missing, from_sam2 says what to do: the install command when
SAM 2 is not installed, the Large download link when the checkpoint is missing
or is a smaller model, and the arm64 check on an Intel Mac Python. Pass
device="cpu" if outlines on Apple silicon look wrong; Meta describes that
support as preliminary.
You can instead wrap an already constructed SAM2-style image predictor with
SegmentAnythingProvider(predictor). Give the adapter exclusive use of that
predictor: it caches embeddings for unchanged image content and selects the
highest-scoring mask.
Try SAM 2 with the demo (source checkout)¶
From a neurospatial source checkout, install SAM 2 into its virtual environment, put the checkpoint where the demo looks for it, and launch:
SAM2_BUILD_CUDA=0 uv pip install --python .venv/bin/python 'sam-2 @ git+https://github.com/facebookresearch/sam2.git@2b90b9f5ceec907a1c18123530e92e794ad901a4'
mkdir -p data/annotation_demo
curl --fail --location --output data/annotation_demo/sam2.1_hiera_large.pt \
https://dl.fbaipublicfiles.com/segment_anything_2/092824/sam2.1_hiera_large.pt
uv run --no-sync python examples/try_annotation.py --provider sam2
Keep --no-sync so uv retains these optional packages, which are not part of
the core dependency lockfile; the data/ checkpoint is ignored by Git. Use
--checkpoint /path/to/sam2.1_hiera_large.pt for a checkpoint elsewhere,
--device cpu to force the CPU, and --video /path/to/movie.mp4 --frame-index
100 to try your own recording. Without --provider sam2, the demo uses CPU
region growing and needs no checkpoint. Both providers start the polygon
preview at 1.4 pixels of simplification; set Polygon detail to 0
for an exact mask outline. The demo never downloads weights.
Design Credit and References¶
We credit CVAT, CVAT.ai, and its contributors for the interaction design that inspired our assisted outlining: inside/outside clicks, live mask-to-polygon previews with adjustable detail, and an optional inference region of interest. The following links pin the public source reviewed for this integration so it remains traceable as upstream develops:
- Prompting and ROI workflow: user interactions and polygon-detail controls.
- Annotation interaction controls: prompt requests, live previews, ROI submission, and contour selection.
- Polygon conversion and default settings: contour extraction and the accuracy-to-pixel-tolerance mapping.
- SAM 2 interactor in cvat-models: model prompting and image-embedding reuse.
CVAT's referenced default accuracy of 9 maps to 10 / 7 pixels (about 1.43);
our 1.4 px default rounds that to the slider's 0.1 px steps. Like CVAT, the
editor offers a box prompt alongside inside/outside clicks. Neurospatial's
polygon conversion preserves holes and every retained component, without adding
dilation; CVAT's polygon mode keeps the longest outer contour. Our subsequent
SAM 2 clicks use previous-logit refinement, which CVAT's SAM 2 interactor does
not. These choices mean the implementations can produce different outlines from
the same prompts; the references do not establish CVAT Online's deployed model
settings.
Track Graph Annotation¶
Use annotate_track_graph() to place nodes and connect them into a track.
Native edge deletion and node renaming participate in undo and redo. To change
an edge's geometry, move its endpoint nodes.
Preview 1D uses the same node coordinates, edge order and gaps as the returned
TrackGraphResult. Gaps are in centimeters with calibration and pixels without
it; the spacing control displays the unit. Automatic gaps follow the selected
edge order, and disconnected components are included in the result. Adding a
node keeps a manual edge order and gaps; adding or deleting edges, or deleting
nodes, returns to the automatic layout.
Saving or closing either editor validates the draft before the editor closes.
If it cannot be saved, a dialog says why: choose "Keep Editing" to fix it, or
"Discard and Close" to close without a result, which raises a RuntimeError.
Warnings about the result, such as an ignored boundary, open a Check
Annotations dialog before the editor closes: choose "Keep Editing" to fix them
or "Return Results" to close; the warnings are then issued to your script. If
the final draft save fails, for example because the disk is full, the result is
still returned and a warning says so; choose "Keep Editing" and use Save As…
if you need the draft.
Adding Calibration¶
Convert pixel coordinates to real-world units (e.g., centimeters):
The resulting environment records units="cm" when calibration is supplied,
or units="pixels" otherwise. bin_size and simplify_tolerance use those
same units.
Calibrate in the Editor¶
Expand Units and bin size at the top of Project and Assistance; its
button always shows the current bin size and, once an environment is drawn, about
how many bins the environment will have. Calibration is optional for drawing outlines; use it
when you need output in centimeters. Click Mark two scale endpoints and mark the
ends of an object of known length. Enter that length in centimeters, then click
Apply calibration. Lengths convert only when the output units change. A
pixel Output bin size is converted to cm, so the grid keeps its size in the
image: an 8 pixel bin at 0.3 cm per pixel becomes 2.4 cm. Applying a new
calibration to an already calibrated project keeps the cm value, because a bin
size chosen in cm is a physical choice. Enter a new value if you want a
different grid. Shapes stay aligned with the reference frame because editable
geometry remains in pixels. Clear calibration returns output units to
pixels and converts the bin size back. The export simplify_tolerance follows
the same rule. These changes can be undone and are saved in the project.
result.calibration holds the calibration the outputs use, including one made
in the editor, so pixel tracking can be put into the environment's
coordinates without a saved project:
Using Scale Bar¶
from neurospatial.ops import VideoCalibration, calibrate_from_scale_bar
# Two points on a known length in the video
point1_px = (100, 200) # Start of scale bar (pixels)
point2_px = (300, 200) # End of scale bar (pixels)
known_length_cm = 50.0 # Known length in cm
frame_size = (640, 480) # Video dimensions
transform = calibrate_from_scale_bar(
point1_px, point2_px, known_length_cm, frame_size_px=frame_size
)
calibration = VideoCalibration(transform, frame_size)
# Annotate with calibration
result = annotate_video(
"experiment.mp4",
calibration=calibration,
bin_size=2.0, # Now in cm!
)
Using Landmark Correspondences¶
import numpy as np
from neurospatial.ops import VideoCalibration, calibrate_from_landmarks
# Known correspondences: video pixels → environment cm
landmarks_px = np.array([
[50, 50], # Top-left corner in pixels
[590, 50], # Top-right corner in pixels
[590, 430], # Bottom-right corner in pixels
[50, 430], # Bottom-left corner in pixels
])
# Environment y points up, so the top of the image has the largest y
landmarks_cm = np.array([
[0, 80], # Top-left in cm
[100, 80], # Top-right in cm
[100, 0], # Bottom-right in cm
[0, 0], # Bottom-left in cm
])
transform = calibrate_from_landmarks(
landmarks_px, landmarks_cm, frame_size_px=(640, 480)
)
calibration = VideoCalibration(transform, (640, 480))
result = annotate_video(
"experiment.mp4",
calibration=calibration,
bin_size=2.0,
)
Simplifying Hand-Drawn Polygons¶
Hand-drawn polygons often have jagged edges. Use simplify_tolerance to smooth them:
result = annotate_video(
"experiment.mp4",
bin_size=2.0,
simplify_tolerance=1.0, # Douglas-Peucker tolerance in output units
)
Higher values produce smoother polygons but may lose detail.
Simplification does not repair self-intersections. Correct intersecting edges in the viewer before saving so that the intended area is unambiguous.
Annotation Modes¶
Control what to annotate with the mode parameter:
# Both boundary and regions (default)
result = annotate_video("video.mp4", mode="both", bin_size=2.0)
# Only environment boundary
result = annotate_video("video.mp4", mode="environment", bin_size=2.0)
# Only regions (no environment created)
result = annotate_video("video.mp4", mode="regions")
# Note: bin_size not required for regions-only mode
Handling Multiple Boundaries¶
If you accidentally draw multiple environment boundaries, control the behavior:
# Use the last drawn boundary (default)
result = annotate_video(
"video.mp4",
bin_size=2.0,
multiple_boundaries="last",
)
# Use the first drawn boundary
result = annotate_video(
"video.mp4",
bin_size=2.0,
multiple_boundaries="first",
)
# Raise error if multiple boundaries (strict mode)
result = annotate_video(
"video.mp4",
bin_size=2.0,
multiple_boundaries="error",
)
Editing Existing Regions¶
Provide existing regions to edit them:
Interior holes are retained when reopening either regions or an initial environment boundary. Each hole remains associated with its original polygon; a hole in a region does not remove that area from the environment.
Edit these rings directly, or draw new shapes for additional regions. A pasted copy of a polygon keeps its own holes and is independent of the original. A hole pasted without its polygon becomes a separate polygon. Polygons that are not valid, such as a self-intersecting outline from a LabelMe file, open for repair; saving reports the problem until it is fixed.
from neurospatial.regions import Regions, Region
from shapely.geometry import Polygon
# Create initial regions
initial = Regions([
Region("reward_zone", "polygon", Polygon([(10, 10), (20, 10), (20, 20), (10, 20)])),
])
# Edit them interactively
result = annotate_video(
"video.mp4",
initial_regions=initial,
bin_size=2.0,
)
Importing from External Tools¶
Import annotations created in other tools:
Both import adapters retain coordinate units in each region's
metadata["coord_system"]: "cm" with calibration and "pixels" without it.
This metadata is preserved when constructing an environment from the boundary.
LabelMe¶
from neurospatial.annotation import regions_from_labelme
# Without calibration (pixel coordinates)
regions = regions_from_labelme("annotations.json")
# With calibration (cm coordinates)
regions = regions_from_labelme("annotations.json", calibration=calibration)
Polygons, rectangles, circles and points become regions; an open line is
refused because it encloses no area. A label used more than once is numbered
label_0, label_1, ... in file order.
CVAT¶
from neurospatial.annotation import regions_from_cvat
regions = regions_from_cvat("cvat_export.xml", calibration=calibration)
Export with the "CVAT for images 1.1" format; video track exports are refused.
When the export annotates more than one frame, pass the frame to read, by its
image name or id: regions_from_cvat("cvat_export.xml", image="frame_000120.png",
calibration=calibration).
Region Types¶
Shapes are assigned region types that determine their purpose:
| Region type | Color | Purpose |
|---|---|---|
environment |
Cyan | Primary boundary defining spatial extent |
hole |
Red | Excluded areas within the boundary |
region |
Yellow | Named regions of interest |
wall |
Magenta | Named physical walls, drawn as paths |
landmark |
Orange | Named cues, monitors and objects: ellipse = point, path = line, polygon = area |
Access the region type in your code:
from neurospatial.annotation import RegionType
# Type hint for region-type parameters
def process_annotation(role: RegionType) -> None:
if role == "environment":
# Handle boundary
...
API Reference¶
annotate_video()annotate_project()AnnotationProjectAnnotationShapeRegionGrowProviderSegmentAnythingProviderAnnotationResultregions_from_labelme()regions_from_cvat()
Common Issues¶
"napari is not installed"¶
Install napari with:
Shapes don't appear¶
Ensure you're in polygon drawing mode (default). Press 3 to move shapes or 4 to edit vertices.
Calibration appears wrong¶
Check that:
- Y-axis convention matches your data (scientific data typically uses Y-up)
- Scale factor is correct (verify by measuring known distances)
- Landmark correspondences are accurate
Viewer closes unexpectedly¶
The viewer blocks until closed, including in IPython with %gui qt. If it
closes without saving, check for Python errors in the console.
See Also¶
- Regions: Managing regions programmatically
- Environments: Creating environments from other sources
- Alignment & Transforms: Coordinate transformations