This page is the file SPEC.md of the spam-gui repository, shown as HTML.

spam-gui: specification for new graphical modules

This file describes how the graphical modules of spam-gui are built, how they look, and how they behave. Every new module must follow it, so that all the modules of spam-gui feel like one program.

It is written for spam developers and for Claude, together. To build a module with Claude, open Claude Code in this repository and ask, for example:

Read SPEC.md, then let's write the spec of a new module for <what the module does>, from specs/TEMPLATE.md. Then build it, following both, and check in with me after each step you build.

The specs:

File What it says
SPEC.md (this file) What is common to all the modules: the rules, the shared window, how a module is built, layout, large images, testing, how to work with the maintainers
specs/TEMPLATE.md The form of a module's spec, filled in with the maintainer before the module is written
specs/modules/<module>.md What one module does: its spam functions, its steps, its state, the decisions taken for it, its tests (e.g., specs/modules/mmr.md)
TODO.md The work planned, in order (e.g., the milestones of the reorganisation), and what « done » means for each
OPEN_QUESTIONS.md Everything waiting for the maintainer, one line each (« to decide », « to confirm »), pointing to the details
TO_SPAM.md The analysis functions written in spam-gui while spam lacks them, to move into spam (section 1)
specs/common-steps.md (planned) The steps several modules share (load two images, eye registration, crop): what they need and what they give

Words. spam-gui is made of modules: a module is one kind of work the user does in the window, with its own panel, chosen with the module button at the top left or on the command line (spam-gui <module>). The multimodal registration is a module; a display of the strains of a regular grid on the images would be another. A module may be made of steps (StepModule), or not. Never call a module a « tool ». To avoid two other meanings of « module »:

Other words with one meaning each: a step (of a module made of steps), a pane (a box of a step, numbered), the view (« Single », « Double », « Triple »: one, two or three image windows), the mouse mode (of an image window: pan and zoom, or move points).

This file does not repeat what a module's spec says: its examples are short, and point to the module's spec for more.

The multimodal registration (src/spam_gui/modules/mmr.py, spec specs/modules/mmr.md) is the reference implementation: when this file and that code seem to disagree, the code shows how it is actually done, and this file says why.

The reorganisation in progress. The code is being reorganised for more modules (section 3.4). What is marked « (planned) » here does not exist yet; until it does, the code is where it is now.


1. Ground rules

These are not negotiable.

  1. Never change spam's algorithms. The modules call spam's functions as they are (e.g., spam.DIC.multimodalRegistration). If spam gives a result that looks wrong, explain why (with figures if useful) and offer options. Don't patch spam, and don't replace its calculation with a reimplementation.

  2. No image analysis in spam-gui: it belongs in spam. spam-gui is a graphical interface: it calls spam, shows its results, and lets the user make the choices. It implements no image analysis (e.g., histograms of images, peak finding, fits, phase diagrams, registration, filters, measurements).

  3. spam-gui stays independent of spam's development. It must work with the released spam (0.9.1 or later), installed from PyPI. When a module needs an analysis function that the released spam does not have:

    • it is written in src/spam_gui/tospam/, in folders that mirror spam's packages (tospam/DIC/ for spam.DIC, so that the file moves into spam at the same place; the file's docstring names the spam package it is meant for), as it would be in spam: spam's conventions and names, a docstring, unit tests. Nothing there may import Qt, napari, or another file of spam-gui;
    • it is listed in TO_SPAM.md, at the root of the repository: what it does, which module uses it, and its status (waiting, proposed to spam, in a spam release);
    • once a released spam has it, it is removed from tospam/, the modules call spam's, and spam-gui requires that release (pyproject.toml).

    Everything that concerns spam is written in TO_SPAM.md, and that's it: functions missing in it, problems found in it (how to reproduce them, the cause, a fix), code that may belong in it. spam-gui does not work around spam's problems.

    Before writing one, check that spam has nothing that does it, and ask the maintainer. Code that only runs spam's functions by parts on images larger than the memory, or computes what is drawn on screen, is not analysis; when in doubt, list it in « To decide » of TO_SPAM.md and ask.

  4. A module is a sequence of steps. It subclasses spam_gui.gui.steps.StepModule and is listed in src/spam_gui/modules/__init__.py (MODULES). It never builds its own window, menus, or docks: the window is shared (section 2).

  5. Nothing is committed unless the maintainer asks.

  6. Each source file starts with the GPL-3.0-or-later header used in the other files.


