spacr.qt.screens.image_import¶
Import Images — a pile of files from any microscope, read before it is written.
spacr.image_import does the work; this screen exists for the step the
instruction that asked for it calls “the single most important requirement”:
showing the proposal. spaCR’s import path before this asked the user to
pick a filename convention from a closed list — cellvoyager, cq1, or
write your own regular expression — and gave them no way to see whether it
worked until masks came out wrong. Of ten real acquisition layouts, two
parsed and eight recovered nothing.
So the middle of this screen is the parse, one row per file, with the filename beside the fields it was parsed into. A field read as a channel is visible there in a second and invisible everywhere else.
Layout:
┌──────────────────────────────────────────────────────────────────┐
│ Images /data/raw [Choose…] │
│ Sample [400] ☑ Read inside each file [Scan] │
├──────────────────────────────────────────────────────────────────┤
│ file plate well field channel z t │
│ A01/fld1_DAPI.tif plate1 A01 1 1 1 1 │
│ A01/fld1_GFP.tif plate1 A01 1 2 1 1 │
├──────────────────────────────────────────────────────────────────┤
│ token value is channel (editable — one answer per value) │
│ 0 DAPI 1 │
│ 0 GFP 2 │
├──────────────────────────────────────────────────────────────────┤
│ 8 files, 4 axes resolved … ! what is still unanswered │
├──────────────────────────────────────────────────────────────────┤
│ /data/raw_spacr plate1 ☑link ☐tiles [Save…][Load…][Import] │
└──────────────────────────────────────────────────────────────────┘
Design notes:
Scan writes nothing.
spacr.image_import.plan_import()walks a sample of the tree and reads each file’s own axis metadata;Importis a separate press, and it stays disabled while the plan has a problem — because every problem the plan states is a way for the result to be quietly wrong, and the import is the irreversible half.The proposal table is not editable, and the answer table is. A wrong guess is never wrong for one file: it is wrong for a token position, in every name that has one. So the correction is made where the mistake lives, one answer fixing every file that shares it, rather than by retyping eight thousand rows. That is also the only correction
spacr.image_import.ImportPlan.with_mapping()can apply without re-walking the disk, which is what makes editing instant.Everything the plan could not place is on screen, with its values, and counted. Silence about unparsed files is how a plate imports with a third of its fields missing.
Off the GUI thread. A scan reads every file’s header; an import of a 400-plate archive takes minutes. Both go through
spacr.qt.bridge.make_thread(), and the completion handler is reached through a bound method (ImageImportScreen._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, as in
spacr.qt.screens.foreign— a QMessageBox would hang a headless run.
Classes¶
Editable table of the tokens inference could not name. |
|
Choose a folder, read the proposal, answer what it asks, import. |
|
Read-only table over |
Module Contents¶
- class spacr.qt.screens.image_import.AnswerModel(parent=None)[source]¶
Bases:
PySide6.QtCore.QAbstractTableModelEditable table of the tokens inference could not name.
ONE ROW PER VALUE, not per token: the question “what does
DAPImean” has a different answer from “what doesGFPmean”, and a single cell holding both would have to be parsed back out of a string the user typed.An edit emits
answers_editedas well asdataChanged, and the screen listens to the former — re-resolving the plan refreshes this table’s tooltips, and a live re-resolve hung offdataChangedwould recurse.- Parameters:
parent – parent widget.
Create an empty answer table model.
- Parameters:
parent – parent object, or
None.
- columnCount(parent=QModelIndex()) int[source]¶
How many columns the answer table shows.
- Parameters:
parent – unused; the model is flat.
- Returns:
the column count.
- data(index, role=Qt.DisplayRole)[source]¶
One cell of the answer 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 answer column: the value being named is what the files actually contain, and letting it be edited would let a user rename the data rather than explain 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.
- mapping() Dict[int, Dict[str, int]][source]¶
The answers as
spacr.image_import.ImportPlan.with_mapping()wants them, skipping every row still blank or not yet a number.A HALF-TYPED ANSWER IS NOT AN ANSWER.
"1"arrives one keystroke after"", and treating a blank or a stray letter as zero would write a channel nobody asked for.
- rowCount(parent=QModelIndex()) int[source]¶
How many unnamed values are waiting for an answer.
- Parameters:
parent – unused; the model is flat.
- Returns:
the row count.
- setData(index, value, role=Qt.EditRole) bool[source]¶
Record the user’s answer for one value.
- Parameters:
index – the cell.
value – what the user typed.
role – the Qt edit role.
- Returns:
True when the answer was taken.
- set_answer(row: int, text: str) bool[source]¶
Type into one row’s answer cell, the way the editor would.
- Parameters:
row – the question row; out of range returns
False.text – the answer, normally a channel number; stored stripped.
- Returns:
True when the answer changed.
- set_plan(plan: spacr.image_import.ImportPlan | None) None[source]¶
Ask, for every token position the names could not place, what its values mean — carrying forward any answer already given.
- Parameters:
plan – the import plan whose
layout.unplacedvalues become question rows, pre-filled from itsmapping;Noneclears the table.
- class spacr.qt.screens.image_import.ImageImportScreen(parent=None, threaded: bool = True)[source]¶
Bases:
PySide6.QtWidgets.QWidgetChoose a folder, read the proposal, answer what it asks, import.
- 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 scan does not freeze the window.
Build the screen and arm its drop zone.
The drop handler also takes a saved plan, so last week’s answers arrive by the same gesture this week’s images do.
- Parameters:
parent – parent widget, or
None.threaded – run the scan and the import on a worker thread. Set
Falsein tests soscanfinishes before it returns.
- answer_question(row: int, channel: str) bool[source]¶
Answer one row of the question table, as an editor would.
- Parameters:
row – the question row; out of range returns
False.channel – the answer typed into the row, normally a channel number.
- Returns:
True when the answer changed.
- load_plan(path: str) bool[source]¶
Read a saved plan back and show it.
The folder is re-read and only the saved ANSWERS are reused, which is the point of loading one: this week’s plate may have more images than last week’s, and replaying a stale file table would silently import last week’s.
- Parameters:
path – the saved plan file, read with
spacr.image_import.load_plan().- Returns:
False, with the reason in the status line, when the file cannot be read.
- plan() spacr.image_import.ImportPlan | None[source]¶
The plan currently on screen, or None.
- proposal_value(row: int, column: str) str[source]¶
One parsed field, by row and column name.
- Parameters:
row – the zero-based row of the proposal table.
column – the column name; an unknown name or out-of-range row gives
"".
- result() spacr.image_import.ImportResult | None[source]¶
The result of the last import, or None.
- run_import() bool[source]¶
Write 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_plan(path: str) bool[source]¶
Write the plan on screen where a later run can load it back.
- Parameters:
path – destination file, written with
spacr.image_import.save_plan().- Returns:
False, with the reason in the status line, when there is no plan yet or the write fails.
- scan() bool[source]¶
Work out what the folder holds. Writes nothing.
- Returns:
True when the job was started (or, unthreaded, completed); False when the inputs are unusable, with the reason inline.
- set_destination(path: str) None[source]¶
Where the project will be written.
- Parameters:
path – the destination folder;
Noneor empty clears it.
- set_link(on: bool) None[source]¶
Whether the import symlinks rather than copies.
- Parameters:
on – truthy to symlink, falsy to copy.
- set_plate_name(name: str) None[source]¶
The plate name every written filename carries.
- Parameters:
name – the plate name;
Noneor empty clears it.
- set_read_inside(on: bool) None[source]¶
Whether the scan opens each file for its own axis metadata.
- Parameters:
on – truthy to read metadata inside each file.
- set_root(path: str) None[source]¶
Point the screen at a folder of images without opening a dialog.
Fills the destination with
<folder>_spacrwhen it is still empty, because a destination INSIDE the folder being read is theconsolidatebug this module replaces: the second run imports the first run’s output.- Parameters:
path – the folder of images to read;
Noneor empty clears the field.
- set_sample(count: int) None[source]¶
How many files the scan reads before deciding.
- Parameters:
count – the number of files; converted with
intand held to the box’s range of 10 to 1,000,000.
- set_tile_policy(policy: str) bool[source]¶
Choose what happens to a field that arrives as several tiles.
- Parameters:
policy – one of the values in
TILE_POLICIES.- Returns:
False for a policy that is not offered, with the reason inline rather than as an exception into a GUI slot.
- set_tiles_as_fields(on: bool) None[source]¶
Whether each tile is given a field number of its own.
The older two-state spelling of
set_tile_policy(), kept because it is what a caller who has not heard of stitching means: off is now the DEFAULT policy rather than the skip it used to be, since a stitched field is what it always wanted.- Parameters:
on – truthy selects the
"fields"policy, falsy"stitch".
- class spacr.qt.screens.image_import.ProposalModel(parent=None)[source]¶
Bases:
PySide6.QtCore.QAbstractTableModelRead-only table over
spacr.image_import.ImportPlan.rows().A model rather than a QTableWidget because a plate is tens of thousands of files and building that many QTableWidgetItems freezes the window.
Its header and its cells both come from the plan, so this screen and the text table
spacr.image_import.ImportPlan.table()prints cannot disagree about what was inferred — one is the other with padding.- Parameters:
parent – parent widget.
Create an empty proposal table model.
- Parameters:
parent – parent object, or
None.
- columnCount(parent=QModelIndex()) int[source]¶
How many columns the proposal shows.
- Parameters:
parent – unused; the model is flat.
- Returns:
the column count.
- data(index, role=Qt.DisplayRole)[source]¶
One cell of the proposal.
- Parameters:
index – the cell.
role – the Qt display role.
- Returns:
the cell’s value for that role, or None.
- 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.
- row(index: int) List[str][source]¶
One row’s cells, or an empty list when
indexis out of range.- Parameters:
index – the zero-based row number.
- rowCount(parent=QModelIndex()) int[source]¶
How many proposed rows the plan holds.
- Parameters:
parent – unused; the model is flat.
- Returns:
the row count.
- set_plan(plan: spacr.image_import.ImportPlan | None) None[source]¶
Show
plan’s parse, or clear the table for None.- Parameters:
plan – the import plan whose
columns()androws()fill the table, orNone.
- value_at(index: int, column: str) str[source]¶
One cell, addressed by column NAME.
The columns depend on the folder — a tiled tree has a
tilecolumn and a flat one does not — so a caller that wants the channel must ask for “channel” rather than for column 4.- Parameters:
index – the zero-based row number.
column – the column name; an unknown name or out-of-range row gives
"".
Nested helpers¶
- ImageImportScreen.run_import._job()¶
Write the project in the worker thread.
Every argument is a plain value captured above, for the same reason the scan’s is: this runs off the GUI thread, and reading a checkbox from there is undefined behaviour rather than a stale answer.
spacr/qt/screens/image_import.py:1133
- ImageImportScreen.scan._job()¶
Read the folder in the worker thread. Writes nothing.
The settings are read out of the widgets BEFORE this closes over them: a job must not touch a QLineEdit from another thread, and a user who edits the sample size mid-scan would otherwise change what the running scan is doing.
spacr/qt/screens/image_import.py:873