Skip to content
Merged
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
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,20 @@ The tool receives only the area; **results come back on the right place of the s

The downsample of an export is 1 for now and is kept next to the offset (`downsample`, always 1.0), so the later resolution step changes one place.

## Tools compatibility (docs/PROTOCOL.md, "Compatibility rules for hosts")
Every app is checked when the registry is read (start, **Rescan apps**), against the cached schema the app's labconstrictor-tools wrote: `protocol` must be 1, and this host needs **labconstrictor-tools 0.3.0a1 or newer** with the features `reserved_inputs_v1` (the `_job_dir` and `_calibration` inputs) and `retention_prune` (the `prune` command; the run-record helpers arrived in 0.3.0a1). The rules are those of `labconstrictor_tools.compat.check_schema`, written in Groovy (`LcCompat`) and tested against every vector of `conformance.json` (`compat`):
* an app that lacks something is **not offered** and is listed under "skipped" with one sentence, e.g. `runs: this app's labconstrictor-tools 0.2.0rc1 is older than the 0.3.0a1 this host needs: update it`, `... lacks retention_prune: update it`, `... is too old (it lists no features, this host needs reserved_inputs_v1, retention_prune): update it`;
* an app whose version text is not understood is offered with a warning in the status line;
* a run is checked again when Run is pressed (an app registered after the window opened is checked at the next Rescan; the run check is the last line).

## Dynamic choices and the worker (SPEC_WRONG_SCIENCE section 5)
A `ChoicesFrom` parameter is a dropdown filled by another tool of the app. Before this, one thread per parameter shared one unlocked worker field, and a late answer of another form could fill the dropdown of the form shown now. Now:
* **Identity of a lookup.** Each question is `(form generation, app, tool, parameter, the values it depends on, request id)`. The generation is process-wide and never reset (it grows whenever a form is built or destroyed); the request id is per window and never reset. An answer is applied **only if all of these still hold** when it arrives (the dependencies are recomputed then, not remembered); otherwise it is dropped, with its error and its tooltip. A tool or app switched while a lookup is out can therefore never put its choices (or its error) on the new form.
* **A dependency change invalidates at once.** The moment a value the dropdown depends on changes, its choices are marked pending (the dropdown is replaced by the text field with the tooltip "waiting for the choices...") and every older question of it is stale; only the new question waits for the 400 ms pause in the typing.
* **Bounded work.** One lookup in flight per window (one thread, no thread per lookup); waiting questions are coalesced (one slot per parameter, a newer one replaces an older one that has not started); at most 8 wait, the oldest is dropped and logged; a lookup that takes longer than 60 s is ended with its worker (shortened for tests with `-Dlc.qupath.lookup_timeout_s`; the status says `... did not answer within N s`). These three numbers are `status: default` (not yet confirmed) in `conformance.json`.
* **One owner for the workers** (`LcWorkers`, one lock). A run or a lookup leases a worker when it is submitted and uses only that one until it ends. A lookup never uses the worker a run holds and never starts while a run is active (it asks again after 1.5 s); a run waits for a lookup that holds its worker. An app switch during a run never replaces the run's worker (the run goes on with it; the new app's worker is made for the next job, and the old one is closed when its last job ends). Cancel goes to the worker of the run. A worker that crashed or hit its time limit is discarded, never reused.
* **Closing the window** drops the waiting lookups, ends the one in flight, stops the lookup thread and closes every worker (a run in progress is a cancellation, see Results); nothing is started afterwards, and a worker that was being made at that moment is closed too.

