Skip to content

Data Model

Data Entities

Root

The project's root. This is the entry point for the copick API. It allows access to information about the pickable objects and runs contained in the project.

Example Code - Print available objects and runs
"""Print all objects and runs in a copick project."""

import copick

# Initialize the root object from a configuration file
root = copick.from_file("path/to/config.json")

# List all available objects
obj_info = [(o.name, o.label) for o in root.pickable_objects]

print("Pickable objects in this project:")
for name, label in obj_info:
    print(f"  {name}: {label}")

# Execute a function on each run in the project
runs = root.runs

print("Runs in this project:")
for run in runs:
    print(f"Run: {run.name}")
    # Do something with the run

Refer to the API Reference for more information on the CopickRoot API.

Pickable Object

Objects are any entity that can be labeled inside a 3D image using points, meshes or dense segmentation masks. In most cases, these will be macromolecular complexes or other cellular structures, like membranes. They can also be more abstract entities like "contamination particles", "carbon edges", or "sample boundaries".

In the configuration file, each object is defined by a JSON object, that allows the user to specify the object's name, label, color, radius, and other properties.

Naming Conventions

Object names should never contain underscores!

Example Code - Read an object's density map.
"""Read a density map from an object's zarr-store into a numpy array."""

import copick
import numpy as np
import zarr

# Initialize the root object from a configuration file
root = copick.from_file("path/to/config.json")

# Get the object named 'proteasome'
proteasome = root.get_object("proteasome")

# Read the density map for the object from its zarr-store
zarr_array = zarr.open(proteasome.zarr())["0"]
density_map = np.array(zarr_array)

Refer to the API Reference for more information on the CopickObject API.

Example Object Definition

The following is an example of a pickable object definition in the configuration file:

{
    "name": "proteasome",
    "is_particle": true,
    "pdb_id": "3J9I",
    "emdb_id": "1234",
    "identifier": "GO:0001234",
    "label": 1,
    "color": [255, 0, 0, 255],
    "radius": 60,
    "map_threshold": 0.0418,
    "metadata": {
        "source": "experimental_data",
        "confidence": 0.95,
        "notes": "High-resolution structure"
    }
}

  • name: The name of the object, which should be unique within one project.
  • is_particle: A boolean indicating whether the object can be represented by point annotations. By default, all objects can be represented by mesh annotations or dense segmentations.

  • pdb_id: The PDB ID of the object, if available.

  • emdb_id: The EMDB ID of the object, if available.
  • identifier: An ontology/database identifier for the object, if available. Supported namespaces are GO, UniProtKB, CHEBI, PDB (note the dash separator, e.g. PDB-1BXN), UBERON, CL, and CDPO. When using the data portal, this field is matched exactly against the annotation object_id to find matching annotations in the data portal.
  • label: An integer that indicates which numeric label should be used in segmentations to represent this object.
  • color: An array of four integers that represent the RGBA color of the object when rendered in a 3D viewer.
  • radius: An integer that represents the radius of the object in angstroms. This is used to determine the size of the object when rendering it as a sphere in a 3D viewer.
  • map_threshold: A float that represents the threshold value to use when a density map is used to represent the object. This is used to determine the isosurface level to use when rendering the object as a mesh. Density maps are discovered by the copick API by looking for files with the same name as the object in the Objects directory of the project's root.
  • metadata: An optional dictionary that can contain arbitrary key-value pairs for storing additional custom information about the object. This field allows users to attach project-specific metadata such as confidence scores, data sources, or processing notes. The key copick is reserved for copick's own extensions of the object definition (see Filament Objects below).

Filament Objects

A filament is a continuous, typically helical or tubular assembly, such as a microtubule or an actin filament, that is annotated with ordered points along its axis. An object is declared a filament by a FilamentSpec stored in its metadata under metadata["copick"]["filament"]:

{
    "name": "microtubule",
    "is_particle": true,
    "label": 2,
    "radius": 120,
    "metadata": {
        "copick": {
            "filament": {"polar": true, "helical_rise_a": 9.4}
        }
    }
}
  • polar: Whether the structure has a polarity (true for microtubules and actin). Omitted if not stated.
  • helical_rise_a, helical_twist_deg: Rise per subunit in angstrom and twist per subunit in degrees. Both are descriptive; tools never use them as a default sampling distance.

