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
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Needs **labconstrictor-tools 0.3.0a1 or newer** in the app's environment (the ru
* **Trust checks** on every registry entry (same rules as the Python registry and the Fiji script): the entry names its five text fields; the app name is a plain name; the interpreter lies inside the install prefix (which is not a filesystem root); the entry and its schema file are owned by you (in a shared folder also by root) and not writable by group or others; the schema sits next to the entry; the interpreter, its folder and the prefix are not writable by everybody (a sticky folder like `/tmp` excepted); tool ids and parameter names are plain names, parameter names Python identifiers (a parameter name becomes a file name). An entry that fails is listed under "skipped" with its reason and never started. On Windows the owner and permission checks do not apply.
* **Results and run records, and their clean-up** (SPEC_WRONG_SCIENCE section 3). Each run makes a results folder `~/.labconstrictor/results/<time>_<6 hex>_<app>_<tool>` (names made safe, like the command line) and a run record folder `~/.labconstrictor/runs/<time>_<app>_<tool>`. **This host never deletes anything there.** It writes the two marker files of the spec: `.lc-owner.json` first, when the folder is made (a QuPath that dies mid-run leaves an owned, unfinished folder that nobody removes), and `.lc-done.json` last, atomically, when the run ends: `ok` (completed), `cancelled` (the tool honoured Cancel, or the worker had to be killed after Cancel was pressed, or the window was closed during the run), `failed` (the tool failed, or the worker died with no Cancel pressed). The status comes from the Tools helper `labconstrictor_tools.runs.done_status`. Then it starts `labconstrictor-tools prune` (`<the app's Python> -m labconstrictor_tools prune`) in a subprocess and does not wait for it: the newest 20 finished results and the newest 50 run records are kept, and Tools removes the older ones, **only** folders that carry a valid owner marker and a done marker. Folders from older versions (no marker), foreign folders, folders of runs that are still going or that crashed with QuPath (no done marker), links and files are never removed and do not use up the quota. A prune that cannot start, or that fails, is written to the log (`prune could not be started ...` / `prune ended with exit code N`) and never fails or delays the run; the folders then stay until a `prune` works. The older folders written by earlier versions of this script have no marker, so they are never cleaned up automatically (Tools has a follow-up item for an explicit `clean --legacy`).
* **Run record** `runs/<...>/run.json`: built by the Tools helper `labconstrictor_tools.runs.build_record` and written with `record_json` (this script keeps no copy of the layout), plus `"host": "qupath"`, the `run_id` and `started_utc` of the run, and the `_calibration` input. A crash after Cancel keeps its crash text there (status `CRASHED`). If the record cannot be built (the app's Python cannot start, the helper fails) the status line says `(the run record could not be written (...); its folders stay as they are and are never removed)`. Every log line is also appended to the shared `~/.labconstrictor/logs/labconstrictor.log` (Tools line format, `qupath:` prefix, 1 MB rotation with 5 backups), so `labconstrictor-tools logs` and `support-bundle` see QuPath runs.
* **Same rules as the other hosts** (the host-parity inventory of labconstrictor-tools): a form shows parameters in the Napari/Fiji order (a group gathered where it first appears; a Collapsed group is folded where it first appears; advanced last); `clear_after_run` resets every type of field; `enabled_when` follows text, number, choice, yes/no and "set" drivers (`""` counts as unset, numbers compare as numbers); a whole-number box holds ±(2^53 - 1) at most, declared limits or not (the largest whole number a double holds exactly, Tools `notebook.MAX_FORM_INTEGER`; a typed number beyond it stops at the limit), a decimal box without declared limits ±1e15; a choice of integers is sent as an integer; a missing input says `'L' is required`; NaN is refused with `'L' must be a finite number`; a folder input must be a folder; a worker that is idle for 10 minutes is closed; a worker ended after Cancel reads `cancelled (worker stopped)`; a worker that died is explained with the Tools sentences (a missing package first, then the exit code); a tool with a parameter type this host does not know is refused alone (`this tool cannot be shown: ...`); two tools with one label are listed as `label (id)` and the apps are sorted by name; `Copy as command` writes Python text the way `repr` does (a newline cannot break the snippet) and says `there is no clipboard to copy to` when there is none; message results show `- ` items as bullets; a result file that is missing, or a result type this version does not know, is said in one sentence.
* **Same rules as the other hosts** (the host-parity inventory of labconstrictor-tools): a form shows parameters in the Napari/Fiji order (a group gathered where it first appears; a Collapsed group is folded where it first appears; advanced last); `clear_after_run` resets every type of field; `enabled_when` follows text, number, choice, yes/no and "set" drivers (`""` counts as unset, numbers compare as numbers); a whole-number box holds ±(2^53 - 1) at most, declared limits or not (the largest whole number a double holds exactly, Tools `notebook.MAX_FORM_INTEGER`), a decimal box without declared limits ±1e15, and **a typed number beyond a limit is refused, never put at the limit** (see **Number limits** below); a choice of integers is sent as an integer; a missing input says `'L' is required`; NaN is refused with `'L' must be a finite number`; a folder input must be a folder; a worker that is idle for 10 minutes is closed; a worker ended after Cancel reads `cancelled (worker stopped)`; a worker that died is explained with the Tools sentences (a missing package first, then the exit code; exit code 3 is called an import failure only when the worker's last words contain `cannot import tool module`, Tools PR #124: the Windows C runtime also ends a process with 3 for `abort()` and a raised SIGSEGV); a tool with a parameter type this host does not know is refused alone (`this tool cannot be shown: ...`); two tools with one label are listed as `label (id)` and the apps are sorted by name; `Copy as command` writes Python text the way `repr` does (a newline cannot break the snippet) and says `there is no clipboard to copy to` when there is none; message results show `- ` items as bullets; a result file that is missing, or a result type this version does not know, is said in one sentence.

## Pixel size (calibration)
A tool parameter linked to an image with `PixelSizeOf` (micrometres per pixel) is a text box with a button "Use the image's calibration". It follows the chosen image, the typed file and the image open in QuPath until you type a value (then your value stays, also when the image changes; press the button, or empty the box, to take the image's calibration again).
Expand Down Expand Up @@ -59,6 +59,13 @@ 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.

## 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.
* **Beyond a limit is refused, never clamped.** A number outside the limits (default or declared) starts no run and sends nothing: the status line says `'L' must be <= 9007199254740991` / `'L' must be >= -9007199254740991` (the worker's templates, `conformance.json` `messages.number_above_maximum` / `number_below_minimum`; a decimal limit is written the Python way, `'L' must be <= 1000000000000000.0`; a declared Max of 10 gives `'L' must be <= 10`). The typed text stays in the box, so what is refused is what is seen. This is said when the box loses focus or Enter is pressed, and again when Run is pressed. The arrows stop at the limits and never change typed text. Before this the box put a typed number at the nearest limit, so a typed 9007199254740993 was sent as 9007199254740991 and a declared Max of 10 turned 11 into 10.
* Text that is not a number is refused too: `'L': '12x' is not a number`, `'L': '5.0' is not a whole number` (a whole-number box), `'L' must be a finite number` (`nan`, `inf`), `'L' is required` (empty). SPEC 6 gives only the integer sentence; the others are this host's wording (the float sentence and the not-a-number sentences are not in the spec).
* A linked pixel size (`PixelSizeOf`) is its own text field with its own rules (positive and finite, see Pixel size); it does not use these limits.

## Run identity (SPEC_WRONG_SCIENCE section 4)
A run is placed on the image it was exported from, never on whatever is open when it ends.

Expand Down Expand Up @@ -116,7 +123,9 @@ 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_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 and typed), integer choices, required and non-finite inputs (55 checks).
`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).

`BODY=gui_test_parity_host_body.groovy LC_HOME=<scratch folder named lhome_host> tests/run_gui_test.sh` checks the tool chooser, an unsupported parameter type, folder inputs, the Python literal (also read back by Python), bullets, missing result files, a failing clipboard and the crash sentences (fake interpreters that print and exit); a missing image file is refused with the sentence, a missing table is left to the worker, a clipboard that refuses or throws is said (49 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.
- 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.

Source and issues: [LabConstrictor-QuPath](https://github.com/CellMigrationLab/LabConstrictor-QuPath).
Loading
Loading