2. The window

All modules share one window, made by spam_gui.gui.window (installSpamWindow). A module fills the parts marked module in the picture below and leaves the rest alone.

┌──────────────────────────────────────────────────────────────────────────────────────────────┐
│[Multimodal registration ▾] [1 Load images] › [2 Eye registration] › [3 Crop] › … [Summary]   │  top bar, row 1
│◧                                                                                           ◨ │  top bar, row 2
│┌ 4. Joint histogram ───────┐ ┌ 1 Z Y X 3D │1 2 1+2 ▦│✥ ↖│Fit ┐ ┌ 2 … ┐  ┌ Windows ─────────┐ │
││ ▾ 1. Grey level mapping   │ │                               │ │     │  │ View  [Single]…  │ │
││ ▾ 2. Bin and peak sel…    │ │        image window 1         │ │  2  │  │ Compare along Z Y X│
││ ▾ 3. Joint histogram …    │ │                               │ │     │  └──────────────────┘ │
││        (module: the step)   │ │                               │ │     │  ┌ ▾ Layer controls ─┐ │
│└───────────────────────────┘ │                               │ │     │  │ opacity, histogram│ │
│┌ ▾ Current Phi ────────────┐ │                               │ │     │  │ ▸ Advanced        │ │
││ [Initial][Eye][MMR]       │ │                               │ │     │  └───────────────────┘ │
││     ⎡ …  ⎤  (module: footer)│ │                               │ │     │  ┌ ▾ Layer list ─────┐ │
│└───────────────────────────┘ └───────────────────────────────┘ └─────┘  └───────────────────┘ │
│                                                                                            ◢ │
└──────────────────────────────────────────────────────────────────────────────────────────────┘

Top bar (SpamWindow.toolbar):