A filament object must have is_particle: true, because filaments are annotated with points; its radius is the tube radius. Copick validates the declaration when it reads the configuration. Clients that do not know filaments read the object as an ordinary particle, and keep the declaration when they rewrite the configuration because it lives in metadata.

root.new_object(name="microtubule", is_particle=True, radius=120, filament={"polar": True})
root.get_object("microtubule").is_filament  # True
copick add object -c config.json --name microtubule --object-type filament --radius 120 --polar

Picks of a filament object follow the filament conventions: instance_id is the filament ID, points are ordered along each filament, and the transform's +Z axis is the filament axis.

Run

A run is a collection of data that is associated with a particular location on the sample. Run objects allow access to any 3D image data, segmentations, and annotations that are associated with a particular location on the sample. Images are stored in groups based on their voxel spacing, while tilt series, point annotations, mesh annotations, and dense segmentations are related to the run as a whole.

Example Code - List available segmentations for a run
"""Print all segmentations for a run in a copick project."""

import copick

# Initialize the root object from a configuration file
root = copick.from_file("path/to/config.json")

# Get the run named 'TS_001'
run = root.get_run("TS_001")

# List all available segmentations for the run
segmentations = run.segmentations
for segmentation in segmentations:
    print(f"Segmentation: {segmentation.name}")

Refer to the API Reference for more information on the CopickRun API.

Voxel Spacing

A voxel spacing groups together all tomograms of a particular resolution. Voxel spacings are rounded to the third decimal place.

Example Code - List available tomograms for a voxel spacing
"""Print the list of tomograms for a given voxel spacing."""

import copick

# Initialize the root object from a configuration file
root = copick.from_file("path/to/config.json")

# Get the run named 'TS_001'
run = root.get_run("TS_001")

# Get the voxel spacing with a resolution of 10 angstroms
voxel_spacing = run.get_voxel_spacing(10.000)

# List all available tomograms for the voxel spacing
tomograms = voxel_spacing.tomograms
for tomogram in tomograms:
    print(f"Tomogram: {tomogram.name}")

Refer to the API Reference for more information on the CopickVoxelSpacing API.

Image data

Tomogram

At each resolution, multiple tomograms can be stored. Tomograms are stored as OME-NGFF files, which are a zarr-based format that allows for efficient access to multiscale 3D image data. The filename of the zarr file allows relating the image to its reconstruction method or processing steps. Typical useful tomogram types are wbp, sirt, denoised, etc.

Example Code - Read a tomogram into a numpy array
"""Read a tomogram from a zarr-store into a numpy array."""

import copick
import numpy as np
import zarr

# Initialize the root object from a configuration file
root = copick.from_file("path/to/config.json")

# Get the run named 'TS_001'
run = root.get_run("TS_001")

# Get the voxel spacing with a resolution of 10 angstroms
voxel_spacing = run.get_voxel_spacing(10.000)

# Get the tomogram named 'wbp'
tomogram = voxel_spacing.get_tomogram("wbp")

# Read the tomogram from its zarr-store
# Scale "0" is the unbinned tomogram
zarr_array = zarr.open(tomogram.zarr())["0"]
tomogram_data = np.array(zarr_array)

# Scale "1" is the tomogram binned by 2
zarr_array_bin2 = zarr.open(tomogram.zarr())["1"]
tomogram_data_bin2 = np.array(zarr_array_bin2)

Refer to the API Reference for more information on the CopickTomogram API.

Example tomogram file name

Tomograms are named according to the following pattern:

wbp.zarr

The wbp part of the filename is the type of tomogram. This could be wbp, sirt, denoised, etc.

Feature Map

Feature maps are stored as OME-NGFF files with relation to the tomogram they are computed from. Feature maps are stored as zarr files, and can be used to store any type of data that is computed from a tomogram. They may be useful for interactive segmentation tasks.

Example Code - Read a feature map into a numpy array
"""Read a feature map from a zarr-store into a numpy array."""

import copick
import numpy as np
import zarr

# Initialize the root object from a configuration file
root = copick.from_file("path/to/config.json")