## Number limits (SPEC_WRONG_SCIENCE section 6)
* A whole-number box accepts ±(2^53 - 1) = ±9007199254740991 (`conformance.json` `limits.form_integer_max`) and a decimal box ±1e15 (`limits.unbounded_float`) when the tool declares no `Min` / `Max`; a declared bound is used as declared (a whole-number bound beyond 2^53 - 1 is cut to it).
* **The text in the box is the value.** A whole number is read as an exact integer (no double ever holds it), a decimal number is compared with the limits as its exact decimal text (so `1.0000000000000002e15` and even `1.00000000000000001e15` are beyond 1e15), and the request carries exactly what was typed (`9007199254740991` stays `9007199254740991`). Surrounding spaces are not part of the number; `+5` and `007` are 5 and 7.
Expand Down Expand Up @@ -123,6 +137,10 @@ The `gui_test_parity_*` bodies need the registered `interactions` app (its Pytho

`BODY=gui_test_parity_pixel_body.groovy LC_HOME=<scratch folder named lhome_pixel> tests/run_gui_test.sh` reads the pixel size of files (ImageJ unit, centimetre TIFF unit), keeps a typed value, says non-square pixels, reports an unreadable file (15 checks).

`BODY=gui_test_conformance_body.groovy tests/run_gui_test.sh` reads `conformance.json` (`LC_CONFORMANCE`, else `python -m labconstrictor_tools.conformance --path`): it validates the file with Tools' own `--validate`, compares **every** limit, message template and host-internal code of the file with this script's constants (a key this script does not know fails: a new rule is a failing test until the host follows it), runs every `compat` vector through `LcCompat`, runs the done-status rule through the real Tools helper for every way a run can end, and keeps a ledger: every fixture id of the file is mapped to the body that runs it or to the reason it does not apply to QuPath (an id that is in the file and not in the ledger fails; the ones that cannot run here are printed as `UNSUPPORTED`).

`BODY=gui_test_async_body.groovy LC_HOME=<scratch folder named lhome_async> tests/run_gui_test.sh` runs ASY-01..08 of `conformance.json` through the real dialog with the gated source tools of the `runs` test app (a gate file is the barrier; the dependency of the dropdown is the gate path): a delayed answer that comes back after a tool switch, a dependency change and a failure, 50 changes in a second, 12 questions at once, two dropdowns, a run followed by an app switch and a lookup, a lookup past its time limit, closing the window with a lookup in flight (also while a worker is being made), and three threads leasing, releasing, reaping and closing fake workers at fixed and random interleavings.

`BODY=gui_test_numbers_body.groovy LC_HOME=<scratch folder named lhome_numbers> tests/run_gui_test.sh` runs NUM-01..05 of `conformance.json` against the `Numbers` tool of the `runs` test app (it reports the text and the Python type of what its request carried): the limits and the sentences are read from the conformance file, a typed number beyond a limit is refused with no run and no request and its text stays in the box, a declared Max, decimals that a double cannot tell apart, the text rules, an optional number, Enter and focus lost, and the arrows.

`BODY=gui_test_parity_form_body.groovy LC_HOME=<scratch folder named lhome_form> tests/run_gui_test.sh` registers a synthetic app and checks the form order, ClearAfterRun for every type, EnabledWhen for every driver, number limits (whole numbers up to 2^53 - 1, held; a typed number beyond the limit refused), integer choices, required and non-finite inputs (55 checks).
Expand Down
1 change: 1 addition & 0 deletions docs/user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Choose **Extensions > LabConstrictor tools...**, pick an app and a tool, and pre
- Results and run records are kept in `~/.labconstrictor/results` and `~/.labconstrictor/runs`. The extension never deletes them; after each run it starts `labconstrictor-tools prune`, which keeps the newest 20 results and 50 run records and removes only folders LabConstrictor made and finished. Folders from older versions of the extension are not removed automatically.
- The app's `labconstrictor-tools` must be 0.3.0a1 or newer; with an older one the run is refused and the status line says to update it.
- If you open another image while a tool runs, the results are not drawn on the new image: the status line says where the files are.
- An app whose `labconstrictor-tools` is older than 0.3.0a1 is not offered; the status line (or the list of skipped apps after **Rescan apps**) says to update it.
- A number you type that is outside the allowed range (or is not a number) is refused with a sentence like `'Threshold' must be <= 10`; it is never changed to fit, and the run does not start.
- `labconstrictor-tools doctor` shows which apps are registered.

Expand Down
Loading
Loading