Left panel (a module's panel):

Centre, the image windows (spam_gui.gui.windows):

Right panels. These are fixed panes: they can't be closed, moved or floated, and each can be collapsed by clicking its title.

Bottom right: a visible triangle to resize the window.


3. Building a module

A module starts with its spec. Before any code, copy specs/TEMPLATE.md to specs/modules/<module>.md and fill it in with the maintainer: the spam functions it calls, its steps (titles, sub-panes, ready() messages), its state, what is carried between binnings. Then build it step by step (section 7), and keep the spec up to date: each decision taken with the maintainer goes in its « Decisions », with the reason.

Reuse before writing. A step that another module already has (e.g., loading two images, an eye registration, a crop) is reused, not copied (planned: modules/common/, specs/common-steps.md). If it must change for the new module, change it for all the modules, and check them all.

3.1 Skeleton

from spam_gui.gui.steps import Step, StepModule


class LoadStep(Step):
    title = "Load images"

    def __init__(self, module):
        super().__init__(module)
        ...  # build the widgets (section 4)

    def enter(self):        # the step is shown: prepare it from self.state
        ...

    def exit(self):         # another step is shown instead
        ...

    def ready(self):        # what is missing before the next step (a short sentence), or None. Must not change anything.
        return None if self.state.image is not None else "Load an image first (« Load »)."

    def leave(self):        # check (ready()), then store the results in self.state; an error message keeps the user here
        error = self.ready()
        if error:
            return error
        self.state.something = ...
        return None

    def fingerprint(self):  # a summary of the results: when it changes, the later steps are reset
        return ...

    def reset(self):        # an earlier step changed: forget what depended on it
        ...


class MyTool(StepModule):
    name = "My module"                        # the text of the module button
    description = "One sentence: what it does (tooltip of the module list, the help of the command line)."
    command = "mytool"                      # its subcommand: spam-gui mytool [its arguments]
    stepClasses = [LoadStep, ...]           # in order; a Step with anytime = True (e.g., a summary) comes last

    def makeState(self):                    # what the steps share
        ...

    def makeFooter(self):                   # optional: a pane under the step box, shown whatever the step
        return None

    @classmethod
    def addArguments(cls, parser):          # optional: its arguments on the command line (argparse), e.g. its images
        parser.add_argument("image", nargs="?", help="a TIFF file, or a folder of TIFF slices")

    def useArguments(self, args):           # optional: those given, once the window is open
        ...

Then add MyTool to MODULES in src/spam_gui/modules/__init__.py.

3.2 Steps and navigation

3.3 What the shared files provide

Use these instead of writing your own (files of spam_gui.gui, and of spam_gui.core for parts, io, phi, volumes):

Need Use
A box with a title, optionally collapsible (▾/▸) widgets.PanelBox(title, content, collapsible=True, margins=…)
Buttons side by side, one checked at a time widgets.buttonGroup(parent, layout, labels, tooltips, slot)
An on/off button widgets.toggleButton(parent, layout, label, tooltip, slot)
Widgets that wrap onto more lines in a narrow panel widgets.FlowLayout
A two-handle slider, a histogram over a slider widgets.RangeSlider, widgets.SliderHistogram
A message, a question, a file, a folder, a choice, a colour dialogs.ask, warn, openFile, saveFile, chooseFolder, chooseItem, chooseColour
A long calculation (thread, progress bar, cancel) busy.runWithProgress(parent, text, function)
Work on a large image by parts, in threads parts.runParts, parts.slabs, parts.block, parts.stage
Memory: available, fits?, readable size, scratch arrays parts.availableMemory, fitsInMemory, gigabytes, allocate, scratchArray
Read or map an image, bin it io.openVolume, io.mapVolume, io.readBinned
Φ between spam and napari, binnings, crops phi.phiToAffine, phi.convertPhi, phi.convertBox, phi.phiForCrop
A napari layer added or replaced, removed layers.setImageLayer, layers.removeLayer
A plot in the theme's colours plotting.PlotCanvas(viewer): newAxes(projection), colourbar, finish
A number box or drop-down made after the panel: the wheel must not change it while the panel scrolls module.guardWheel(box)
A short note under a button module.say(text, anchor)
The images shown in the windows; refusing a 3D view that would not fit in memory module.windows.setImages(…), windows.refuse3D = …
Information, errors napari.utils.notifications.show_info, show_error

Never call QFileDialog.getOpenFileName, QMessageBox.question, etc. directly. The functions in spam_gui.gui.dialogs place every pop-up at the centre of the window, on its screen. Otherwise, with several screens, a pop-up can open unseen on another screen and the program seems frozen.

3.4 Where the code goes

src/spam_gui/
  app.py  panel.py  napari.yaml   # the spam-gui command; the entry from napari's menu
  gui/                            # the shared window, Qt: window, windows, controls, widgets, dialogs,
                                  #   busy, steps, plotting, layers (napari's); points (pairs of points, for any
                                  #   registration module)
  core/                           # no Qt, no napari: calculations by parts, volumes computed on demand,
                                  #   reading images and binning them (io), Φ conversions (phi)
  tospam/                         # analysis functions spam does not have yet, to move into spam (section 1,
                                  #   TO_SPAM.md), in folders as spam's: tospam/DIC/ for spam.DIC, ...
  modules/
    __init__.py                   # MODULES: the modules, in the order of the module button's list
    common/                       # (planned) steps several modules share
    <module>/                       # (planned) one module: __init__.py (its StepModule), state.py, one file per step,
                                  #   what only it uses
tests/
  conftest.py                     # a temporary napari settings file for each run
  unit/                           # (planned) tests of what has no Qt (core/, tospam/, a module's calculations)
  gui/                            # the window tests: window.py (planned: the shared window, whatever the module),
                                  #   screenshots.py (planned), modules/<module>.py (planned: a module's workflow)
specs/                            # the specs (see the top of this file)
scripts/pages.py                  # the Markdown files as a website, published by GitLab Pages (.gitlab-ci.yml)

Until the reorganisation is done, the MMR module is one file (modules/mmr.py) and its phase layers are src/spam_gui/phases.py; the unit tests are tests/test_*.py, and the window test is tests/gui/workflow.py.

The rules each folder follows:


4. Layout and appearance

4.1 Organise a step as numbered sub-panes

4.2 Rows, labels, buttons

4.3 Text

4.4 A layout that doesn't jump

The user must never see panels move by themselves.

4.5 Colours

4.6 Plots


5. Large images and responsiveness

The images can be 2 to 4 GB each, larger than the memory. A module must stay responsive.


6. Testing and checking the result


7. Working with the maintainers (for Claude)


8. Checklist for a new module


9. Decisions

Choices made with the maintainers for the whole program, each with its reason and what was set aside, so that a later change does not undo them unknowingly. Those of one module are in its spec (« Decisions »). A reason not known is written « (to confirm) » and listed in OPEN_QUESTIONS.md.