# Get the run named 'TS_001'
run = root.get_run("TS_001")

# Get the voxel spacing with a resolution of 10 angstroms
voxel_spacing = run.get_voxel_spacing(10.000)

# Get the tomogram named 'wbp'
tomogram = voxel_spacing.get_tomogram("wbp")

# Get the feature map named 'sobel'
feature_map = tomogram.get_features("sobel")

# Read the feature map from its zarr-store
zarr_array = zarr.open(feature_map.zarr())["0"]
feature_map_data = np.array(zarr_array)

Refer to the API Reference for more information on the CopickFeatures API.

Example feature map file name

Feature maps are named according to the following pattern:

wbp_density_features.zarr

The wbp part of the filename is the type of tomogram that the feature map was computed from. The sobel part of the filename is the type of feature that the feature map represents. This could be density, gradient, curvature, etc.

Tilt Series

A tilt series is the stack of 2D projection images a run's tomograms are reconstructed from. Each run can hold several tilt series, identified by their type (e.g. raw, aligned, dose-weighted, denoised). They are stored as OME-NGFF images with the axes z, y, x, where z is the section (image) index in the stack, using the same layout as the CryoET Data Portal:

  • chunks of one image ((1, 1024, 1024)) at every level;
  • three levels, binned by 2 along y and x only (local mean); sections are never binned;
  • the scale of level i is [pixel_size, pixel_size·2^i, pixel_size·2^i] in Angstrom.

The metadata of a tilt series is stored in the copick attributes of its zarr group, in the shape of the CETS data models (a CETS TiltSeries with one TiltImage per section, an AcquisitionSession and an Instrument, next to copick extensions). Per image it records the section, the nominal (stage) tilt angle, the acquisition order (starting at 0), the dose received before the image (pre-exposure, in e/Ų), the dose and exposure time of the image, and its CTF fit. CTF fits are normalized to the CETS convention: defocus_u ≥ defocus_v in Angstrom (underfocus positive), the astigmatism angle in degrees within [0, 180), and the phase shift in degrees. The metadata is read only when needed, so listing a run's tilt series does not open their stores.

Example Code - Read a tilt series and its metadata
"""Read a tilt series and its per-image metadata."""

import copick

# Initialize the root object from a configuration file
root = copick.from_file("path/to/config.json")

# Get the tilt series of type 'raw' of the run named 'TS_001'
run = root.get_run("TS_001")
tilt_series = run.get_tilt_series("raw")

# All images (section, y, x) at full size, and one image binned by 4
images = tilt_series.numpy()
image_bin4 = tilt_series.section(0, zarr_group="2")

# Per-image metadata: stage angle, acquisition order, pre-exposure dose and CTF
metadata = tilt_series.metadata
print(metadata.pixel_size, metadata.ctf_status)
for image in metadata.images:
    print(image.section, image.nominal_tilt_angle, image.acquisition_order, image.accumulated_dose, image.ctf)

Refer to the API Reference for more information on the CopickTiltSeries API.

Example tilt series file name

Tilt series are named according to the following pattern:

TiltSeries/raw.zarr

The raw part of the filename is the type of the tilt series. Types must not contain @.

A CryoET Data Portal project serves the portal's tilt series (type raw, or aligned for stacks of aligned images) read-only, with metadata built from the portal's per-image records; see Data Portal.

Alignments

An alignment holds, for one tilt series, the rigid projection of each aligned image: its refined tilt angle, tilt about x, in-plane rotation and shift (see Geometry), the sections it leaves out (e.g. dark images), and the reference volume the parameters refer to. A tilt series can have several alignments (e.g. from different programs, or global and local ones). Alignments are stored as JSON files in the Alignments directory of the run, holding a CETS Alignment (one ProjectionAlignment per aligned section) next to copick extensions such as the alignment type (GLOBAL or LOCAL; of a local alignment only the rigid part is stored), the method, the source format and a status:

  • author: an alignment from its author;
  • portal_default: a CryoET Data Portal placeholder, created when a deposition holds no alignment;
  • no_per_section: another alignment without per-section parameters;
  • inconsistent: an alignment that does not fit its tilt series.

