spacr.omero¶
OMERO import / export — a spaCR project out of a server, and results back in.
A large share of imaging labs never see a filesystem. Their images live in an OMERO server, they are addressed by id, and the only way anyone looks at a result is the right-hand panel in OMERO.web. spaCR, by contrast, is built around a flat folder of Yokogawa-named TIFFs. This module is the bridge, in both directions, and it is deliberately two directions rather than one: a one-way copy out of OMERO leaves the answers stranded in a folder nobody on the microscope side will ever open.
What it does¶
Import. import_dataset() and import_plate() take an OMERO
Dataset or Plate id and write a spaCR source folder:
plate1_A01_T0001F001L01A01Z01C01.tif plate1_A01_T0001F001L01A01Z01C02.tif plate1_A01_T0001F002L01A01Z01C01.tif …
That is spacr.convert.target_name() verbatim — the exact shape
spacr.utils._get_regex('cellvoyager', 'tif') parses — so the output folder
is handed straight to Mask/Measure with metadata_type='cellvoyager' and
nothing else. No new layout is invented here. The one thing that must
survive the trip is the plate geometry, because spaCR keys every measurement,
every plot and every well-level statistic off plateID / rowID /
columnID / fieldID; well_position() is where OMERO’s 0-based
(row, column) becomes spaCR’s ('r1', 'c1') and the well name A01.
Export. export_map_annotation(), export_file_annotation() and
export_tag_annotation() push spaCR’s own output back onto the OMERO
objects it came from: a key/value MapAnnotation per image or per well (what
OMERO.web actually renders), a FileAnnotation for the results CSV or the
figure PDF, and a TagAnnotation for a categorical verdict.
Why the logic is not in the adapter¶
Everything that is easy to get wrong here is arithmetic and string
formatting, not RPC: the row/column mapping, the filename, the id parsing,
the float formatting, the decision to replace rather than append. All of it
lives in pure functions over plain data — well_position(),
plane_filename(), parse_object_ref(), format_map_value(),
measurement_pairs(), plan_annotation() — and the BlitzGateway
calls are confined to a thin layer that only walks containers and moves
bytes. That is why this module has a real test suite on a machine with no
OMERO server and no omero-py installed: the fake gateway in
tests/test_omero.py implements about a dozen methods, and everything worth
testing is reachable through it.
Every adapter function takes gateway as its first argument. There is no
module-level connection and no implicit session — a caller that wants one
calls connect(), and a test passes its own object.
The well mapping, stated¶
OMERO’s WellWrapper.getRow() and .getColumn() are 0-based.
well_position() adds one and hands the result to
spacr.schema.well_id(), which is spaCR’s own definition of a well name.
Consequences worth writing down because they are the ones that get fumbled:
row 0 ->
A, row 7 ->H(a 96-well plate), row 15 ->P(384).row 25 ->
Z.row 26 ->
AA, not[(which is whatchr(65 + 26)gives) and not anIndexError(which is whatstring.ascii_uppercase[26]gives). This is bijective base 26, and it is not a hypothetical: a 1536-well plate has 32 rows and runsA..Z,AA..AF. Column 47 ->48, for the same plate, so nothing here caps the column at 24 either.the column is zero padded to two digits (
A01, neverA1) because spaCR’s strict Yokogawa regex is[A-Z]\d{2}.
Sizes and units¶
ImageWrapper.getPixelSizeX() returns a length object with a unit, not a
number of micrometres. This module never assumes µm: pixel_size_from()
carries the value and the unit symbol together in a PixelSize and
writes both into the import sidecar. A pixel size recorded in nm that is read
back as µm is a 1000x error in every area in the database and it is silent.
What is written alongside the images¶
Two sidecars, in the destination folder, both of which spaCR itself is happy to ignore:
omero_import.csvone row per plane: the target filename, the OMERO image id and name, the spaCR keys (
plateID/rowID/columnID/fieldID/prc), the channel index and its OMERO name,z/t, the pixel size and its unit, and where the well came from (seewell_sourcebelow).omero_import.jsonone object per import: server host and port, container kind/id/name, plate token, counts, channel names, pixel size, the spaCR version and a UTC timestamp.
Neither ever contains a password or a session key. That is asserted by a
test, along with the fact that OmeroConnection will not print one
either.
Not downloading a 100 GB plate to answer “what is in it”¶
inspect_container() reports the image count, the wells, the dimensions
and the channel names without ever calling getPlane — a fact the test
suite pins by counting calls on the fake gateway. The importers additionally
take limit (stop after N OMERO images) and dry_run (build the complete
plan, list every filename that would be written, touch no pixels).
Replace or append, and what is never done¶
Annotations are written under a spaCR-owned namespace
(NAMESPACE_ROOT), which is what makes a second run able to find its
own previous output instead of piling up a fifth copy of the same key/value
table.
The default is REPLACE, implemented as update-in-place, and it is the safe
option rather than a compromise: an existing MapAnnotation whose namespace
is exactly spaCR’s has its value overwritten. Nothing is created, nothing is
unlinked, nothing is deleted, and no annotation outside spaCR’s namespaces is
so much as read for its value. APPEND is available and explicit for
anyone who wants a history of runs on the object.
Three consequences are stated rather than hidden:
This module never deletes anything. There is no call to
deleteObjectsin it, under any mode, for any object. If a previousAPPENDrun left three copies,REPLACEupdates the oldest and reports the rest inAnnotationResult.duplicates— removing them is a decision for a human with the OMERO.web UI, not for an importer.FileAnnotations always append. The bytes of an OriginalFile cannot be rewritten in place through the gateway, and the alternative — delete the old attachment — is exactly the destructive behaviour ruled out above. The namespace plus the timestamp in the description identify the newest.
TagAnnotations are never edited. In OMERO a tag is a shared object; renaming the tag linked to this image renames it on every other object in the group that carries it. So
export_tag_annotation()links a tag when the verdict is new, does nothing when the identical verdict is already linked, and reports the previous tag when the verdict has changed — it never unlinks and never renames.
Connection settings and secrets¶
connection_settings() reads arguments first and the environment second:
OMERO_HOST, OMERO_PORT, OMERO_USER, OMERO_PASSWORD (or
OMERO_PASS), OMERO_SESSION_KEY, OMERO_GROUP, OMERO_SECURE.
OmeroConnection is frozen and redacts both secrets from repr(),
str() and OmeroConnection.redacted(), and no log record in this
module carries one.
The optional dependency¶
omero-py is an extra: pip install "spacr[omero]". It is deliberately
not part of spacr[all], because it depends on zeroc-ice, a
compiled C++ Ice runtime whose wheels lag Python releases and which otherwise
needs a C++ toolchain (see the comment beside the extra in setup.py).
So import spacr.omero must work without it, and it does: the import is
function-local, behind require_omero(), and a missing install produces
one actionable sentence naming pip install "spacr[omero]" rather than a
ModuleNotFoundError six frames deep inside Ice’s own import machinery.
This follows spacr.qt._QT_MISSING_MESSAGE and
spacr.anndata_export.require_anndata().
Two details of that guard are worth knowing:
A missing
Icecounts as a missing omero extra. A half-builtzeroc-iceis the single most likely way this fails in the field, andNo module named 'Ice'mentions neither OMERO nor spaCR.missing_omero_message()says what happened.This module is called
spacr/omero.pyand does not shadow the third-partyomeropackage. Absolute imports have been the default since Python 3, so a module inside thespacrpackage that asks foromerogets the top-level distribution, not its own sibling. That is verified rather than assumed — seetests/test_omero.py::test_spacr_omero_does_not_shadow_the_third_party_package, which puts a decoyomeropackage onsys.pathand checks which one arrives — because a self-import here would be silent and would look like a broken OMERO install.
The import is written as
importlib.import_module(OMERO_GATEWAY_MODULE) rather than as a literal
import omero.gateway for that same reason: the one place the name is
resolved is a named constant next to the guard that checks what came back,
instead of a bare statement in a file of the same name. The cost is that
tests/test_declared_dependencies_match_imports.py — which reads import
statements out of the AST — cannot see it, exactly as it cannot see
umap. That blind spot is pinned from the other side by
tests/test_omero.py::test_omero_py_is_reached_through_a_string_literal_and_must_not_be_removed,
so the next dependency census that reads “omero-py: unused” finds the answer
instead of deleting the extra.
Attributes¶
Exceptions¶
Connection settings are incomplete, or the server refused the login. |
|
The id resolved to nothing, or to a container with nothing usable in it. |
|
Base class for every refusal this module makes. |
|
|
|
An object id could not be read, or names the wrong kind of object. |
|
A well's row/column indices are not a position on a plate. |
Classes¶
What an export is about to do, decided before anything is touched. |
|
What one annotation write did. |
|
The answer to "what is in this container", with no pixels fetched. |
|
What a multi-target export did. |
|
What is known about one OMERO image without reading a single pixel. |
|
What an import did, or (with |
|
Everything needed to open a session, with the secrets kept out of sight. |
|
A parsed reference to one OMERO object. |
|
A physical pixel size together with the unit it was recorded in. |
|
One 2-D plane that would be, or was, written. |
|
One well, in both vocabularies at once. |
Functions¶
|
Open a session and return the connected gateway. |
|
Build an |
|
Attach a results CSV or a figure PDF to an OMERO object. |
|
Write spaCR measurements onto an OMERO object as key/value pairs. |
|
Write one per-well MapAnnotation per well of an OMERO Plate. |
|
Attach a categorical verdict — |
|
Render |
|
Report whether the |
|
Import a Dataset or a Plate, dispatching on what |
|
Import an OMERO Dataset into a spaCR source folder. |
|
Import an OMERO Plate into a spaCR source folder. |
|
Report what a Dataset or Plate contains, without fetching pixels. |
|
Return whether |
|
Report whether |
|
Return the |
|
Project one measurement row onto the key/value pairs OMERO stores. |
|
Return the full "install the extra" text naming |
|
Invert |
|
Parse |
|
Parse an OMERO object reference into a kind and a positive id. |
|
Read a |
|
Decide whether to overwrite spaCR's previous annotation or add one. |
|
Decide what to do about a categorical verdict tag. |
|
Return the Yokogawa filename spaCR expects for one plane. |
|
Reduce |
|
Import and return |
|
Reduce many object rows to one per-well summary, in plain Python. |
|
Recover a well name from an OMERO image name, or return |
|
Map OMERO's 0-based |
|
Summarise object rows and project them onto key/value pairs. |
Module Contents¶
- exception spacr.omero.OmeroConnectionError[source]¶
Bases:
OmeroErrorConnection settings are incomplete, or the server refused the login.
Initialize self. See help(type(self)) for accurate signature.
- exception spacr.omero.OmeroContainerError[source]¶
Bases:
OmeroErrorThe id resolved to nothing, or to a container with nothing usable in it.
Initialize self. See help(type(self)) for accurate signature.
- exception spacr.omero.OmeroError[source]¶
Bases:
ValueErrorBase class for every refusal this module makes.
A
ValueErrorrather than a bespoke hierarchy root: every one of these is “the input does not describe something I can act on”, and a caller that already handlesValueErroraround a conversion step keeps working.Initialize self. See help(type(self)) for accurate signature.
- exception spacr.omero.OmeroExtraMissing[source]¶
Bases:
ImportErroromero-py(or the Ice runtime under it) is not installed.An
ImportErrorsubclass, so a caller already guarding withexcept ImportErrorkeeps working and the actionable message — not a traceback through Ice’s import machinery — is what reaches the user.Initialize self. See help(type(self)) for accurate signature.
- exception spacr.omero.OmeroIdError[source]¶
Bases:
OmeroErrorAn object id could not be read, or names the wrong kind of object.
Initialize self. See help(type(self)) for accurate signature.
- exception spacr.omero.OmeroWellError[source]¶
Bases:
OmeroErrorA well’s row/column indices are not a position on a plate.
Initialize self. See help(type(self)) for accurate signature.
- class spacr.omero.AnnotationPlan[source]¶
What an export is about to do, decided before anything is touched.
- Parameters:
action –
ACTION_CREATE,ACTION_UPDATEorACTION_UNCHANGED.annotation_id – the annotation to update, for
ACTION_UPDATE/ACTION_UNCHANGED;Nonefor a create.namespace – the namespace the annotation lives in.
duplicates – other spaCR annotations already in that namespace, left untouched and reported.
reason – one sentence explaining the choice, for the result and the log.
- class spacr.omero.AnnotationResult[source]¶
What one annotation write did.
- Parameters:
action –
ACTION_CREATE,ACTION_UPDATEorACTION_UNCHANGED.namespace – the namespace written.
annotation_id – the annotation’s id, when the server gave one.
n_pairs – how many key/value pairs were written (map annotations).
duplicates – other spaCR annotations in the same namespace, left untouched.
reason – the plan’s explanation, carried through.
- class spacr.omero.ContainerListing[source]¶
The answer to “what is in this container”, with no pixels fetched.
- Parameters:
kind –
'Dataset'or'Plate'.object_id – the container’s OMERO id.
name – the container’s OMERO name.
images – one
ImageInfoper image found.wells – the well names present, for a Plate; empty for a Dataset.
unplaced_wells – how many wells had no row/column and were skipped.
- class spacr.omero.ExportResult[source]¶
What a multi-target export did.
- Parameters:
namespace – the namespace written.
results – one
AnnotationResultper target, keyed intargetsorder.targets – the target labels (well names, image ids) in order.
missing – labels that were asked for but not found on the container.
- class spacr.omero.ImageInfo[source]¶
What is known about one OMERO image without reading a single pixel.
- Parameters:
image_id – the OMERO image id.
name – the OMERO image name.
size_x – width in pixels.
size_y – height in pixels.
size_z – number of z-slices.
size_c – number of channels.
size_t – number of timepoints.
channels – the channel names OMERO holds, in channel order.
pixel_size_x – the physical pixel size in x, with its unit.
pixel_size_y – the physical pixel size in y, with its unit.
pixel_size_z – the z step, with its unit.
well – the well this image sits in, for a Plate;
Nonein a Dataset, which has no plate geometry.field_id – the 1-based field (WellSample) index, for a Plate.
- class spacr.omero.ImportResult[source]¶
What an import did, or (with
dry_run) what it would have done.- Parameters:
kind –
'Dataset'or'Plate'.object_id – the container’s OMERO id.
name – the container’s OMERO name.
plate – the
plateIDtoken every filename was built with.dst – the destination folder.
planned – every plane the import covered, in write order.
written – the filenames actually written.
skipped – filenames that already existed and were left alone.
dry_run – whether pixels were fetched at all.
limited – whether
limitstopped the walk before the end.n_images – how many OMERO images were visited.
- class spacr.omero.OmeroConnection[source]¶
Everything needed to open a session, with the secrets kept out of sight.
Frozen, so a settings object cannot be mutated halfway through a run, and
repr-suppressed, because the single most common way a password reaches a log file, a crash report or a notebook cell is somebody printing the object that holds it.redacted()is the form that is safe to write down.- Parameters:
host – server hostname. Required.
port – server port, default
DEFAULT_PORT.username – OMERO user name. Required for password auth, optional with a session key.
password – the password. Never printed.
session_key – an existing session uuid, used instead of a password. Never printed.
secure – whether to keep the connection encrypted after login. Default
True.group – optional OMERO group to switch into after connecting.
- describe() str[source]¶
Return a one-line human description, with no secret in it.
- Returns:
e.g.
omero.example.org:4064 as jdoe (password, secure).
- redacted() Dict[str, Any][source]¶
Return a plain dict of the settings, safe to log or serialise.
Both secrets are replaced by
SECRET_PLACEHOLDERwhen present and byNonewhen absent, so the shape of the record does not itself leak whether a password was supplied… it does say which credential was used, which is diagnostic and not a secret.- Returns:
a JSON-serialisable dict with no credential in it.
- class spacr.omero.OmeroRef[source]¶
A parsed reference to one OMERO object.
- Parameters:
kind – the container type in OMERO’s capitalisation (
'Dataset'), orNonewhen the input was a bare number and the caller must say what it is.object_id – the positive integer id.
text – the original input, kept for error messages.
- class spacr.omero.PixelSize[source]¶
A physical pixel size together with the unit it was recorded in.
- Parameters:
value – the magnitude, or
Nonewhen the server has no calibration for this image (which is common, and is not an error).unit – the unit symbol as OMERO reports it —
'MICROMETER','µm','nm'. Carried verbatim; this module never converts, because a silent unit conversion is a silent factor of 1000.
- class spacr.omero.PlanePlan[source]¶
One 2-D plane that would be, or was, written.
- Parameters:
filename – the target filename (not a path — the destination folder is the caller’s).
image_id – the OMERO image the plane comes from.
image_name – that image’s OMERO name.
well – the well name, e.g.
'A01'.position – the full
WellPositionfor that well.field_id – 1-based field id.
channel – 1-based channel id.
channel_name – the channel’s OMERO name.
z – 1-based z index.
t – 1-based timepoint index.
info – the source image’s
ImageInfo.well_source –
'plate'(from the well’s row/column),'name'(parsed out of the image name) or'sequence'(assigned in listing order because nothing else said).
- class spacr.omero.WellPosition[source]¶
One well, in both vocabularies at once.
- Parameters:
row_index – 1-based row (spaCR’s convention). OMERO’s row + 1.
column_index – 1-based column. OMERO’s column + 1.
row_id – spaCR’s
rowID, e.g.'r1'.column_id – spaCR’s
columnID, e.g.'c1'.well – the well name, e.g.
'A01'— zero padded to two column digits, which is what spaCR’s strict Yokogawa regex requires.plate_format – the smallest standard plate this position fits (96, 384, 1536, …), or
Nonewhen it fits none. Reported, never enforced: a partial or non-standard plate is a real thing.
- spacr.omero.connect(settings: OmeroConnection | None = None, *, gateway_factory: Any | None = None, **overrides: Any) Any[source]¶
Open a session and return the connected gateway.
settingsmay be omitted entirely, in which case one is built from**overridesand the environment throughconnection_settings().The caller owns the returned object and is responsible for closing it (
gateway.close()); this module deliberately does not hold a module-level connection, so nothing here can leak a session between two unrelated runs.- Parameters:
settings – a prepared
OmeroConnection, orNone.gateway_factory – callable taking the settings and returning an unconnected gateway. Defaults to building a real
BlitzGateway, which is the only line in this module that needs the extra.overrides – passed to
connection_settings()whensettingsisNone.
- Returns:
the connected gateway object.
- Raises:
OmeroConnectionError – when the server refuses the login.
OmeroExtraMissing – when the extra is not installed and no
gateway_factorywas supplied.
- spacr.omero.connection_settings(host: str | None = None, *, port: int | str | None = None, username: str | None = None, password: str | None = None, session_key: str | None = None, secure: bool | str | None = None, group: str | None = None, env: Mapping[str, str] | None = None) OmeroConnection[source]¶
Build an
OmeroConnectionfrom arguments, then the environment.Arguments always win; anything left unset is looked up in
envthroughENV_VARS. Reading the environment matters more here than it looks: it is how a password stays out of a notebook, out of a settings CSV and out of the shell history, which is the same reasonOMERO_PASSWORDexists at all.- Parameters:
host – server hostname; falls back to
OMERO_HOST.port – server port; falls back to
OMERO_PORT, thenDEFAULT_PORT.username – falls back to
OMERO_USER/OMERO_USERNAME.password – falls back to
OMERO_PASSWORD/OMERO_PASS.session_key – falls back to
OMERO_SESSION_KEY; used instead of a password when present.secure – falls back to
OMERO_SECURE, thenTrue.group – falls back to
OMERO_GROUP.env – the environment to read. Defaults to
os.environ; passing a plain dict is how the tests avoid touching the real one.
- Returns:
a validated, frozen
OmeroConnection.- Raises:
OmeroConnectionError – when the host is missing, the port is not a usable TCP port, no credential was supplied, or a password was given with no user name to go with it.
- spacr.omero.export_file_annotation(gateway: Any, target: Any, path: str | os.PathLike, *, namespace: str = NS_FILE, mimetype: str | None = None, description: str | None = None) AnnotationResult[source]¶
Attach a results CSV or a figure PDF to an OMERO object.
The counterpart to the truncated MapAnnotation: the panel gets the fifty numbers worth reading, this gets the whole table.
File annotations always append, and that is the non-destructive choice rather than an oversight. The bytes of an OriginalFile cannot be rewritten through the gateway, so “replace” would have to mean delete the previous attachment — deleting evidence of an earlier run, which is exactly what this module refuses to do anywhere. Previous spaCR file annotations are reported in
AnnotationResult.duplicates, and the description carries a UTC timestamp so the newest is identifiable.- Parameters:
gateway – a connected gateway;
createFileAnnfromLocalFileis called on it.target – the Dataset, Plate, Image or Well to attach to.
path – the local file.
namespace – the spaCR file namespace.
mimetype – overrides the type guessed from the extension.
description – overrides the generated description.
- Returns:
an
AnnotationResultwithaction=create.- Raises:
OmeroError – for a foreign namespace, or a path that is not a file.
- spacr.omero.export_map_annotation(gateway: Any, target: Any, pairs: Sequence[Sequence[Any]], *, namespace: str = NS_MEASUREMENTS, mode: str = REPLACE, annotation_factory: Any | None = None) AnnotationResult[source]¶
Write spaCR measurements onto an OMERO object as key/value pairs.
A MapAnnotation is what OMERO.web renders in the right-hand panel, which makes it the only annotation an OMERO user reliably sees. Build the pairs with
measurement_pairs()(per object) orwell_summary_pairs()(per well) — both already do the string conversion OMERO requires and the truncation the panel requires.- Parameters:
gateway – a connected gateway; used only to construct the wrapper.
target – the Image, Well, Dataset or Plate wrapper to annotate.
pairs – the key/value pairs. Values are cast to
strhere as a last line of defence — OMERO’s model has no numeric map value.namespace – which spaCR namespace to write. Must be one spaCR owns.
mode –
REPLACE(default: update spaCR’s own previous annotation in place) orAPPEND.annotation_factory – callable
(wrapper_name, gateway)returning a new annotation wrapper. Defaults to the realomero.gateway.MapAnnotationWrapper; the tests pass their own.
- Returns:
an
AnnotationResult.- Raises:
OmeroError – for an unknown mode or a foreign namespace.
- spacr.omero.export_plate_summaries(gateway: Any, ref: Any, summaries: Mapping[str, Sequence[Mapping[str, Any]]], *, namespace: str = NS_WELL_SUMMARY, mode: str = REPLACE, annotation_factory: Any | None = None, **pair_kwargs: Any) ExportResult[source]¶
Write one per-well MapAnnotation per well of an OMERO Plate.
This is the half that closes the loop: spaCR measured the plate, and the numbers land back on the wells they came from, where the person at the microscope will actually see them. Wells are matched by name (
'A01'), which round-trips throughwell_position()andomero_indices(), so the mapping used on the way in is the mapping used on the way out.- Parameters:
gateway – a connected gateway.
ref – a Plate id,
'Plate:4711', or an OMERO.web URL.summaries –
{well_name: [object_row, ...]}. The rows are summarised withsummarise_rows(); pass a single already-summarised row as a one-element list to skip the aggregation.namespace – the spaCR well-summary namespace.
mode –
REPLACE(default) orAPPEND.annotation_factory – callable
(wrapper_name, gateway).pair_kwargs – passed to
measurement_pairs().
- Returns:
an
ExportResult; wells named insummariesthat the Plate does not have are listed inExportResult.missingrather than raising, because a plate map with an extra control well in it is a normal thing to hand in.- Raises:
OmeroIdError – when
refdoes not name a Plate.OmeroContainerError – when the Plate does not exist.
- spacr.omero.export_tag_annotation(gateway: Any, target: Any, text: str, *, namespace: str = NS_TAG, mode: str = REPLACE, annotation_factory: Any | None = None) AnnotationResult[source]¶
Attach a categorical verdict —
'hit','QC fail'— as a tag.See
plan_tag()for why a tag is never edited in place: it is a shared object, and renaming it would relabel every other object carrying it. This is idempotent for an unchanged verdict and additive (with a report) for a changed one, and it never unlinks or deletes.- Parameters:
gateway – a connected gateway.
target – the object to tag.
text – the verdict.
namespace – the spaCR verdict namespace.
mode –
REPLACE(default) orAPPEND.annotation_factory – callable
(wrapper_name, gateway); defaults toomero.gateway.TagAnnotationWrapper.
- Returns:
an
AnnotationResult.- Raises:
OmeroError – for an unknown mode, a foreign namespace, or an empty verdict (a tag with no text is invisible in OMERO.web).
- spacr.omero.format_map_value(value: Any, *, float_format: str = FLOAT_FORMAT, max_chars: int = MAX_VALUE_CHARS) str[source]¶
Render
valueas the string an OMERO map annotation stores.OMERO map values are strings — there is no numeric type in the model — so the formatting decision is not cosmetic, it is the only thing that decides what a reader sees and what a re-parser gets back. The rules, all of them:
boolfirst, before the numeric branch, becauseTrueis anintand would otherwise render as1.numpy.bool_needs its own check for the same reason: it is not abool, and it does answer__index__.integers render exactly:
str(int(value)), never throughfloat_format, so a 12-digit object id does not become1.23457e+11.floats go through
FLOAT_FORMAT— six significant digits, with trailing zeros dropped, so3.0is'3'and1.23456789e-5is'1.23457e-05'.infand-infrender as'inf'/'-inf'rather than being hidden, because in spaCR they mean a ratio with a zero denominator and that is worth seeing.missing values (see
is_missing()) render asMISSING_TEXT. Dropping them instead is whatNAN_DROPis for, at themeasurement_pairs()level; here the value has to become some string.anything else is
str(), stripped, newlines collapsed to spaces (a newline inside a map value breaks the panel’s layout), and truncated tomax_charswith a trailing….
- Parameters:
value – the value to render.
float_format – the format string used for non-integral floats.
max_chars – the length at which the text is truncated.
- Returns:
a string, always.
- spacr.omero.have_omero() bool[source]¶
Report whether the
omeroextra can be imported, without raising.- Returns:
Truewhenrequire_omero()would succeed.
- spacr.omero.import_container(gateway: Any, ref: Any, dst: str | os.PathLike, *, kind: str | None = None, **kwargs: Any) ImportResult[source]¶
Import a Dataset or a Plate, dispatching on what
refsays it is.- Parameters:
gateway – a connected gateway.
ref –
'Plate:4711','Dataset:123', an OMERO.web URL, or a bare id together withkind.dst – destination folder.
kind –
'Dataset'or'Plate', required whenrefis bare.kwargs – passed to
import_dataset()orimport_plate().
- Returns:
an
ImportResult.- Raises:
OmeroIdError – when the kind is unknown, unsupported, or contradicts
ref.
- spacr.omero.import_dataset(gateway: Any, ref: Any, dst: str | os.PathLike, *, plate: str | None = None, limit: int | None = None, dry_run: bool = False, overwrite: bool = False, well_from_name: bool = True, settings: OmeroConnection | None = None) ImportResult[source]¶
Import an OMERO Dataset into a spaCR source folder.
A Dataset is a flat bag of images with no plate geometry, so the wells have to come from somewhere. Two sources, in order, and which one was used is recorded per file in
SIDECAR_CSV:'name'the image name contains a delimited well token (see
well_from_image_name()). Turned off withwell_from_name=False.'sequence'nothing said, so wells are handed out row-major in listing order —
A01,A02, … — one per image. This is a labelling, not a claim about the physical plate, and it is what makes the images loadable by spaCR at all.
- Parameters:
gateway – a connected gateway.
ref – a Dataset id,
'Dataset:123', or an OMERO.web URL.dst – destination folder; created if missing.
plate – the
plateIDtoken. Defaults to the Dataset’s own name, run throughplate_token().limit – stop after this many OMERO images. The cheap way to try an import against a container you have not seen.
dry_run – plan everything, write the sidecars, fetch no pixels.
overwrite – rewrite TIFFs that already exist. Off by default, so a re-run resumes rather than redoing the download.
well_from_name – whether to look for a well in the image name.
settings – the connection these images came from, recorded (redacted) in
SIDECAR_JSON.
- Returns:
an
ImportResult.- Raises:
OmeroIdError – when
refnames something other than a Dataset — a Plate id passed here is refused rather than half-imported.OmeroContainerError – when the id resolves to nothing or the Dataset has no images.
- spacr.omero.import_plate(gateway: Any, ref: Any, dst: str | os.PathLike, *, plate: str | None = None, limit: int | None = None, dry_run: bool = False, overwrite: bool = False, settings: OmeroConnection | None = None) ImportResult[source]¶
Import an OMERO Plate into a spaCR source folder.
A Plate is the case spaCR was built for: every image already knows its well, and the well knows its row and column. Those indices are carried across by
well_position()and become the well name in the filename androwID/columnIDin the sidecar, so a plate map drawn in spaCR lines up with the plate map in OMERO.web.Fields are the WellSample order within each well, 1-based, which is what OMERO means by an imaging site.
Wells with no row/column (present in the Plate but never placed) are skipped and counted, never defaulted to A01.
- Parameters:
gateway – a connected gateway.
ref – a Plate id,
'Plate:4711', or an OMERO.web URL.dst – destination folder; created if missing.
plate – the
plateIDtoken. Defaults to the Plate’s own name.limit – stop after this many OMERO images.
dry_run – plan everything, write the sidecars, fetch no pixels.
overwrite – rewrite TIFFs that already exist.
settings – the connection, recorded (redacted) in the JSON sidecar.
- Returns:
an
ImportResult.- Raises:
OmeroIdError – when
refnames something other than a Plate.OmeroContainerError – when the id resolves to nothing or the Plate has no placed wells with images.
- spacr.omero.inspect_container(gateway: Any, ref: Any, *, kind: str | None = None) ContainerListing[source]¶
Report what a Dataset or Plate contains, without fetching pixels.
This exists so that “what is in plate 4711?” costs a metadata query rather than a 100 GB download. Nothing in this function or anything it calls touches
getPrimaryPixelsorgetPlane, and the test suite asserts that by counting calls on the fake gateway.- Parameters:
gateway – a connected
BlitzGateway(or anything with the samegetObjectsurface).ref – an id, a
'Plate:4711'reference, or an OMERO.web URL.kind – the object type, when
refis a bare id. Defaults to the type named byref; one of the two must say.
- Returns:
- Raises:
OmeroIdError – for an unusable id, or when
kindandrefdisagree, or when neither says what the object is.OmeroContainerError – when the id resolves to nothing.
- spacr.omero.is_missing(value: Any) bool[source]¶
Return whether
valuerepresents missing annotation data.Missing values include
None, floating-point NaN, pandasNAandNaTsentinels, and objects for which self-inequality cannot be reduced to a boolean. These values are omitted rather than serialized as literal sentinel text in an OMERO map annotation.- Parameters:
value – anything.
- Returns:
Truewhen the value is missing.
- spacr.omero.is_spacr_namespace(namespace: str | None) bool[source]¶
Report whether
namespaceis one spaCR owns and may write to.The test is exact equality against
SPACR_NAMESPACES, not a prefix match: a prefix match would claimgithub.com/EinarOlafsson/spacr-fork/ 1/measurementsas spaCR’s own and overwrite somebody else’s annotation.- Parameters:
namespace – an annotation namespace, possibly
None(OMERO’s default namespace, which spaCR never writes to).- Returns:
Truewhen spaCR owns it.
- spacr.omero.list_spacr_annotations(target: Any) Tuple[Tuple[int, str | None], ...][source]¶
Return the
(id, namespace)of every spaCR annotation ontarget.listAnnotations()is deliberately called without a namespace filter and the filtering is done here. The filter is what decides whether an annotation gets written to, and that decision must not depend on a server honouring a query parameter: a server that ignoredns=would otherwise hand back a stranger’s annotation and this module would update it.- Parameters:
target – any OMERO object wrapper with
listAnnotations().- Returns:
(id, namespace)pairs, only for namespaces inSPACR_NAMESPACES.
- spacr.omero.measurement_pairs(row: Mapping[str, Any], *, columns: Sequence[str] | None = None, priority: Sequence[str] = PRIORITY_KEYS, max_pairs: int = MAX_MAP_PAIRS, nan_policy: str = NAN_KEEP, extra: Mapping[str, Any] | None = None, float_format: str = FLOAT_FORMAT) Tuple[Tuple[str, str], ...][source]¶
Project one measurement row onto the key/value pairs OMERO stores.
Takes plain data — a
dict, ordf.iloc[i].to_dict()— so that the projection can be tested without a DataFrame, a database or a server.Why there is a cap. A spaCR object table has 300-600 columns. A MapAnnotation with 400 entries renders in OMERO.web as a 400-row scrolling table in a 300 px panel, which nobody reads and which makes the panel useless for the annotations that were worth showing. So the annotation is a summary, capped at
max_pairs(MAX_MAP_PAIRS= 50 by default) and ordered so the useful keys are the ones that survive:prioritykeys first in the order given, then the row’s own order. When anything is cut, the last pair isTRUNCATION_KEY, saying how many were dropped and that the full table is the CSV attached underNS_FILE. The full table is never only in the annotation.The result always has at most
max_pairsentries, notice included.- Parameters:
row – the measurement row, as a mapping.
columns – restrict to these columns, in this order (after the priority keys).
Noneuses every key inrow.priority – keys promoted to the front when present.
max_pairs – the hard cap, including the truncation notice.
nan_policy –
NAN_KEEP(default — a missing value becomes the string'NaN', so “measured and missing” is distinguishable from “never computed”) orNAN_DROP(the key is omitted).extra – pairs merged in after
row, overriding it — used for provenance such as the spaCR version.float_format – passed to
format_map_value().
- Returns:
a tuple of
(key, value)string pairs.- Raises:
OmeroError – for an unknown
nan_policyormax_pairs< 2 (a cap of one leaves no room for both a measurement and a notice).
- spacr.omero.missing_omero_message(module: str) str[source]¶
Return the full “install the extra” text naming
module.- Parameters:
module – the top-level module that could not be imported, e.g.
'omero'or'Ice'.- Returns:
OMERO_MISSING_MESSAGEformatted formodule, withICE_NOTEappended whenmodulebelongs to zeroc-ice.
- spacr.omero.omero_indices(well: str) Tuple[int, int][source]¶
Invert
well_position():'A01'->(0, 0).The round trip is what makes the export direction possible — matching a spaCR per-well summary back onto the OMERO wells it came from.
- Parameters:
well – a well name (
'A01','aa1','AF48') or a spaCR(rowID, columnID)pair already joined, e.g.'r1_c1'is not accepted — pass the well name.- Returns:
(row, column), both 0-based, as OMERO reports them.- Raises:
OmeroWellError – when
wellis not a well name.
- spacr.omero.parse_object_id(value: Any, expect: str | None = None) int[source]¶
Parse
valueto a positive OMERO id, checking the type it names.This is the guard that stops a Plate id being handed to the Dataset importer: the mistake is easy (both are integers, both exist on the same server) and the failure without this check is a confusing empty import rather than an error.
- Parameters:
value – anything
parse_object_ref()accepts.expect – the required object type, e.g.
'Dataset'. A reference that names a different type is refused; a bare id, which names no type, is accepted and taken at the caller’s word.
- Returns:
the positive integer id.
- Raises:
OmeroIdError – for an unusable id, or when the reference names a type other than
expect.
- spacr.omero.parse_object_ref(value: Any) OmeroRef[source]¶
Parse an OMERO object reference into a kind and a positive id.
Accepts, deliberately, every form a user is likely to paste:
123or'123'— the id alone, kind unknown;'Dataset:123','dataset-123'— id and kind together;'https://omero.example.org/webclient/?show=plate-42'— what the OMERO.web address bar contains while you are looking at the plate.
- Parameters:
value – an int, or a string in one of the forms above, or an
OmeroRef(returned unchanged).- Returns:
an
OmeroRef.- Raises:
OmeroIdError – for a negative or zero id, a non-numeric id, an empty value, a float, or a type name OMERO does not have. OMERO ids are positive int64 and 0 is never one.
- spacr.omero.pixel_size_from(length: Any) PixelSize[source]¶
Read a
PixelSizeout of whatevergetPixelSizeX()returned.omero-py returns different things depending on how it is called and how old the server is: a
LengthIobject (getValue()/getSymbol()orgetUnit()), a plain float (already in µm, by omero-py’s own convention), orNonefor an uncalibrated image. All three are handled, and only the plain-float case assumes a unit — because in that case omero-py has already made the assumption itself, and this records that it did rather than pretending the unit is unknown.- Parameters:
length – the value returned by
getPixelSizeX/Y/Z.- Returns:
a
PixelSize;PixelSize(None, None)when there is no calibration.
- spacr.omero.plan_annotation(existing: Iterable[Sequence[Any]], namespace: str, mode: str = REPLACE) AnnotationPlan[source]¶
Decide whether to overwrite spaCR’s previous annotation or add one.
Pure:
existingis a sequence of(annotation_id, namespace)pairs, which is all the decision needs, so the rule is testable without a server.Under
REPLACE— the default — an annotation whose namespace is exactlynamespaceis updated in place. Nothing is created, nothing is unlinked and nothing is deleted; a re-run therefore leaves one annotation rather than a growing pile, and an annotation in any other namespace (a colleague’s, OMERO’s own bulk annotations) is never even considered. When more than one already exists — only possible after anAPPENDrun or a concurrent writer — the oldest (lowest id) is updated and the rest are reported induplicatesrather than removed, because deleting an annotation somebody may have linked elsewhere is not a decision an exporter gets to make.- Parameters:
existing –
(id, namespace)pairs already on the target.namespace – the spaCR namespace being written.
mode –
REPLACEorAPPEND.
- Returns:
an
AnnotationPlan.- Raises:
OmeroError – for an unknown mode, or a namespace spaCR does not own (this module refuses to write outside
SPACR_NAMESPACES, which is the guarantee that makes “replace” safe).
- spacr.omero.plan_tag(existing: Iterable[Sequence[Any]], namespace: str, text: str, mode: str = REPLACE) AnnotationPlan[source]¶
Decide what to do about a categorical verdict tag.
Tags get their own rule, and the reason is a property of OMERO rather than a preference: a TagAnnotation is a shared object. The tag reading
hiton this image is very likely the same row in the database as the tag readinghiton two hundred other images, so editing its text to saynot hitwould silently relabel all of them. Updating in place is therefore not available here, whatever the mode.What happens instead:
the identical verdict is already linked ->
ACTION_UNCHANGED, nothing is written and the export is idempotent;no spaCR verdict is linked ->
ACTION_CREATE;a different spaCR verdict is linked ->
ACTION_CREATEfor the new one, and the old one is reported induplicatesand left in place. Two verdicts on one object is visibly wrong in OMERO.web, which is better than silently rewriting history.
- Parameters:
existing –
(id, namespace, text)triples already on the target.namespace – the spaCR verdict namespace.
text – the verdict being written.
mode –
REPLACEorAPPEND;APPENDskips even the idempotence check.
- Returns:
an
AnnotationPlan.- Raises:
OmeroError – for an unknown mode or a namespace spaCR does not own.
- spacr.omero.plane_filename(plate: str, well: str, field_id: int, channel: int, z: int = 1, t: int = 1) str[source]¶
Return the Yokogawa filename spaCR expects for one plane.
A thin, deliberate delegation to
spacr.convert.target_name(), so there is exactly one definition of the name in spaCR and an OMERO import cannot drift away from a folder conversion.- Parameters:
plate – plate token; run it through
plate_token()first.well – canonical well id, e.g.
'A01'.field_id – 1-based field (imaging site) id.
channel – 1-based channel id.
z – 1-based z-slice id.
t – 1-based timepoint id.
- Returns:
e.g.
'plate1_A01_T0001F001L01A01Z01C01.tif'.
- spacr.omero.plate_token(name: Any) str[source]¶
Reduce
nameto a plate token that survives spaCR’s filename regex.Underscores are stripped along with every other non-alphanumeric character, and the reason is not cosmetic: the
cellvoyagerregex splits the plate from the well on_, so a plate literally calledmy runwould move the split point and misparse every well. This is the same rule asspacr.convert._sanitise, which is the function that already governs the rest of spaCR’s conversions.- Parameters:
name – any label — an OMERO plate name, a dataset name, an id.
- Returns:
a non-empty token of
[A-Za-z0-9-], falling back to'plate'when nothing survives.
- spacr.omero.require_omero() Any[source]¶
Import and return
omero.gateway, or raise a message worth reading.- Returns:
the imported
omero.gatewaymodule, which is whereBlitzGateway,MapAnnotationWrapperandTagAnnotationWrapperlive.- Raises:
OmeroExtraMissing – when
omero-py(or the Ice runtime beneath it) is not installed. The message namespip install "spacr[omero]".ImportError – unchanged, when the failure is a real bug inside a module that is installed.
- spacr.omero.summarise_rows(rows: Sequence[Mapping[str, Any]], *, columns: Sequence[str] | None = None) Dict[str, Any][source]¶
Reduce many object rows to one per-well summary, in plain Python.
The mean of each numeric column over the rows that actually have a value there, plus
n_objects. Missing values are excluded from their own column’s mean rather than zero-filled: in spaCR a NaN is usually structural (apathogen_*column is NaN for a cell with no pathogen in it), and zero-filling turns “no pathogen” into “a pathogen of zero size”. A column with no usable value in any row is reported asNone, whichmeasurement_pairs()renders asNaN.Non-numeric columns are carried through when every row agrees on the value — that is how
plateID,rowID,columnIDandconditionreach the well annotation — and dropped when they disagree, because a single well cannot have two plate ids and picking one would be a guess.- Parameters:
rows – the per-object rows for one well.
columns – restrict to these columns;
Noneuses the union of the rows’ keys.
- Returns:
a plain dict, ready for
measurement_pairs().
- spacr.omero.well_from_image_name(name: Any) str | None[source]¶
Recover a well name from an OMERO image name, or return
None.Images in a Dataset have no plate geometry — a Dataset is a flat bag — so the only place a well can come from is the name the acquisition software gave the file. This reads the first delimited token that parses as a well within a 1536-well plate (32 rows, 48 columns).
It is a guess, and it is treated as one:
import_dataset()recordswell_source='name'or'sequence'per file in the sidecar CSV, andwell_from_name=Falseturns it off entirely.- Parameters:
name – the OMERO image name.
- Returns:
a canonical well name (
'A01'), orNonewhen the name contains nothing that parses as one.
- spacr.omero.well_position(row: Any, column: Any) WellPosition[source]¶
Map OMERO’s 0-based
(row, column)onto spaCR’s well keys.This is the single most important line of the import, because everything downstream — every group-by, every plate heatmap, every well-level statistic — is keyed off
rowID/columnID, and a plate that comes in one row out is a plate whose controls are in the wrong place.Row letters are bijective base 26 via
spacr.schema.well_id(), so:well_position(0, 0).well == 'A01' well_position(25, 0).well == 'Z01' well_position(26, 0).well == 'AA01' # not '[01', not IndexError well_position(31, 47).well == 'AF48' # a 1536-well plate
- Parameters:
row – OMERO’s
WellWrapper.getRow(), 0-based.column – OMERO’s
WellWrapper.getColumn(), 0-based.
- Returns:
a
WellPosition.- Raises:
OmeroWellError – when either index is missing, not an integer, or negative. OMERO leaves both
Noneon a well that was never placed on the plate, and silently treating that as row 0 would put an unplaced well in A01 alongside a real one.
- spacr.omero.well_summary_pairs(rows: Sequence[Mapping[str, Any]], *, columns: Sequence[str] | None = None, **kwargs: Any) Tuple[Tuple[str, str], ...][source]¶
Summarise object rows and project them onto key/value pairs.
summarise_rows()followed bymeasurement_pairs(), which is the combination the per-well export always wants.- Parameters:
rows – the per-object rows for one well.
columns – restrict the summary to these columns.
kwargs – passed to
measurement_pairs().
- Returns:
a tuple of
(key, value)string pairs.
- spacr.omero.OMERO_MISSING_MESSAGE = Multiline-String[source]¶
Show Value
"""Talking to an OMERO server needs the optional `omero` extra, which is not installed in this environment (missing module: {module}). Install it with: python -m pip install "spacr[omero]" It is deliberately not part of `spacr[all]`: omero-py depends on zeroc-ice, a compiled C++ runtime, so it is only installed when you ask for it."""