spacr.qt.screens.foreign¶
Import Project — somebody else’s images, masks and measurements, reviewed column by column before any of it becomes a spaCR database.
spacr.foreign does the work; this screen exists for the one step
that cannot be automated. Their Area is in µm², spaCR’s cell_area
is in px², and the difference between an import that is right and one that
is plausibly wrong by a factor of several hundred is whether a human
looked at the mapping table. So the mapping table is the middle of the
screen, it is editable, and every edit re-runs the conflict and unit
checks in place.
Layout:
┌──────────────────────────────────────────────────────────────────┐
│ Images /data/theirs/images [Choose…] │
│ Masks [cell ▾] /data/theirs/cell_masks [Choose…] [Add] │
│ cell -> /data/theirs/cell_masks [Remove] │
│ Table /data/theirs/results.csv [Choose…] │
│ µm per pixel [0.65] On conflict [Refuse ▾] [Preview] │
├──────────────────────────────────────────────────────────────────┤
│ their column → target transform unit_in unit_out │
│ Area (um^2) foreign_area_um2 area um^2 px^2 │
│ MeanIntensity foreign_meanint… identity │
│ … (target/transform/units │
│ are editable in place) │
├──────────────────────────────────────────────────────────────────┤
│ NOT MAPPED (1): Notes │
│ CONFLICTS (1): [shadows_spacr] 'cell_area' -> 'foreign_cell_area'│
├──────────────────────────────────────────────────────────────────┤
│ /data/imported [Save mapping…] [Load mapping…] [Import] │
└──────────────────────────────────────────────────────────────────┘
Design notes:
Preview writes nothing.
spacr.foreign.plan_import()scans, pairs and verifies;Importis a separate press, and it stays disabled while the plan has a blocking problem — a target that collides with a spaCR feature name, a measurement table with no object-label column, a field whose masks do not pair.Editing is instant and local. A cell edit calls
spacr.foreign.ImportPlan.with_column_maps(), which re-resolves the columns without touching the disk, so the unmapped list, the conflict list and the Import button update as you type.Off the GUI thread. Scanning a plate of TIFFs and reading every label image takes seconds; the import takes minutes. Both go through
spacr.qt.bridge.make_thread(), and the completion handler is reached through a bound method (ForeignScreen._job_settled) rather than a closure, becausePipelineWorker.finishedis emitted in the worker thread and a closure connected to it would build widget children there. Tests passthreaded=False.No modal dialogs on any error path. Everything lands in the inline status label and the report pane. A QMessageBox would hang a headless run.
Classes¶
Editable table over a list of |
|
Pick their files, review the mapping, import, read the summary. |
Functions¶
|
Put Import's fold strip on |
|
Every filename convention Import can read, as |
|
What a mask class is CALLED, as opposed to what it is keyed by. |
|
How many organelle slots a form should show, given what is filled. |
Module Contents¶
- class spacr.qt.screens.foreign.ColumnMapModel(parent=None)[source]¶
Bases:
PySide6.QtCore.QAbstractTableModelEditable table over a list of
spacr.foreign.ColumnMap.A model rather than a QTableWidget because a CellProfiler export has several hundred columns and building that many QTableWidgetItems freezes the window.
An edit emits
mapping_editedas well asdataChanged, and the screen listens to the former. Hanging the live re-resolve offdataChangedlooks equivalent and is not: re-resolving updates the per-row status, updating the status refreshes the tooltips, and refreshing tooltips emitsdataChangedagain — one keystroke recursed until the interpreter ran out of stack.- Parameters:
parent – parent widget.
Build an empty column mapping.
- Parameters:
parent – parent object.
- columnCount(parent=QModelIndex()) int[source]¶
How many columns the mapping table shows.
- Parameters:
parent – unused; the model is flat.
- Returns:
the column count.
- data(index, role=Qt.DisplayRole)[source]¶
One cell of the mapping table.
- Parameters:
index – the cell.
role – the Qt display role.
- Returns:
the cell’s value for that role, or None.
- flags(index)[source]¶
Which cells the user may edit.
Only the target column: the source name is what the foreign file actually contains, and editing it would let a user rename their data rather than map it.
- Parameters:
index – the cell.
- Returns:
the Qt item flags.
- headerData(section, orientation, role=Qt.DisplayRole)[source]¶
One header label.
- Parameters:
section – the row or column number.
orientation – which header.
role – the Qt display role.
- Returns:
the label, or None.
- map_at(row: int) spacr.foreign.ColumnMap | None[source]¶
One row’s mapping, or None when
rowis out of range.- Parameters:
row – zero-based table row.
- maps() List[spacr.foreign.ColumnMap][source]¶
The mapping as it currently stands, including every edit.
- rowCount(parent=QModelIndex()) int[source]¶
How many source columns are waiting to be mapped.
- Parameters:
parent – unused; the model is flat.
- Returns:
the row count.
- row_of(source: str) int[source]¶
The row index for a source column name, or -1.
- Parameters:
source – the foreign table’s column name, compared as a string with each mapping’s
source.
- setData(index, value, role=Qt.EditRole) bool[source]¶
Record which spaCR column a source column maps to.
- Parameters:
index – the cell.
value – what the user chose.
role – the Qt edit role.
- Returns:
True when the mapping was taken.
- set_maps(maps: List[spacr.foreign.ColumnMap] | None) None[source]¶
Replace the whole mapping.
- Parameters:
maps – the
spacr.foreign.ColumnMaprows to show, in order;Noneempties the table.
- set_status(status: Dict[str, str] | None) None[source]¶
Attach a per-source status (
mapped/uncalibrated/ …).Shown as the row’s tooltip, so a user can see why a column was renamed without leaving the table.
- Parameters:
status – mapping of source column name to status word;
Noneclears every status.
- class spacr.qt.screens.foreign.ForeignScreen(parent=None, threaded: bool = True)[source]¶
Bases:
PySide6.QtWidgets.QWidgetPick their files, review the mapping, import, read the summary.
- Parameters:
parent – parent widget.
threaded – when False every job runs inline on the calling thread. Tests use it so assertions are exact; the app leaves it True so a long import does not freeze the window.
Build the importer: images, masks, table and mapping.
- Parameters:
parent – parent widget.
threaded – whether the import runs on a worker.
- add_mask_folder(object_type: str, path: str) bool[source]¶
Register a mask folder for one object class.
- Parameters:
object_type – storage name of a mask class in
OBJECT_CHOICES, e.g."cell"; surrounding whitespace is ignored.path – folder holding that class’s masks; replaces any folder already registered for the class.
- Returns:
False when the class is unknown or the path is blank, with the reason inline — never an exception into a GUI slot.
- clear_mask_folders() None[source]¶
Forget every mask folder, so a new set does not inherit the last.
- column_maps() List[spacr.foreign.ColumnMap][source]¶
The mapping as edited — exactly what Import would apply.
- load_mapping(path: str) bool[source]¶
Read a mapping back and apply it to the plan on screen.
- Parameters:
path – column-map CSV written by
save_mapping()orspacr.foreign.save_column_map(), possibly edited. A read error is shown in the status line and returnsFalse.
- pixel_size() float | None[source]¶
The scale as a number, or None when blank or unparseable.
None is a real answer, not a failure: a column that needs a pixel size and does not have one is reported uncalibrated rather than scaled by 1.0.
- plan() spacr.foreign.ImportPlan | None[source]¶
The plan currently on screen, or None.
- preview() bool[source]¶
Scan, pair and verify. Writes nothing.
- Returns:
True when the job was started (or, unthreaded, completed); False when the inputs are unusable, with the reason in the inline status label.
- remove_mask_folder(object_type: str) bool[source]¶
Forget one object class’s mask folder.
- Parameters:
object_type – storage name of the mask class; returns
Falsewhen no folder is registered for it.
- result() spacr.foreign.ImportResult | None[source]¶
The result of the last import, or None.
- run_import() bool[source]¶
Build the project. Off the GUI thread unless
threaded=False.- Returns:
True when the job was started, False when it was refused — with the reason inline.
- save_mapping(path: str) bool[source]¶
Write the mapping on screen to a reviewable CSV.
- Parameters:
path – destination CSV file, written by
spacr.foreign.save_column_map(). ReturnsFalsewhen there is nothing to save or the write fails.
- set_custom_regex(pattern: str) None[source]¶
Set the pattern the Custom convention parses by.
- Parameters:
pattern – a regular expression with named groups
wellID,fieldIDandchanID;''clears it.
- set_destination(path: str) None[source]¶
Set the destination project root.
- Parameters:
path – folder the import is written to;
Noneor empty clears the field.
- set_images(path: str) None[source]¶
Point the screen at their image folder without opening a dialog.
- Parameters:
path – the foreign image folder. When the destination is still empty it is filled with this path plus
_spacr.
- set_mapping_value(row: int, key: str, value: str) bool[source]¶
Edit one cell the way the delegate would.
The screen’s own edit path, exposed so a test drives the same code an editor widget does rather than reaching into the model.
- Parameters:
row – zero-based mapping row.
key – column name from
MAP_COLUMNS, e.g."target"; an unknown or read-only column returnsFalse.value – new cell text; surrounding whitespace is stripped.
- set_measurements(path: str) None[source]¶
Point the screen at their measurement table.
- Parameters:
path – the foreign measurement table file;
Noneor empty clears the field.
- set_metadata_type(key: str) bool[source]¶
Choose the filename convention the files are read by.
- Parameters:
key – a key from
naming_choices();''means folders.- Returns:
False, with the reason inline, for a key the list lacks.
- set_on_conflict(value: str) None[source]¶
Choose refuse-or-rename for colliding targets.
- Parameters:
value –
"refuse"or"rename", as inCONFLICT_CHOICES; anything else raisesValueError.
- set_pixel_size(value: Any) None[source]¶
Set the µm-per-pixel scale; None or ‘’ means unknown.
- Parameters:
value – micrometres per pixel, as a number or text; it is shown as typed and parsed later by
pixel_size().
- spacr.qt.screens.foreign.install_folds(screen: PySide6.QtWidgets.QWidget) spacr.qt.widgets.fold_strip.FoldStrip | None[source]¶
Put Import’s fold strip on
screen’s masthead.
- spacr.qt.screens.foreign.naming_choices() List[Tuple[str, str]][source]¶
Every filename convention Import can read, as
(key, label).The SAME list Mask’s
metadata_typeoffers, read fromspacr.regex_infer._metadata_convention_menu(), so a plate Mask can parse is a plate Import can parse and the two lists cannot drift. Folders come first because they are what Import did before it knew any convention; the vendor conventions follow in Mask’s vendor order, each labelled with its vendor and marked when spaCR is guessing about it.- Returns:
the choices,
NAMING_AUTOfirst.
- spacr.qt.screens.foreign.object_label(role: str) str[source]¶
What a mask class is CALLED, as opposed to what it is keyed by.
NO USER SEES A LETTER, asked for in as many words on 2026-09-02: “i dont want to see organelle a b c d anywhere, organelle number should always be controlled by number of organelles”. The slots are stored lettered –
organelle,organelleb,organellec– because a role name is the prefix of every settings key it owns andorganelle2cannot round-trip through aprcfokey without colliding with the object LABELLED 2. That is a storage decision and it has no business on screen, where a slot is a NUMBER: Organelle 1, Organelle 2.- Parameters:
role – a role from
OBJECT_CHOICES.
- spacr.qt.screens.foreign.organelle_slots_offered(added: Sequence[str]) int[source]¶
How many organelle slots a form should show, given what is filled.
THE COUNT FOLLOWS THE ORGANELLES, not a constant. Offering four slots to a user with one organelle is three questions nobody asked, and offering exactly the ones in use plus one is what “controlled by the number of organelles” means on a form that has no separate count field: adding the first makes the second available, and so on for as many as the mask planes can carry.
- Parameters:
added – the roles already mapped.
- Returns:
how many organelle slots to list, at least one.
Nested helpers¶
- ForeignScreen.preview._job()¶
Plan the foreign import. Off the GUI thread.
spacr/qt/screens/foreign.py:988
- ForeignScreen.run_import._job()¶
Run the foreign import. Off the GUI thread.
spacr/qt/screens/foreign.py:1158