tilt_series.alignments lists all alignments of a tilt series, best first (preferred, then the CryoET Data Portal's standard ones, local before global ones); tilt_series.valid_alignments only those from their authors with per-section parameters, and tilt_series.default_alignment the first of these.

Tilt series, alignments and CTF fits are imported from, and exported to, the files of other tools:

# A SerialEM stack and its mdoc; an AreTomo3 alignment with its CTF fits, linked to the AreTomo3 tomogram
copick add tiltseries TS_01.mrc --run TS_01 --voltage 300 --cs 2.7 --amplitude-contrast 0.07 -c config.json
copick add tomogram TS_01_Vol.mrc --run TS_01 --tomo-type aretomo3 -c config.json
copick add alignment TS_01.aln --format aretomo3 --run TS_01 --ctf TS_01_CTF.txt \
    --reference-tomogram aretomo3@7.7 -c config.json

# The default alignment of every run, for RELION 5 (or --output-format warp, aretomo3, imod, cdp)
copick export alignment --alignment-uri "*@raw?default=true" --output-format relion --include-ctf \
    --output-dir relion -c config.json
copick export tiltseries --tiltseries-uri raw --output-format mrc --angles aretomo3-tlt --output-dir out -c config.json

The formats are AreTomo3 (.aln, _TLT.txt, _CTF.txt), Warp tilt-series XML, RELION 5 tomograms.star, IMOD (.xf, .tlt, .xtilt, tilt.com), CTFFIND4 and SerialEM .mdoc. How their rows map to sections and what each can hold is described in Geometry.

copick export cets (or copick.ops.cets_export.export_cets) assembles these per-entity CETS blocks into one CETS Dataset document, a Region per run, with identifiers prefixed by the run name; what CETS has no field for (copick's extensions, alignment names and reference volumes, tomogram links) goes to a companion file next to it.

A tomogram records the alignment it was reconstructed with in the copick attributes of its zarr group (a CETS Tomogram with its tilt_series_id, and the alignment's name), set with tomogram.set_alignment(...) and read as tomogram.alignment and tomogram.tilt_series. A read-only tomogram (e.g. in a static project) cannot record it; the alignment names the tomogram instead ("{tomo_type}@{voxel_size}", in its links.tomograms). Tomograms of the CryoET Data Portal report the alignment the portal links them to. Rewriting a tomogram's data keeps its link.

Example Code - Navigate between tomograms, alignments and tilt series
"""Find the alignment and tilt series of a tomogram, and the alignments of a tilt series."""

import copick

root = copick.from_file("path/to/config.json")
run = root.get_run("TS_001")

# From a tomogram to the alignment it was reconstructed with, and on to the tilt series
tomogram = run.get_voxel_spacing(10.0).get_tomograms("wbp")[0]
alignment = tomogram.alignment
tilt_series = tomogram.tilt_series

# From a tilt series to its alignments: all of them, or only those from their authors (no placeholders), best first
tilt_series = run.get_tilt_series("raw")
for alignment in tilt_series.alignments:
    print(alignment.name, alignment.status, alignment.alignment_type, alignment.is_valid)
best = tilt_series.default_alignment

# Per-section parameters: refined tilt angle, in-plane rotation and shift (CETS frame, Angstrom)
for section in best.sections:
    print(section.section, section.tilt_angle, section.in_plane_rotation, section.shift_x, section.shift_y)

Refer to the API Reference for more information on the CopickAlignment API.

Annotation data

Point Annotations

Point annotations are stored as JSON files in the Picks directory of the run. Each file contains a list of points in angstrom coordinates that represent the location of a particular object in the tomogram. The filename of the JSON file allows relating the points to the user or tool that created them, as well as the object that they represent.

Naming Conventions

user_ids, session_ids, and object names should never contain underscores!

Example Code - Read point annotations from copick
"""Read points from a CopickPicks object."""

import copick
import numpy as np

# Initialize the root object from a configuration file
root = copick.from_file("path/to/config.json")

# Get the first run in the project
run = root.runs[0]

# Get 'proteasome' picks of user 'alice'
picks = run.get_picks(object_name="proteasome", user_id="alice")[0]

# Get the points from the picks
point_arr = np.ndarray((len(picks.points), 3))
for idx, pt in enumerate(picks.points):
    point_arr[idx, :] = [pt.location.x, pt.location.y, pt.location.z]

Refer to the API Reference for more information on the CopickPicks API.

Example point file name

Point files are named according to the following pattern:

good.picker_0_proteasome.json

The good.picker part of the filename is the user or tool that created the points. The 0 part of the filename is the session id of the user or tool that created the points. The proteasome part of the filename is the name of the object that the points represent.

Each point has a location (angstrom), a transformation (a 4×4 matrix from the object's frame to the tomogram, whose translation is a shift added to the location), an instance_id (default 0) and a score (default 1.0). For filament objects, instance_id is the filament ID and the points are ordered along each filament; see Pick Geometry.

positions, transforms = picks.numpy()            # locations and 4x4 transforms
centres = picks.full_positions()                 # location + transform translation
picks.from_numpy(positions, transforms, instance_ids=ids, scores=scores)
ids, scores = picks.instance_ids(), picks.scores()

Mesh Annotations

Mesh annotations are stored as glb files in the Meshes directory of the run. Each file contains a 3D mesh, with vertices in angstrom coordinates, that represents the shape of a particular object in the tomogram. The filename of the glb file allows relating the mesh to the user or tool that created it, as well as the object that it represents.

Naming Conventions

user_ids, session_ids, and object names should never contain underscores!

Example Code - Read mesh annotations and visualize them in 3D
"""Read a mesh from a CopickMesh object and display it."""

import copick

# Initialize the root object from a configuration file
root = copick.from_file("path/to/config.json")

# Get the first run in the project
run = root.runs[0]

# Get a membrane mesh from user 'bob'
mesh = run.get_meshes(object_name="membrane", user_id="bob")[0]

# Show the mesh
mesh.mesh.show()

Refer to the API Reference for more information on the CopickMesh API.

Example mesh file name

Mesh files are named according to the following pattern:

good.picker_0_proteasome.glb

The good.picker part of the filename is the user or tool that created the mesh. The 0 part of the filename is the session id of the user or tool that created the mesh. The proteasome part of the filename is the name of the object that the mesh represents.

Filaments

Traced filaments are stored as JSON files in the Filaments directory of the run, named like point annotations ([user_id]_[session_id]_[object_name].json). Each file holds the filaments of one object, each an ordered centreline (polyline) in angstrom coordinates with its own instance_id:

{
    "pickable_object_name": "microtubule",
    "user_id": "tracer",
    "session_id": "1",
    "voxel_spacing": 10.0,
    "unit": "angstrom",
    "version": 1,
    "filaments": [
        {"instance_id": 1, "points": [[100.0, 200.0, 50.0], [110.0, 200.5, 50.2], ...],
         "polarity_known": false, "score": 1.0, "radius": 120.0, "metadata": {}}
    ]
}
  • instance_id is at least 1 and unique within the file; picks sampled from a filament use it as their instance_id.
  • points are ordered along the filament, at least two, and should be no further apart than the voxel spacing the filament was traced at, so that linear interpolation follows the centreline.
  • polarity_known says whether the point order follows the structure's polarity (meaningful for objects whose filament spec has polar: true). Where it is true, the points run from the minus end to the plus end (microtubules), or from the pointed end to the barbed end (actin), so the +Z axis of a pick sampled in point order points toward the plus (barbed) end.
  • version is the version of this file format.

RELION export (copick export picks --output-format star) takes each filament pick's polarity from the filament with the same instance_id: in the Filaments under the picks' own URI by default, or in those named by --filaments-uri when the picks were sampled into another session. rlnAnglePsiFlipRatio is 0 for picks of filaments with polarity_known and 0.5 for the others. A pick whose filament is missing from those Filaments is an error, as is a --filaments-uri that matches no Filaments in a run. Picks without any Filaments (none under their own URI, and no --filaments-uri) all get 0.5.

The cryoET Data Portal has no filament annotations, so data-portal projects keep filaments in their overlay; a self-contained Croissant project (Mode A) cannot hold them.

filaments = run.new_filaments(object_name="microtubule", user_id="tracer", session_id="1")
filaments.from_numpy([centreline_1, centreline_2], voxel_spacing=10.0)  # (M, 3) arrays in angstrom
for line in run.get_filaments(object_name="microtubule")[0].numpy():
    ...
Editable curves

A filament can also carry the curve its points were made from, so that an editor can reopen the clicked control points or a fitting program's spline instead of re-deriving them from the dense points. The curve is an optional curve object on a filament, beside points; points stay the centreline every reader uses, and are regenerated from the curve whenever it changes.

{"instance_id": 1,
 "points": [[100.0, 200.0, 50.0], "... regenerated from the curve ..."],
 "curve": {"kind": "catmull-rom", "alpha": 0.5, "step": 10.0,
           "control_points": [[100.0, 200.0, 50.0], [180.0, 230.0, 55.0], [260.0, 240.0, 70.0]]}}
Field Kinds Rule
kind all catmull-rom (passes through its control points; interactive tracing), linear, or bspline (a fitted B-spline, stored exactly). Other values are reserved and kept as they are.
control_points all Ångström, ordered like points, finite. catmull-rom/linear: at least 2, consecutive points distinct. bspline: the coefficients, at least degree + 1.
step all > 0 Å: the spacing of the regenerated points, usually the voxel spacing.
alpha catmull-rom 0 to 1, default 0.5 (centripetal).
degree, knots bspline Degree 1 to 5; len(control_points) + degree + 1 non-decreasing knots, clamped (the end values repeat exactly degree + 1 times, interior values at most degree times).
smoothing bspline Optional, the fit's smoothing factor (scipy's s); descriptive.

Evaluating. linear joins the control points with straight segments. catmull-rom is the Barry–Goldman form of the Catmull-Rom spline with knot intervals |P_{i+1} − P_i|^alpha, and end points extended by 2·P_0 − P_1 and 2·P_n − P_{n−1}; it passes through every control point. bspline is evaluated with de Boor's algorithm over the domain [u_degree, u_n], exactly as scipy's splev evaluates a splprep fit; it starts and ends at its first and last coefficient.

Regenerating points. The curve is cut into pieces at its anchors: the control points for catmull-rom and linear, and the curve at each distinct knot for bspline. Each piece is sampled at m = max(16, ceil(16·ℓ/step)) equal parameter steps (ℓ: the segment's length, or the length of the span's control polygon), starting exactly at its anchor and ending at the next. Its length L along those samples is then split into N = max(1, ceil(L/step − 1e-9)) equal arc-length steps. The pieces' points are joined, and the curve's end is appended. So consecutive points are at most step apart and every anchor is one of the points. The reference implementation is copick.util.filaments, and tests/data/filament_curves.json holds reference cases that other implementations reproduce to within 10⁻³ Å.

Writers regenerate points whenever they change the curve, remove curve when they change points any other way, and reverse both together. copick does not store a curve of a known kind that no longer describes its points: it drops it, with a warning. Fitting programs store their spline as a bspline (CopickFilamentCurve.from_tck), scaling the coefficients if they fitted voxel coordinates, and record their settings in the filament's metadata["fit"].

Readers that do not edit ignore curve. A curve is current when the first and last points, and every anchor, lie within 0.01·step of the points; an editor only reuses a current curve. Without one, it derives catmull-rom control points from the points: starting from the two ends, it repeatedly adds the point farthest from the curve through the chosen ones until none is farther than the tolerance (half the voxel spacing by default; copick.util.filaments.control_points_from_polyline). An editor that does not support bspline, or needs to insert or delete one of its points, converts it the same way.

filaments = run.new_filaments(object_name="microtubule", user_id="tracer", session_id="1")
filaments.from_control_points([clicks_1, clicks_2], voxel_spacing=10.0)  # (n, 3) control points in angstrom

curve = filaments.editable_curve(instance_id=1)  # the stored curve, or one derived from the points
edited = filaments.get(1).with_control_points(moved_control_points)
filaments.filaments = [edited, filaments.get(2)]
filaments.store()

from scipy.interpolate import splprep
from copick.models import CopickFilamentCurve

tck, _ = splprep(centreline_voxels.T, s=5.0, k=3)
fitted = CopickFilamentCurve.from_tck(tck, step=10.0, smoothing=5.0, scale=10.0)
run.new_filaments(object_name="microtubule", user_id="fitter", session_id="0").from_curves([fitted])

Refer to the API Reference for the CopickFilaments API.

Dense Segmentations

Dense segmentations are stored as OME-NGFF files in the run. The directory and the filename of the zarr file relate the segmentation to the user or tool that created it and to what it represents, and record which of four types it is:

Type Directory Name Voxel values Filename ends in
binary Segmentations a pickable object 1 inside the object, 0 outside _[object_name].zarr
multilabel Segmentations any descriptive name the label of the pickable object at that voxel, 0 for background _[name]-multilabel.zarr
instance InstanceSegmentations a pickable object the ID of the object instance at that voxel, 0 for background _[object_name].zarr
panoptic PanopticSegmentations any descriptive name two channels: the object label and the instance ID within that object _[name].zarr

Types added after binary and multilabel live in their own directory, so copick versions and other clients that predate them never list them, instead of misreading their voxel values.

An instance segmentation holds every instance of one object, for example every microtubule or every vesicle in a run, and tells them apart: voxel value v > 0 belongs to the instance with instance_id == v. These are the same IDs that picks and filaments carry, so picks or filaments describing the same instances (from the same user and session) use the same IDs. 0 is background, matching picks, where instance_id 0 means "no instance".

seg = run.new_segmentation(10.0, "microtubule", "1", user_id="tracer", is_instance=True)
seg.from_numpy(labels)              # integer volume, 0 = background, n = instance n
seg.instance_ids()                  # array([1, 2, ...])
run.get_segmentations(is_instance=True)

A panoptic segmentation combines both in one volume of shape (2, Z, Y, X), with a leading OME-NGFF axis of type channel (scale [1, voxel, voxel, voxel]):

  • channel 0, label: the label of the pickable object at each voxel, 0 for background (the multilabel convention);
  • channel 1, instance: the instance ID within that object. IDs are per object, so a segment is the pair (label, instance ID): ribosome 1 and vesicle 1 are different segments, and each object's IDs match its picks, filaments and instance segmentations. Instance 0 on a labelled voxel is a region of that object that is not split into instances (a membrane, the cytosol); background voxels carry no instance ID.
seg = run.new_segmentation(10.0, "cell", "1", user_id="tracer", is_panoptic=True)
seg.from_numpy(np.stack([labels, instance_ids]))   # (2, Z, Y, X)
seg.numpy(channel="label")                          # (Z, Y, X)
seg.instance_ids(object_name="microtubule")        # IDs where the label is microtubule's
seg.segments()                                      # [("membrane", 0), ("microtubule", 1), ...]

Writes check that every label is 0 or a pickable object's label, and that no background voxel carries an instance ID. MRC, TIFF and EM hold one volume each, so a panoptic segmentation is exported to them one channel at a time.

Queries that do not name a type select binary and multilabel segmentations, the types every client reads: get_segmentations(...) and delete_segmentations(...) default to is_instance=False, is_panoptic=False (pass None for any type), and run.segmentations lists all of them. Segmentation URIs mark the type with ?multilabel=true, ?instance=true or ?panoptic=true (microtubule:tracer/1@10.0?instance=true); a URI without one selects binary and multilabel segmentations, so copick cp, mv, rm and export do too. copick sync segmentations and copick stats segmentations take --segmentation-type binary|multilabel|instance|panoptic|all.

Data types

Segmentation values are stored without loss. When no dtype is given, from_numpy chooses the smallest unsigned integer type that holds every value: uint8 and up for binary and multilabel segmentations, uint16 and up for instance and panoptic segmentations. A value that does not fit a requested dtype raises an error instead of wrapping.

Naming Conventions

user_ids, session_ids, and object names should never contain underscores! Object names should also not end in -multilabel, which marks a multilabel segmentation in a filename.

Example Code - Read a segmentation into a numpy array
"""Read a segmentation from a CopickSegmentation object."""

import copick
import numpy as np
import zarr

# Initialize the root object from a configuration file
root = copick.from_file("path/to/config.json")

# Get the first run in the project
run = root.runs[0]

# Get 'proteasome' segmentation of user 'alice'
segmentation = run.get_segmentations(name="proteasome", user_id="alice")[0]

# Get the segmentation array from the segmentation
seg_zarr = zarr.open(segmentation.zarr())["0"]
seg = np.array(seg_zarr)

Refer to the API Reference for more information on the CopickSegmentation API.

Example segmentation file names

Segmentation files are named according to the following pattern:

10.000_good.picker_0_proteasome.zarr

The 10.000 part of the filename is the voxel spacing of the tomogram that the segmentation was created from. The good.picker part of the filename is the user or tool that created the segmentation. The 0 part of the filename is the session id of the user or tool that created the segmentation. The proteasome part of the filename is the name of the object that the segmentation represents. This is a binary segmentation.

10.000_good.picker_0_segmentation-multilabel.zarr

The 10.000 part of the filename is the voxel spacing of the tomogram that the segmentation was created from. The good.picker part of the filename is the user or tool that created the segmentation. The 0 part of the filename is the session id of the user or tool that created the segmentation. The segmentation part of the filename is an arbitrary name that describes the segmentation. This is a multilabel segmentation, thus all objects in the project could be represented in this segmentation.

InstanceSegmentations/10.000_tracer_1_microtubule.zarr

An instance segmentation of the microtubule object by the tool tracer in session 1: each microtubule's voxels hold its instance ID.

cryoET Data Portal

In data-portal projects, the portal's SegmentationMask annotations appear as binary segmentations and its volumetric InstanceSegmentationMask annotations as instance segmentations (user data-portal, session = the annotation file ID). The portal's point-based InstanceSegmentation annotations are not segmentations and are not shown.

On-disk Data Model

The on-disk data model of copick is as follows:

📁 copick_root
├─ 📄 copick_config.json
├─ 📁 Objects
│  └─ 📄 [pickable_object_name].zarr
└─ 📁 ExperimentRuns
   └─ 📁 [run_name] (index: src/io/copick_models.py:CopickPicks.runs)
      ├─ 📁 VoxelSpacing[xx.yyy]/
      │  ├─ 📁 [tomotype].zarr/
      │  │  └─ [OME-NGFF spec at 100%, 50% and 25% scale]
      │  └─ 📁 [tomotype]_[feature_type]_features.zarr/
      │     └─ [OME-NGFF spec at 100% scale]
      ├─ 📁 VoxelSpacing[x2.yy2]/
      │  ├─ 📁 [tomotype].zarr/
      │  │  └─ [OME-NGFF spec at 100%, 50% and 25% scale]
      │  └─ 📁 [tomotype]_[feature_type]_features.zarr/
      │     └─ [OME-NGFF spec at 100% scale]
      ├─ 📁 TiltSeries/
      │  └─ 📁 [ts_type].zarr/
      │     └─ [OME-NGFF spec, axes z/y/x, at 100%, 50% and 25% scale of y and x; CETS-shaped metadata]
      ├─ 📁 Alignments/
      │  └─ 📄 [alignment_name].json
      ├─ 📁 Picks/
      │  └─ 📄 [user_id | tool_name]_[session_id | 0]_[object_name].json
      ├─ 📁 Meshes/
      │  └─ 📄 [user_id | tool_name]_[session_id | 0]_[object_name].glb
      ├─ 📁 Filaments/
      │  └─ 📄 [user_id | tool_name]_[session_id | 0]_[object_name].json
      ├─ 📁 Segmentations/
      │  ├─ 📁 [xx.yyy]_[user_id | tool_name]_[session_id | 0]_[object_name].zarr
      │  │   └─ [OME-NGFF spec at 100% scale, 50% and 25% scale]
      │  └─ 📁 [xx.yyy]_[user_id | tool_name]_[session_id | 0]_[name]-multilabel.zarr
      │      └─ [OME-NGFF spec at 100% scale, 50% and 25% scale]
      ├─ 📁 InstanceSegmentations/
      │  └─ 📁 [xx.yyy]_[user_id | tool_name]_[session_id | 0]_[object_name].zarr
      │      └─ [OME-NGFF spec at 100% scale, 50% and 25% scale]
      └─ 📁 PanopticSegmentations/
         └─ 📁 [xx.yyy]_[user_id | tool_name]_[session_id | 0]_[name].zarr
             └─ [OME-NGFF spec, axes c/z/y/x, at 100% scale, 50% and 25% scale]