Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -314,9 +314,21 @@ src/morphobricks_preprocessing/
(plan §12 Q26, Q31). Since PP-9 it holds the page methods, __init__ and
the shared helpers; the step methods live on mixin classes in the modules
below, and it re-exports every public name it had (`__all__`)
notebook_steps.py steps(): the shipped notebook's 13 steps, one at a time (PR 17a; pure
composition of the cards below); notebook_stepper.py the strip, Back/Next
and the overview; notebook_step_states.py each step's done/one-line state
notebook_steps.py steps(): the shipped notebook's 12 steps, one at a time (PR 17a; pure
composition of the cards below); notebook_stepper.py the strip, Back/Next,
the overview, the compact page (one screen per step: the cards scroll in
their own box) and about()/field_rows(); notebook_step_states.py each
step's done/one-line state
notebook_compact.py the Images and Run steps arranged for one screen: one Folders card, the
collapsed panels, and Compare groups (the former statistics page) in the
run step (redesign of 10 November 2026)
notebook_progress.py Wait: one line per wait (done of total, item, elapsed, time left once
measurable); progress_html, time_left (pure parts)
notebook_quick_scan.py the first step's quick scan: the folder walk only, one random image for
looking only, then the explicit click that reads every header
axis_example.py which planes the axes step's example shows (pure): one tile per channel,
a few time points or slices; notebook_plots.example_png draws them
pixel_size_summary.py the pixel size in one line, which images differ and where (pure)
notebook_names.py the names card's rows (conditions on step 4, replicates on step 5)
notebook_axes_quick.py the stepper's one-click Confirm these axes (Q3, PR 17b)
notebook_row_fold.py reopen: unchanged, answered rows fold; new or changed rows orange
Expand Down Expand Up @@ -746,9 +758,12 @@ Technical details go to `logging` (shown in the notebook's "Technical details" p
| Masks (Q32) | Optional masks folder outside the experiment, one subfolder per mask type mirroring the experiment's folders, or the experiment's folders with `<image>_<type>.tif` (both may be mixed; `ambiguous_mask` when a name could fit two images or both layouts), paired by leaf folder + `position_id`; OME-Zarr label images are masks too. Header-only errors (`masks_folder_not_found`, `masks_inside_experiment`, `mask_names_differ_in_case`, `duplicate_mask`, `ambiguous_mask`, `unreadable_mask`, `mask_not_2d`, `mask_size_mismatch`, `mask_pixel_type`) and warnings (`mask_missing`, `mask_without_image`, `mask_outside_type_folder`) in `check`/`run`; manifest key `masks` (optional, version 1). Optional mask step (`masks` command, `scan_masks`): kinds, relations with evidence, direct-parent hierarchy, `mask_relationships.json` (report version 1); default thresholds the user may change (`thresholds=` keyword, one CLI option each, checked by `mask_thresholds`, written in the report); suggestions only, the user names and confirms. SciPy core dependency. Confirmed by the owner: 2D masks at the image's YX, integer/bool pixels, masks folder outside the experiment. Next: notebook step and a file of the user's answers; tag v0.2.0 proposed after merge. Plane identity (issue #36): masks may have Z and/or T named in their own metadata (never guessed: unnamed axes are `mask_not_2d`); the masks of an image share their planes (`mask_planes_differ`) and `run` checks them against the image's confirmed roles (`mask_planes_mismatch`, `mask_plane_issues`), so a 2D mask is refused for a Z-stack or time-lapse; manifest mask entries gain `axes` and `shape` (optional keys, version 1); `read_mask(z=, t=)` reads one plane; `scan_masks` scans plane by plane |
| Notebook style | Follow the lab's NucleiSky and LabChronicle notebooks (see §7) |
| Widget widths | **99%**, never 100% |
| Waits | Every wait over about a second shows `notebook_progress.Wait`: done of total, the item, elapsed time, time left once measurable. Readers take a `progress` callback; no bare spinner (owner, 10 November 2026) |
| Disabled actions | Every disabled button has a visible `notebook_render.blocked_line` beside it saying what blocks it; a tooltip alone is a hidden refusal (owner, 10 November 2026) |
| One screen per step | The shipped notebook's steps fit 1366 x 768: one row of chips, explanations under *About this step*, details in collapsed panels, long lists scrolling in their own box (owner, 10 November 2026) |
| Paths and filenames in widget HTML and notebook text | **`<b>…</b>` / `**…**`, never `<code>` or inline code** |
| Things the user must confirm | **Large buttons** in a highlighted question box (`ConfirmToggle`), never small checkboxes |
| Statistics step | "Step 4 · Calculate the statistics (optional)": clearly optional, writes nothing. A CSV chooser (Browse…) is prefilled with the table just computed, otherwise empty. Not calculated automatically |
| Statistics step | **Shipped notebook since v0.5.1: the collapsed *Compare groups* panel of the run step, for that run's table; no table chooser, no second intensity check (owner, 10 November 2026). The page below remains for `results_step()` without `steps()`.** "Step 4 · Calculate the statistics (optional)": clearly optional, writes nothing. A CSV chooser (Browse…) is prefilled with the table just computed, otherwise empty. Not calculated automatically |

---

Expand Down
32 changes: 32 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,38 @@ OME-XML description is parsed (which images are marked is unchanged; a malformed
debug line without a traceback), and while the capture is attached the package's DEBUG records
go to the "Technical details" panel only, with WARNING and above still reaching the root handlers.

### Notebook redesign 4: each step fits a laptop screen; Compare groups in the run step

Owner test of v0.5.0 (10 November 2026), items (a) and (b): "simple and minimalistic".
Layout only: no answer, check, refusal, draft, description or run output changes.

- **One screen per step (1366 x 768).** The strip is one row (shorter chip names: *Images*,
*Compared to*, *Tags*, *Save*, *Describe masks*; the page keeps the full title). A step's
title and purpose are one line; its cards are in a box that scrolls by itself when they are
higher than the screen, so the title, the strip and Back/Next stay in view. Cards, tiles,
tables, buttons and pictures are denser. Each step's explanation is folded under **About
this step**; a card's help paragraph is one line that opens when pointed at.
- **Images:** one *Folders* card with three labelled rows (images, masks, output); **Scan the
folders** and **Looks right: read all image headers** side by side; after the check the scan
makes way for the result, and the pictures per folder, the check report and *Exclude images*
are collapsed panels.
- **Axes:** a refusal of the roles is one sentence with *Files and what to do* on demand
(`notebook_render.issues_brief_html`), e.g. "Error · different channel counts: The images
have different numbers of channels: 3 channels (10 images), 4 channels (4 images)."
- **Run:** *Before you run* is shown only while something is missing; *Settings and mode* and
*What will happen* are collapsed; after a run the intensity checks and plots, **Compare
groups** and the report are collapsed panels.
- **Compare groups replaces the optional statistics page** in the shipped notebook: the
grouping, the percentiles, the pooled statistics and the distribution plot of the table this
run wrote. There is no table to choose and the intensity checks are not repeated.
`results_step()` after `steps()` returns an empty hidden box, so the shipped notebook's cell
needs no change; called without `steps()` it is the page it was.
- **`welcome()` is one line** (name, version, "the notebook is ready"); the *What next?* boxes
are not shown in the stepper.
- The notebook's Markdown cells and the user guide (§4, "The app in short") describe this
flow. `RELEASE_REF` and the code cells are unchanged: the shipped file opens the redesigned
app once the v0.5.1 release moves the pin.

### Notebook redesign 3: the axes step shows the data as the roles read it; pixel size in one line

Owner test of v0.5.0 (10 November 2026), items (e) and (f). The shipped notebook has 12 steps:
Expand Down
2 changes: 1 addition & 1 deletion docs/colab-release-check.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ The notebook has three code cells, run in order: **1. Install MorphoBricks prepr
press **Check folder**.
17. - [ ] In Step 2, swap the Y and X roles. The red axis check appears, with the **Make
named-axis copies (optional)** card under it. Its button is disabled.
18. - [ ] Press **Show preview** and **Yes, the preview looks right**, then **Yes, write the
18. - [ ] Look at the example (drawn by itself) and press **Yes, the example looks right**, then **Yes, write the
copies with these roles**.
19. - [ ] Press **Browse…**: the amber card names **Copy path**. Paste a Drive folder inside
the experiment and check that it is refused. Then paste one next to the experiment.
Expand Down
57 changes: 57 additions & 0 deletions docs/user_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,63 @@ notebook's step 3 (§4), also tells how they relate.

## 4. The notebook, step by step

### The app in short (v0.5.1)

Cell 3 opens the app: a strip of twelve steps, one on screen at a time, with **Back** and
**Next** under it (**Next** is never blocked; a chip opens its step). Each step fits a laptop
screen: what grows with the experiment (folder lists, pictures, tables) scrolls inside its own
box, explanations are folded under **About this step**, and details are in collapsed panels.
Every wait shows a bar with what is done of the total, the file being read, the time elapsed
and, once it can be measured, the time left. Every greyed-out button has a line beside it
saying what it waits for.

1. **Images.** Give the folders (images; masks and output are optional). **Scan the folders**
lists every folder with its labels and image count without reading any image header, and
shows one image chosen at random, with its path, for looking only: it is not used for
anything. If this is your experiment, click **Looks right: read all image headers**; the
headers are then read and checked. Changing a folder asks for a new scan. The pictures of
each folder, the check's report and **Exclude images** are collapsed below.
2. **Axes.** One box per image dimension with its role, and beside them an **example of one
image as these roles read it**: one tile per channel (`c=0`, `c=1`, …), a few time points
or slices, or the single plane, each with its height and width. It is drawn by itself and
redrawn whenever you change a role, so a wrong role looks wrong (a strip, stripes). Another
image or other planes are under **Another image, or other planes**. Nothing is confirmed
for you: click **Confirm these axes** (offered when the headers name every axis and
nothing is open), or **Yes, the example looks right** and then **Confirm axis roles**.
- **Pixel size** is one line here: for example *Pixel size: 0.108 × 0.108 µm (from the
headers, same in all 270 images)*. When the headers give none, type it once and click
**Use for all images** (or choose *pixels: deliberately uncalibrated*); leaving it empty
records the images as uncalibrated, nothing is guessed. When the headers do not agree,
the line says how many images have which size and in which folders, and **Set per
imaging unit…** opens: there you state a size for one folder (imaging unit) or image.
**Use for all images** is your statement for every image: it replaces sizes stated per
folder, and a size that differs from an image's header is still listed as a warning.
3. **Conditions**, 4. **Replicates**, 5. **Compared against**, 6. **Image type**, 7. **Stains**,
8. **Tag channels**, 9. **Masks**: one kind of answer per step, saved as you give it.
10. **Check and save.** Your experiment in sentences, one question per replicate, then **Yes,
save the experiment description**. The line beside the button says what is still missing.
11. **Run.** **Compute histograms**; what is still to do is listed only while something is.
The settings and *What will happen* are collapsed. After the run: its result, and collapsed
panels for the **intensity checks and plots**, **Compare groups** and the report.
**Compare groups** replaces the former optional statistics page: choose how to group
(condition, biological repeat, technical repeat or image) and click **Calculate
statistics** for the pooled statistics and the distribution plot of the table this run
wrote. It writes nothing.
12. **Describe the masks** (optional), after a run that recorded masks.

Images with different numbers of channels (for example three in most folders and four in
one) are refused in this version (`different_channel_counts`): the axes step names the error in
one sentence, with the files and what to do one click away, and **Confirm axis roles** says
that this is what it waits for.

The rest of this section describes each card in detail. It is written for the same cards
shown as stacked pages (as other bricks embed them); where the app differs, the list above
is what you see. In particular: **Show preview** and **Yes, the preview looks right** are the
example and **Yes, the example looks right**; **Check folder** is **Looks right: read all
image headers**; and step 4 below is the **Compare groups** panel.

### The cards in detail

The notebook's code cells are hidden: run them from top to bottom (click a cell and press
**Shift + Enter**). Each step is a page of boxes. A blue box at the top says what the step
is for, every control has a grey help line, and one coloured message says what happened
Expand Down
10 changes: 5 additions & 5 deletions notebooks/MorphoBricks_Preprocessing.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"- **Confirms the experimental design**: folder labels, biological-repeat pairing and controls.\n",
"- **Confirms image interpretation**: axis roles and fluorescence/RGB image type. Scientific metadata are never silently guessed.\n",
"- **Computes per-plane intensity histograms** used by downstream MorphoBricks.\n",
"- **Runs intensity QC** and provides optional pooled statistics after preprocessing.\n",
"- **Runs intensity QC** and, on request, compares groups after preprocessing.\n",
"\n",
"### Before you start\n",
"\n",
Expand Down Expand Up @@ -372,12 +372,12 @@
"\n",
"In the app:\n",
"\n",
"1. Give the **experiment folder** and optional **output/masks folders**, then click **Check folder**.\n",
"2. Go through the steps one at a time with **Next** (or click a step in the strip at the top): axes, pixel sizes, conditions, replicates, comparisons, image type, stains, channel tags and masks.\n",
"1. Give the **experiment folder** (masks and output folders are optional) and click **Scan the folders**. Look at the folders and the example image, then click **Looks right: read all image headers**.\n",
"2. Go through the steps one at a time with **Next** (or click a step in the strip at the top): axes and pixel size, conditions, replicates, comparisons, image type, stains, channel tags and masks.\n",
"3. On **Check and save**, read your experiment in words and save it; then **Run** computes the histograms.\n",
"4. Review the QC results; pooled statistics are optional.\n",
"4. After the run, the intensity checks and **Compare groups** are collapsed panels of the run step.\n",
"\n",
"Detailed explanations appear inside the app only where a decision is required."
"Every wait shows its progress, and a greyed-out button says beside it what it waits for."
]
},
{
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ baseline = "tools/code_rules_baseline.json"
"morphobricks_preprocessing.run_labels" = "io"
"morphobricks_preprocessing.notebook_axes" = "ui"
"morphobricks_preprocessing.notebook_progress" = "ui"
"morphobricks_preprocessing.notebook_compact" = "ui"
"morphobricks_preprocessing.notebook_quick_scan" = "ui"
"morphobricks_preprocessing.notebook_axes_quick" = "ui"
"morphobricks_preprocessing.notebook_channel_thumbs" = "ui"
Expand Down
2 changes: 2 additions & 0 deletions src/morphobricks_preprocessing/notebook_axes_quick.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ def _refresh_quick_axes(self, drawn: bool = False) -> None:
for widget in (self.preview_checked.widget, self.preview_blocker, self.confirm_button,
self.confirm_blocker): # fmt: skip
ui.set_visible(widget, not offered)
# The offer says the roles are the headers' and nothing is open: the line is not repeated.
ui.set_visible(self.axis_status, not offered)
if offer is None:
return
if offer.roles != self.mapping.roles:
Expand Down
6 changes: 2 additions & 4 deletions src/morphobricks_preprocessing/notebook_axis_preview.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,10 +200,8 @@ def _preview_label(self) -> str:
return ui.status("ok", "Preview with the confirmed axis roles.")
return ui.status(
"info",
"Preview of the roles above, not confirmed yet.",
[
"Nothing is written: the run uses only the roles you confirm below.",
],
"Preview of the roles above, not confirmed yet. Nothing is written: the run uses "
"only the roles you confirm.",
)

def _preview_header(self): # noqa: ANN202
Expand Down
26 changes: 15 additions & 11 deletions src/morphobricks_preprocessing/notebook_axis_step.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,20 @@
from .image_type import FLUORESCENCE
from .notebook_axes import axis_check_html, header_marks
from .notebook_options import IMAGE_TYPE_OPTIONS, ROLE_OPTIONS
from .notebook_render import blocked_line, issues_html, plane_summary_html, role_effect
from .notebook_render import (
blocked_line,
issues_brief_html,
plane_summary_html,
role_effect,
)
from .run import Preflight
from .validation import plural

ROLES_HELP = (
"One box per dimension, in the order stored in the files. Exactly one Y and one X; a "
"dimension with more than one plane needs Z, C or T."
)


class _AxisStep:
"""Step 2's axis roles: the role dropdowns, the axis check and the missing-axes card."""
Expand Down Expand Up @@ -131,6 +141,7 @@ def _build_preview_card(self) -> None:
],
)
self.axis_sizes_slot = widgets.VBox() # the pixel size card, in the shipped notebook
self.roles_help = ui.help_text(ROLES_HELP) # folded into About this step there

