Bring your own data#

This page takes a pycytominer or CellProfiler table to a stamped mantispy object. Everything downstream reads the stamp, so an object that carries it works with every mt.pp, mt.tl and mt.pl function.

What a row is#

Pick the resolution that matches the table, then make sure obs carries the identifier columns it requires.

resolution

One row is

Required obs columns

grouped_by

object

one segmented cell or other object

Metadata_ImageID, Metadata_ObjectType, Metadata_ObjectNumber, plus Metadata_Plate and Metadata_Well for a plate

defaults

well

one well

Metadata_Plate, Metadata_Well

defaults to plate and well

aggregate

one consensus profile, such as a gene or a compound

none

you give it, for example ["Metadata_Perturbation"]

There is no guide level. Rows are objects, wells or aggregates, and guides are values in a column.

Metadata_Well is a zero-padded well name such as A03, not A3. mt.io.read_profiles normalizes the spelling when it reads a table or a platemap. An object assembled by hand has to use it already.

Metadata_Perturbation is the canonical perturbation column. Compound names, gene symbols and ORF identifiers all go there, and the tools that need to know what was done to a row read it first. Keep a more specific column such as Metadata_Compound or Metadata_Gene beside it if you want one.

Read a table#

import mantispy as mt

adata = mt.io.read_profiles("profiles.parquet", platemap="platemap.csv")

read_profiles splits the measurements from the metadata and records resolution="well" for files. platemap is a table, or a path to one, with a Metadata_Well column and optionally Metadata_Plate. It is left-joined onto obs, which is how the perturbation reaches each well. Pass resolution="aggregate" with grouped_by for a table that holds one row per gene.

Mark the controls#

mt.pp.annotate_controls(adata, negcon=["DMSO"])

annotate_controls marks the negative controls, and optionally the positive ones, from the perturbation column. Readouts such as mt.tl.map and mt.tl.hit_calling compare against these rows.

Stamp an object that came from elsewhere#

A published h5ad, another pipeline’s output or a matrix of embeddings has no stamp yet. mt.io.stamp records the resolution and the kind of feature, and rejects the object if obs lacks a required column.

# one row per well, CellProfiler measurements
mt.io.stamp(adata, resolution="well", feature_kind="measurement")

# one row per gene
mt.io.stamp(adata, resolution="aggregate", grouped_by=["Metadata_Perturbation"], feature_kind="measurement")

# learned embeddings
adata.X = adata.X.astype("float32")
mt.io.stamp(adata, resolution="well", feature_kind="embedding")

X is stored as float32, so convert a float64 matrix before stamping.

Build one from a DataFrame#

from_dataframe() turns a table that is already in memory into a stamped object. It splits Metadata_* columns from features and parses CellProfiler feature names.

A slide or any other table without plates and wells needs no Metadata_Plate or Metadata_Well at object resolution. The field of view comes from Metadata_Site, or from an explicit Metadata_ImageID if the table has one. An object is identified by Metadata_ImageID, Metadata_ObjectType and Metadata_ObjectNumber.

adata = mt.io.from_dataframe(df, resolution="object", object_type="cell")

A table from cp_measure uses different feature names. Say so with convention; the default is "cellprofiler".

adata = mt.io.from_dataframe(df, convention="cp_measure", object_type="cell")

Numeric columns that match neither convention, such as proprietary or learned features, are kept as features with feature_kind="embedding", never dropped. object_type is also an argument of stamp().

Check it#

report = mt.io.validate(adata)

validate returns a report of what is wrong and what is only worth a warning. Stamping already runs it, so a stamped object passes. Run it again after you edit obs or X by hand.

See the data model for every field and the stamp() reference for the arguments.