Architecture
This page is for whoever maintains cellmap-flow. It says what each module is
for, how the main requests travel through them, where state is kept, and the
conventions a change has to keep. It describes the package as the 0.3.0
cleanup (PR #103) leaves it. Each module’s docstring says more about it, and
CHANGELOG.md at the repository root says what changed and why.
Paths below are relative to cellmap_flow/.
Module map
Reading data and running a model
Module |
What it is for |
|---|---|
|
Reading datasets. It imports nothing from the rest of the package (no
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Models
Module |
What it is for |
|---|---|
|
One module per model type. |
|
The classes’ public import path, which the docs and plugins use, and
|
|
The model types (every |
|
|
|
A model’s geometry without building the model: from a running
server’s |
|
The cellmap models on the Hugging Face Hub, for the Models tab. |
|
Merging several models’ outputs in a blockwise run ( |
|
The Models tab’s catalog. |
|
A deprecated alias; see Deprecated in 0.3.0. |
Serving and jobs
Module |
What it is for |
|---|---|
|
|
|
The one place a server’s command line is built ( |
|
What a layer URL carries to a server: |
|
The served zarr’s metadata and chunk-key arithmetic, with no Flask. |
|
|
|
What a model’s output range says about the postprocessing it needs. |
|
The secret a finetune job’s server takes a restart with. |
|
Running jobs on LSF or on this machine, without importing
|
The viewer and the dashboard
Module |
What it is for |
|---|---|
|
The neuroglancer viewer. It never imports or starts the dashboard;
callers pass what a layer needs. (The raw layer is read through
|
|
The Flask app: its blueprints, the log panel’s handler, and
|
|
|
|
The typed bodies of the POST routes (pydantic), and |
|
How far a long request has got (a YAML crop import, a resume), under
the |
|
The job manager’s listener: a finetuned model’s viewer layer, and its
|
|
Hands the session’s MinIO state and volume records to
|
|
|
|
One blueprint per area:
|
|
The two pages; below. |
The pages are ES modules served as they are, with no bundler:
dashboard/templates/
index.html the dashboard page; includes _dashboard.html and the
_input_tab, _output_tab, _models_tab, _finetune_tab
and _review_tab partials
pipeline_builder_v2.html the pipeline builder
bbox_json_template.html
dashboard/static/css/ tokens.css (the palette), dark.css, pipeline_builder.css
dashboard/static/js/
lib/ api, dom, page-data, poll, sortable, sse: know no page
shared/ gpu-queues, op-chain, server-config: used by both pages
dashboard/ main.js (the page's entry), connect, models-tab,
model-advice; finetune/ and review/, each an index.js
that wires one module per part of its tab
pipeline-builder/ main.js (the entry), state, canvas, nodes, palette,
io, blockwise, bbx, dialogs, log-panel, messages,
model-config-modal, output-channels
vendor/js-yaml.js
A template hands its scripts data as JSON, in
<script type="application/json" id="page-data">, which
lib/page-data.js reads. Nothing is put on window and no markup has an
inline handler. Build DOM with lib/dom.js’s h() rather than HTML
strings, so that names from a file or a server never run as script.
Blockwise and finetuning
Module |
What it is for |
|---|---|
|
Running models over a whole volume, to disk, with daisy.
|
|
The training job’s entry point, |
|
The trainer’s flags ( |
|
|
|
What each iteration leaves: |
|
The lines the trainer prints for the job manager to find in its log. |
|
|
|
The masked losses; annotations (0 unannotated, 1 background, 2 and up objects) as training targets and masks. |
|
|
|
|
|
What the trainer trains on: |
|
A finetune session on disk, with no Flask, neuroglancer, torch or
|
|
Finetune jobs from the dashboard’s side: |
|
The schema of a YAML crops manifest; building a corrections directory from one without a dashboard. |
|
Folding a finetune into the base weights and re-exporting it at a larger tile. |
|
The YAML that serves a finetuned model. |
Entry points and process-wide modules
Module |
What it is for |
|---|---|
|
The |
|
|
|
Plugins in |
|
|
|
Deprecated, and gone after 0.3.0: |
|
The Review tab’s SQLite index: reading it, and building one
( |
|
Two deprecated aliases and nothing else. |
Request flows
A chunk served to Neuroglancer
A prediction layer’s source is
zarr://<server>/<model>__CFLOW_ARGS__<blob>__CFLOW_ARGS__(viewer.layers.prediction_source). The blob is the layer’s chain,PipelineSpec.to_url_blob(...): minified JSON in URL-safe base64 (serving.protocol.encode_to_str). Every request Neuroglancer makes for the layer carries it.Neuroglancer asks the model’s
CellMapFlowServerfor<dataset>/.zattrs,<dataset>/s0/.zarray, then chunks<dataset>/s0/<z>.<y>.<x>[.<c>].serving.virtual_zarrworks out each answer: the OME attributes, the array’s shape and dtype under the chain, and the world ROI of a chunk key.CellMapFlowServer.chain_forfinds the blob (serving.protocol.split_dataset_url), decodes it and builds its steps (PipelineSpec.from_json_data(..., strict=True).build()), once per URL; the last 32 chains are kept. A URL without a blob gets the process’s chain,process_chain(), which is empty in a server started from the command line; the log then warns that the model is fed raw voxel values.Inferencer.process_chunkpoints the server’sImageDataInterfaceat the layer’s normalizers (with_input_norms) and asksModelRunner.predictfor the chunk. The runner grows the ROI by the model’s context and reads it withImageDataInterface.to_ndarray_ts:io.geometry.Grid.world_to_boxturns the world ROI into voxels, andio.source.read_paddedreads them through the input chain, padding what lies outside the array. A config with its ownprocess_chunk(TensorFlow, ONNX, cellpose, bioimage.io) runs that instead.The forward runs inside a
DeviceSlotsslot: by default one chunk at a time on the GPU, first come first served (CELLMAP_FLOW_GPU_SLOTS). A chunk whose client hung up before its turn is answered 499 without being computed.The
(C, *spatial)output goes through the layer’s postprocessors (inferencer.apply_postprocess), is put in the zarr’s axis order, cast to the dtype the chain declares (pipeline_spec.chain_output_dtype) and encoded. A postprocessor that merges segments across chunks posts its equivalences to<dashboard_url>/update/equivalences, at most every 5 s.
The same chunk from the chunkmirage engine
By default (CELLMAP_FLOW_ENGINE=chunkmirage; flask for the server
above, which is also used where chunkmirage cannot be imported;
serving.engine.make_server) the server is
serving.chunkmirage_server.ChunkmirageServer, built on
chunkmirage, which reads,
caches, schedules and serves; cellmap-flow keeps the model, its chains and
the routes the dashboard reads. The result is the same voxels in the same
place, with any channel axis first.
The layer’s source is
zarr://<server>/<model><ARGS_KEY><blob><ARGS_KEY>/zarr: the same name, then chunkmirage’s format. The dashboard learns which engine a server runs from"engine": "chunkmirage"in its model_info (viewer.layers.prediction_url); a server that does not say is the Flask one, so a dashboard still draws an older finetune job’s layer.chunkmirage resolves an unknown name on its first request (
ChunkmirageServer.pipeline_for) into a pipeline ofserving.chunkmirage_ops.layer_ops: the raw data (the level and grid theImageDataInterfacechose, as a chunkmirage source); anInferenceOp, the model on the layer’s normalizers; onePostprocessOpper postprocessing step; and chunkmirage’scastif the chain leaves another dtype than the one served.chunkmirage reads each chunk’s input with the model’s halo (zeros past the data) and queues it for the device, finest level first, dropping chunks nobody waits for any more (
InferenceOp.slots, fromCELLMAP_FLOW_GPU_SLOTS).InferenceOphands the model aBlockInputfor itsto_ndarray_ts, which applies the normalizers to what lies inside the data and pads after, asImageDataInterface.to_ndarray_tsdoes; a chunk the data ends in is computed whole and cropped.The model’s output is cached (
CELLMAP_FLOW_PREDICTION_CACHE_BYTES, 16 GiB by default, 0 for none): panning back, or changing only the postprocessing, does not run the model again. Its cache key includes the model’s weights version, which the finetune loop bumps after each iteration (ChunkmirageServer.weights_changed).
Submit, or PUT /api/pipeline, and the layers
The dashboard page’s Submit reads the ticked ops of the Input and Postprocess tabs (
static/js/shared/op-chain.js) and sendsPUT /api/pipelinewith{input_norm, postprocess}(static/js/dashboard/main.js). The pipeline builder sends the same, and its canvas asbuilder, 2 s after its last edit (static/js/pipeline-builder/state.js).dashboard/routes/pipeline.put_pipelineparses the body as aPipelineUpdatethroughdashboard.requests.parse(an unknown op is a 400), then builds the steps (a value an op’s class refuses is a 400). Nothing has changed yet._set_chain_and_redrawmakes it the chain withSession.set_pipeline, which sets the live steps and their config together, and keeps the canvas if one was sent. With no viewer yet (no dataset opened) it stops there.The layers’ source is named by the chain’s digest,
spec.to_url_blob(dashboard_url=..., digest=spec.digest()). The same chain sent again gives the same URL, so Neuroglancer keeps the chunks it has, and each server reuses the chain it built.It keeps the shaders the user set in the viewer, dropping a layer’s contrast range when its chain changed. Then, in one
viewer.txn(), it rebuilds the raw layer (viewer.layers.raw_layer) and a prediction layer for each job that has a host (viewer.layers.prediction_layer, given the server’smodel_infofromserving.client.fetch_model_info). A chain that ends in labels gives a segmentation layer, and the server’seffective_output_voxel_sizeplaces the layer.The answer is
{"success": true, "pipeline", "digest", "layers"}; see Viewer layer API. The layers’ next chunk requests carry the new blob.
Launching a model server
A model config comes from
cellmap_flow infer <type>(cli/infer.py: a command per type inmodels.registry.model_types(), its options fromregistry.click_options, its strings throughcoerce_cli_args), or from a YAML (cli/yaml_cli.py:config.yaml.load_config,registry.build_models). The Models tab’s catalog and Hugging Face picks (POST /api/models,dashboard/services/launch.py) need no config for the command, only a type and its arguments.serving.launchbuilds the command line: the words ofjobs.launch.SERVER_COMMAND(cellmap_flow serveby default), then--modeland the model’s entry as JSON (ModelConfig.launch_entry,to_dict()without None, inserver_argv; or the type and arguments, inserver_argv_for), then-d <data path>.jobs.launch.start_hostssubmits it to LSF with one GPU (submit_bsub_job, thenjobs.lsf.submitandbsub_argv). When a queue does not start the job it tries the next ofjobs.queues.candidates. The caller gives the queue and charge group (the CLIs default to the saved settings); the walltime is the caller’s, else the saved setting (jobs.settings.launcher_settings()), elsejobs.site’s. It runs the server on this machine (jobs.local) only when bsub is not installed; when bsub is there and every queue fails, it raisesJobStartError.It waits for the server’s address: from the ready file named in the job’s environment (
jobs.ready), else from theCELLMAP_FLOW_SERVER_IP(...)marker in the job’s output, through bpeek. Then it adds the job tojobs.launch.started_jobs().On the GPU node,
cellmap_flow serve --model <entry> -d <data path>(cli/server_cli.py) rebuilds the config withregistry.build_model, as a YAML’s model entry is rebuilt, and startsCellMapFlowServer. ItsInferencerloads the model, and the warmup forward checks the declared shapes, probes the output range (formodel_info) and keeps fp16 only if it agrees with fp32 (CELLMAP_FLOW_HALF_PRECISION).run()prints the address marker, writes the ready file and serves.The launcher adds the model’s layer (
viewer.layers.prediction_layer). The CLIs do it indashboard.services.startup.generate_neuroglancer_url, which then serves the dashboard and does not return.
A blockwise run
The pipeline builder’s blockwise steps post to
/api/blockwise/validate,/generate,/precheckand/submit(dashboard/routes/blockwise.py). Each answers 200, withvalidorsuccessfalse and anerrorwhen it cannot go on. Generate writes the task YAML (one per box when each box gets its own zarr) underblockwise_tasks_dir; precheck isblockwise_processor.precheck, which loads no model and writes nothing. Submit sends the master to LSF as a CPU job:python -m cellmap_flow.blockwise.multiple_cli <task YAMLs>. From a shell,cellmap_flow blockwise <task.yaml>runs one master.The master,
CellMapFlowBlockwiseProcessor(yaml, create=True), reads the YAML (config.yaml.load_config,registry.build_models,resolve_data_path), builds noInferencer(it needs the geometry only), and creates the output arrays, with OME attributes fromio.ome.singlescale_attrs, on a grid that starts at the corner of the raw level the model reads.run()makes one daisy task per region (the whole extent, or each bounding box), with the idpredict_<model>_<task>. Daisy spawns the workers throughspawn_worker: each is an LSF job, submitted withjobs.launch.submit_bsub_job, that runscellmap_flow blockwise <task.yaml> --client, with a walltime and its own log.A worker builds an
Inferencerper model, takes blocks from daisy and runsInferencer.process_chunkon each, merging several models withmodels.model_merger. Withtrack_progressit writes a marker per finished block undertmp_dir, and a resumed run skips the blocks that have one.The master exits 1 if any block failed or never ran.
A finetune job
The Finetune tab posts
/api/finetune/submit.dashboard/routes/finetune/training.pyparses it (requests.FinetuneSubmit), brings the session’s_virtual_sources.jsonup to date, settles the loss and target type (painted, sparse annotations change them), has the viewer’s listener follow the manager (finetune_layers.follow_jobs), and callsFinetuneJobManager.submit_finetuning_job.The manager (
finetune/job_manager/manager.py) works out what to run withsubmit(the model type, checkpoint, corrections, channels and voxel sizes), makes the run’s directory<session>/runs/<model>_<ts>/with itsrestart_tokenandmetadata.json(persistence), and launchessubmit.build_command’s line withsubmit.launch: on LSF with one GPU, or here without bsub. The line runspython -m cellmap_flow.finetune.finetune_cliand pipes it throughteeinto the run’straining_log.txt. A monitor thread follows the job.The trainer (
finetune_cli.main) parses its flags (finetune.cli), loads the model (model_loading.load_trainable_model), prepares it (adaptation), and runssession_loop.TrainingSession. Each iteration trains onfinetune.data’s patches withlora_trainer, exports intoiterations/NNN_<ts>/and writes a serving YAML (run_outputs), and printsFINETUNED_MODEL_YAMLandTRAINING_ITERATION_COMPLETE(markers.emit). Without--auto-servethe job ends there. With it, the job starts aCellMapFlowServeron the model it trained, in the same process (its address marker goes into the same log), printsWAITING_FOR_RESTART, and waits for/__control__/restartor arestart_signal.json.Every 3 s the monitor (
job_manager/monitor.py) asks the scheduler about the job (state.on_scheduler_status) and reads the log’s new lines (tailer.LogTailer): epochs and loss, status markers (state.on_status_marker), the server’s address and each finished iteration. It records each change inmetadata.json. A job the scheduler says has completed is COMPLETED only once its export is found (monitor.complete_job), and FAILED if it is not.The monitor tells the listeners
on_server_readyandon_iteration_complete. The dashboard’sFinetuneLayerListener(dashboard/finetune_layers.py) answers with the model’s layer, served by the job’s server, and aFinetuneModelConfigamong the session’s models, so the pipeline builder offers it.The tab polls
/api/finetune/job/<id>/statusand streams.../logs/stream(server-sent events,static/js/lib/sse.js), and stops at a final status. A restart (.../restart) syncs the annotations from MinIO, thenjob_manager/restart.pyPOSTs the new settings to the job’s/__control__/restartwith the run’s token inX-Restart-Token, or writes them torestart_signal.jsonwhen the server can’t be reached. A dashboard started later finds a session’s jobs again from theirmetadata.jsonand one bjobs call (persistence.rehydrate).
Annotation volumes and MinIO sync
New Volume posts
/api/finetune/create-volume(routes/finetune/annotation_core.py). The model’s geometry comes frommodels.geometry_cache.resolve_model_geometry: its running server, else the cache, else a build.finetune.session.volume.plan_volumesizes the volume over the whole raw dataset, one chunk per model output, andcreate_volume_zarrwrites<id>.zarrinto the session’scorrections/.dashboard.finetune_utils.ensure_minio_servingcallsfinetune.session.minio.MinioServer.ensure_serving. It startsminio serverif none is running, with the bucketannotationsopen to anonymous access and the periodic sync thread. Then it pulls any chunks painted since the last sync, and mirrors the volume up withmc mirror. The volume is registered in the session’sannotation_volumes(session.store.SessionStore), and its_virtual_sources.jsonis written (session.volume.build_manifest,session.manifest.write_manifest); the trainer finds the volume only through that file.The page then posts
/api/finetune/add-to-viewer(routes/finetune/overlay.py): a writable segmentation layer ons3+<MinIO URL>, selected, with the painting tools bound. The browser paints into MinIO directly.finetune.session.syncbrings the painted chunks back to the volume on disk: every 30 s, on Save (/api/finetune/sync-annotations), before a restart, and before each mirror. It lists the volume’s chunks in the bucket with their ETags and copies those that changed (through.partfiles), recording what it saw in the volume’schunk_sync_state. Nothing on disk is deleted because MinIO lacks it.The trainer reads the volume on disk, never MinIO. Volumes are also filled by a YAML crop import (
/api/finetune/load-crops,session.volume.write_crop_into_volume), resumed from an earlier session (annotation_sessions), seeded from a segmentation (session.instance), or built without a dashboard (python -m cellmap_flow.finetune.build_corrections).
Where state lives
Its owners
Four objects hold what a process shares. Each is read through a function, at
call time and never into a module global, because tests/conftest.py
swaps in fresh ones for every test (_fresh_process_state).
jobs.settings.launcher_settings(): the launcher settings, one attribute per key ofSERVER_CONFIG_DEFAULTS(queue,charge_group,walltime, …), loaded from~/.cellmap_flow/server_config.yamlthe first time the process asks, andsave(). The CLIs and/api/server-configwrite them;start_hosts, blockwise and the dashboard’s finetune submit read them.process_chain.process_chain(): the process’s chain.input_normsandpostprocessare the live steps, which can hold state;input_norm_configandpostprocess_configthe steps as the dashboard received them;specthe chain as data, from the configs or else the live steps;set(spec, built=None)the one way to change it. In the dashboard it is the chain the user submitted. In a server or a blockwise worker it is the fallback for a layer URL or model script that gives no chain.jobs.launch.started_jobs(): the servers this process started, whichstart_hostsappends to,cleanup_handlerkills, andserving.client.running_job_hostlooks a model’s host up in.dashboard.state.get_session(): the dashboard’s own state (below).
The dashboard’s Session
Dashboard code reads and writes its state through
dashboard.state.get_session(), a module registry rather than Flask’s
app.extensions: the CLIs fill it before dashboard.app is imported,
and launch threads, job monitors, the MinIO sync thread and the log panel’s
handler run without an app context. It holds:
the dataset and the viewer:
dataset_path,viewer,raw,shaders,shader_controls,extra_layers,neuroglancer_url;the models:
models_config,model_catalog;the pipeline builder’s last apply:
builder_state,builder_model_configs;blockwise’s
tmp_dirandblockwise_tasks_dir, andtasks_dir();the finetune and review tabs:
minio_state,annotation_volumes,output_sessions,finetune_job_manager(made when first asked for),review;the log panel’s
log_bufferandlog_clients, and the box tool’sbbx_generator_state.
It forwards the rest to the owners above: each setting, settings,
server_config and save_server_config(); input_norms and
postprocess (read-only), pipeline_spec and set_pipeline(spec);
and jobs, whose assignment replaces the started list’s contents. It has
__slots__, so a misspelt attribute raises instead of being stored.
The deprecated g
cellmap_flow.globals.g stays for one release, for scripts and plugins.
Each name it had forwards to its owner with a DeprecationWarning that
names the replacement. The warnings are silent in the servers and the
dashboard, whose code is not __main__, and show under pytest.
Nothing in the package imports globals, and test_import_targets
checks it. Importing globals also configures logging, so a module that
imported it reset a CLI’s --log-level to INFO whenever the CLI imported
that module after parsing the flag, as cellmap_flow_server imports the
server. New code uses the owners:
no
import cellmap_flow.globals, and nog.x;dashboard code reads state through
get_session();everything else takes what it needs as arguments, as
finetune.sessiondoes: its MinIO and sync functions are given the dashboard’sminio_stateand volume records, and keep none of their own.
On disk
Where |
What |
|---|---|
|
The saved job settings: read by |
|
Registered plugins. |
|
Inference-server job logs and ready files ( |
|
The geometry cache and the Hugging Face listing. |
|
The dashboard’s blockwise task YAMLs and master logs, and its default
|
|
The Finetune tab’s saved form, and the corrections directory used when no output path is given. |
|
|
The session layout, metadata.json, the markers and the annotation volume
format are read by dashboards older and newer than whatever wrote them, so
they change only compatibly.
Conventions
An OME translation is voxel 0’s centre
OME-NGFF, and Neuroglancer, put translation at the centre of voxel 0.
Everything inside cellmap-flow works with the lower corner of voxel 0
(ArrayMeta.translation, io.geometry.Grid). Readers convert with
io.ome.ome_corner and writers with io.ome.ome_translation (or
multiscales_attrs and singlescale_attrs, which use it); no other code
adds or subtracts half a voxel. Janelia pyramids have
translation = scale/2 − 4 nm, so every level’s corner is at −4 nm. The
served zarr and the blockwise output start at the corner of the raw level the
model reads. The legacy
resolution/offset and N5 transform attributes keep their old
meaning. An annotation volume’s root attribute dataset_offset_nm is voxel
0’s centre, and session.volume.volume_corner_nm gives the corner.
The chain format
A chain is {"input_norm": [...], "postprocess": [...]}, each an ordered
list of steps, each step a flat {"name": <op class>, **constructor
arguments}. It is the same in layer URL blobs, exported YAMLs, blockwise
task YAMLs’ json_data and finetune manifests, and it is read and written
through pipeline_spec.PipelineSpec. A step is never nested as
{"name", "params"} on the wire, because older servers pass every key but
name to the constructor. Values are kept as given: the dashboard’s forms
send strings and the constructors parse them. Files may still hold the older
{Name: {params}} dict, which the readers accept (it can’t hold the same
op twice); PUT /api/pipeline refuses it.
“Behaviour change:” commits
A change to anything a user, a file or another version of cellmap-flow sees
(served data or metadata, files written, HTTP answers, the CLI, training
results) is its own commit, whose subject starts Behaviour change:. It
carries a test that fails on its parent, and it is listed under
“Behaviour-change commits” in CHANGELOG.md. A refactor leaves every guard
below unchanged, so that a behaviour change can be reviewed, reverted or
dropped on its own.
Tests
One test module per public module, next to its subject in
tests/utils/(most of the package, whatever its package),tests/cli/,tests/finetune/ortests/blockwise/, and tested through its public interface: nothing on private helpers, and no test that would fail only because of an internal rename.Tables with readable row ids: near-duplicates are one parametrized test, each
pytest.param(..., id="...")named for its case, and a test’s name says the behaviour it protects.Shared fixtures are in the conftest files.
tests/conftest.pypointsHOMEat a temporary directory before anything imports cellmap_flow, gives every test fresh owners of the process’s state (_fresh_process_state), restores the root logger after it, and hasraw_zarr,ome_pyramid,write_array,model_script,fake_lsf,vieweranddashboard.tests/finetune/conftest.pyandtests/blockwise/conftest.pyhave their areas’ fixtures.Characterization snapshots are the guards. Each pins, as literals in the test, something another party reads:
test_cli_surface: the CLIs’ commands and flags, each type’sto_dict()andcommand, and the model form;test_url_map: every dashboard URL and its methods;test_served_metadata_snapshot: what a server answers;test_layer_sources_snapshot: the layers every path puts in the viewer;test_bsub_argv_snapshot: every bsub and local command line;test_blockwise_routes: the task YAMLs generate writes;test_training_snapshot,test_dataset_draws_snapshot,test_volume_snapshot: training’s numbers, markers and layout, the patches drawn, and the annotation volume format;the rehydration snapshot in
test_finetune_job_manager;test_deployability,test_import_hygieneandtest_import_targets(everyfrom cellmap_flow... importin the package and the tests resolves).
A refactor starts with the characterization test, which must pass on the code before it.
Patch where the name is used. A test that monkeypatches a name patches it on the module that calls it, and asserts its fake was called, so a move can’t turn it into a test of nothing, or of real bsub or
mc.Markers:
finetune(needs peft, skipped without it),gpu,lsf,minio,network(opt in withCELLMAP_FLOW_NETWORK_TESTS=1) andslow. CI runspytest -m "not gpu and not lsf and not minio and not network"with and without thefinetuneextra.Import hygiene: what jobs, servers, finetune runs and the dashboard’s request threads import must not drag in the dashboard or a model. No module imports
globals(test_import_targets). A new module imports Flask, neuroglancer, huggingface_hub and peft only where it needs them, never at module level, and torch likewise outsidefinetune/andinference/.test_import_hygienechecks it in a fresh interpreter per group:jobs/and the job manager,io/,serving/(withmodels.geometry,geometry_cache,inference.runner, theInferencerandImageDataInterface), the registry (withconfig.yamlandserving.launch),pipeline_specandprocess_chain, the state’s owners,finetune/session/,reviewandviewer/; that importingserver.pyorblockwise/leaves logging alone; and thatimport cellmap_flowwrites nothing underHOME.
The dashboard’s JavaScript
The repository has no JavaScript test runner. CI checks that every file in
static/js parses as an ES module (node --input-type=module --check;
plain node --check passes a module with a syntax error), and pytest
checks what it can without a browser: what the two pages hand their scripts
(test_dashboard_pages), that every element id the builder’s and the
tabs’ scripts look up is on the page (test_builder_page,
test_dashboard_tab_scripts), and that every static file ships and every
import resolves (test_static_assets).
What a page does was checked during the cleanup with a headless-Chrome
harness. It renders each page from the tree with Flask’s test client and
loads it in chrome-headless-shell with the tree’s own scripts and styles.
fetch, EventSource and the dialogs are stubbed, timers run on
virtual time, and scripted scenarios drive the page. Each scenario records
every request (method, URL, body, headers and the script that sent it),
every error and alert, and a signature of each rendered element;
compare.py diffs a run on the base against one on the change, and a
refactor must show no difference. The harness is not in the repository: it
is kept with the cleanup’s working notes, in
cleanup_review/tools/fe_harness/ (harness.py, compare.py, the
*_scenarios.py files and libcheck/), with one machine’s Chrome and
node paths written into it.
The deployability contract
Fileglancer runs cellmap-flow from this repository with pixi, and
tests/utils/test_deployability.py pins what that needs:
runnables.yamlrunspixi run <console script> [<subcommand>]for scripts in[project.scripts], with flags they accept (-dforcellmap_flow view, a positional config forcellmap_flow yaml). A change to a command’s name or flags changes the manifest in the same commit.The dashboard binds a free port with a threaded server, prints its URL, and writes it to the file named by
SERVICE_URL_PATHwhen that is set.jobs.launch.SERVER_COMMANDcomes fromCELLMAP_FLOW_SERVER_COMMAND, which pixi’s activation sets topixi run cellmap_flow serve. It is read at import and used at call time; no other module keeps a copy of it. The value before 0.3.0,pixi run cellmap_flow_server, still takes the--modelthe launchers pass.cellmap_flow viewbills the models it launches toLSB_PROJECT_NAME, the Fileglancer job’s project, unless-Pis given.CI runs
pixi lock --check, so a change to the environment is re-locked in the same commit.
See Installation for running from pixi.
Deprecated in 0.3.0
Each of these still works in 0.3.0, warns, and goes in the next release.
CHANGELOG.md has the details.
Deprecated |
Use instead |
|---|---|
The separate console scripts ( |
The subcommands of the one |
|
|
|
|
|
|
|
The same names in |
|
Read at the dataset’s voxel size and resample what is read. |
|
Read within the dataset’s ROI and pad what is read.
|
Using a name through one of the three alias modules raises a
DeprecationWarning that names its new path;
tests/utils/test_deprecated_imports.py checks that each name still gives
the very same object. These went in 0.3.0 without an alias; nothing in the
docs or the examples used them:
the rest of
cellmap_flow.utils, dissolved intoio/,jobs/,serving/,config/yaml.py,models/,norm/safe_expression,dashboard/services/,pluginsandlogging_setup;python -m cellmap_flow.utils.doctorispython -m cellmap_flow.cli.doctor;finetune/finetune_job_manager.py, nowfinetune/job_manager/, andfinetune/virtual_dataset.py, nowfinetune/data/;lora_wrapper.merge_lora_into_base(K19), replaced byadaptation.LoraStrategy.merge.