def _build_image_type_card(self) -> None:
"""Build the image type card, hidden until the axis roles are confirmed."""
Expand Down Expand Up @@ -182,14 +193,7 @@ def _refresh_axis_step(self) -> None:
self._set_locked(False)
roles = ui.card(
"Axis roles",
[
ui.help_text(
"One box per dimension, in the order stored in the files. Exactly one Y "
"and one X; a dimension with more than one plane needs Z, C or T."
),
grid,
self.plane_summary,
],
[self.roles_help, grid, self.plane_summary],
)
left = widgets.VBox(
[roles, self.missing_card, self.axis_status, self.axis_sizes_slot],
Expand Down Expand Up @@ -279,7 +283,7 @@ def _role_grid(self) -> widgets.GridBox:
return widgets.GridBox(
axis_cards,
layout=widgets.Layout(
grid_template_columns="repeat(auto-fill, minmax(240px, 1fr))", grid_gap="10px"
grid_template_columns="repeat(auto-fill, minmax(170px, 1fr))", grid_gap="8px"
),
)

Expand Down Expand Up @@ -368,7 +372,7 @@ def _mapping_changed(self) -> None:
if review.issues:
others = [i for i in review.issues if i.code not in AXIS_CHECK_CODES]
self.axis_status.value = axis_check_html(review) + (
issues_html(others) if others else ""
issues_brief_html(others) if others else ""
)
self.axis_next.value = ""
else:
Expand Down
Loading
Loading