Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
ab626da
dashboard: port review tab into post-refactor tree (Phase 1a)
dbock May 18, 2026
b58dc96
inferencer: server-side concurrency cap on predict() — Mitigation B (…
dbock May 18, 2026
5ccd546
dashboard: port instance-correction bundle (Phase 2)
dbock May 19, 2026
47b5b8b
dashboard: port viewer-state CRUD + get_raw_layer kwargs (Phase 3)
dbock May 19, 2026
1f6830b
dashboard: spine HTTPS — rewrite MinIO URLs at all emit sites (Phase 4)
dbock May 19, 2026
9b1f7ec
dashboard: finetuned-layer presentation + NG perf bakes (Phase 5a)
dbock May 19, 2026
eb43436
dashboard: extra_layers YAML startup feature (Phase 5b)
dbock May 19, 2026
5d8f4ae
dashboard: server-side caching + MinIO startup robustness (Phase 5c)
dbock May 19, 2026
92dc515
dashboard: polish — KD-fiber sync + spine iframe + list-valued PP + l…
dbock May 19, 2026
89544c5
finetune: dataloader patches + LocalProcessJob pipe-deadlock fix (Pha…
dbock May 19, 2026
24163b8
dashboard: data-layer min_scale pass-through (Phase 5b amendment)
dbock May 19, 2026
ee06a94
yaml_cli: pre-assign inference server port (Phase 5 amendment, untested)
dbock May 19, 2026
610320a
yaml_cli: proxy-aware job.host URL (Phase 8 gap #5)
dbock May 19, 2026
c826339
utils: re-port Patch 30 voxel_offset + 81b88b4 list-serialization (β …
dbock May 19, 2026
190b020
utils: zarr v3 read support for cellmap-flow
dbock May 19, 2026
1a11cdf
Patch 45: emit served voxel's true nm-per-voxel in NGFF .zattrs
dbock May 21, 2026
a102c7e
Merge dbock vacc-compat changes
mzouink Jul 31, 2026
cf0a6cb
Remove min_scale viewer filtering
mzouink Jul 31, 2026
c2c0a04
Make pymorton optional for postprocessors
mzouink Jul 31, 2026
dd1a046
Handle v3 numeric scale paths in viewer utils
mzouink Jul 31, 2026
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
90 changes: 86 additions & 4 deletions cellmap_flow/cli/yaml_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,28 @@
logger = logging.getLogger(__name__)


def _patch_neuroglancer_cache_control() -> None:
# Chunk URLs are content-addressed by viewer/volume token, so within a
# session the body for a given URL cannot change. Adding `immutable` lets
# the browser serve repeats from disk cache with no revalidation
# round-trip. Patch 34 (d446769).
import neuroglancer.server

if getattr(neuroglancer.server.SubvolumeHandler, "_cmf_cache_control_patched", False):
return
_orig_get = neuroglancer.server.SubvolumeHandler.get

async def _patched_get(self, *args, **kwargs):
self.set_header("Cache-Control", "public, max-age=31536000, immutable")
return await _orig_get(self, *args, **kwargs)

neuroglancer.server.SubvolumeHandler.get = _patched_get
neuroglancer.server.SubvolumeHandler._cmf_cache_control_patched = True


_patch_neuroglancer_cache_control()


def run_multiple(
models: List[ModelConfig], dataset_path: str, charge_group: str, queue: str, wrap_raw: bool = True
) -> None:
Expand All @@ -48,14 +70,38 @@ def _submit_model(model):
logger.warning(f"Model {getattr(model, 'name', type(model).__name__)} specifies scale {model.scale}, adjusting dataset path accordingly")
current_data_path = os.path.join(dataset_path, model.scale)

command = f"{SERVER_COMMAND} {model.command} -d {current_data_path}"
# Pre-assign a port so we know the host URL immediately (no waiting).
# Patch b8c22bf: instead of waiting up to 120s for the subprocess to
# print its address, get_free_port() picks an unused port and we pass
# -p {port}. wait_for_host=False short-circuits the parent's
# output-monitoring loop; the model loads in the background and
# predictions appear in NG once ready (~60-90s) without blocking
# dashboard startup. Patch a3d1cd9: job.host uses localhost (not
# get_public_ip) so the SSH-tunneled browser can reach the URL.
# Phase 8 amendment: job.host is proxy-aware. bootstrap_dashboard.sh
# exports CMFLOW_PROXY_MODE; under spine, browser reaches the
# subprocess via spine's nginx /inf-{port}/ forward instead of
# localhost.
from cellmap_flow.utils.web_utils import get_free_port
server_port = get_free_port()
command = f"{SERVER_COMMAND} {model.command} -d {current_data_path} -p {server_port}"
model_name = getattr(model, "name", None) or type(model).__name__

logger.info(f"Submitting job for model: {model_name}")
logger.warning(f"Executing command: {command}")
start_hosts(
command, job_name=model_name, queue=queue, charge_group=charge_group
job = start_hosts(
command, job_name=model_name, queue=queue, charge_group=charge_group,
wait_for_host=False,
)
proxy_mode = os.environ.get("CMFLOW_PROXY_MODE", "direct-ssh")
if proxy_mode == "spine":
spine_url = os.environ.get(
"CMFLOW_SPINE_URL", "https://spine.med.uvm.edu"
).rstrip("/")
job.host = f"{spine_url}/inf-{server_port}"
else:
job.host = f"http://localhost:{server_port}"
logger.info(f"Pre-assigned inference server {model_name} at {job.host}")
return model_name

if models:
Expand Down Expand Up @@ -180,7 +226,6 @@ def main(config_path: str, log_level: str, list_types: bool, validate_only: bool
charge_group = config["charge_group"]
queue = config["queue"]
wrap_raw = config.get("wrap_raw", True)

# Update globals and save to cache
g.queue = queue
g.charge_group = charge_group
Expand Down Expand Up @@ -211,6 +256,43 @@ def main(config_path: str, log_level: str, list_types: bool, validate_only: bool
click.echo(f" - Queue: {queue}")
return

# Pre-build additional zarr layers (loaded at startup alongside EM).
# Each entry stored as (layer, shader, blend) so generate_neuroglancer_url
# can apply the YAML shader + blend mode on the live viewer; without this
# the layer falls back to get_raw_layer's hardcoded white [-1,1] shader.
extra_layers = config.get("extra_layers", [])
if extra_layers:
from cellmap_flow.utils.scale_pyramid import get_raw_layer
g._extra_startup_layers = {}
for layer_cfg in extra_layers:
lpath = layer_cfg["path"]
lname = layer_cfg["name"]
lshader = layer_cfg.get("shader")
lblend = layer_cfg.get("blend")
# layer_type: "image" (default) or "segmentation". When
# segmentation, get_raw_layer returns a SegmentationLayer and
# NG renders categorical IDs with its built-in palette.
ltype = layer_cfg.get("layer_type", "image")
is_seg = ltype == "segmentation"
# Per-layer disable_meshes (segmentation only): suppress NG's
# auto-mesh subsource so segment-pick gestures don't trigger
# marching-cubes mesh generation. Defaults to False — current
# behavior is unchanged unless explicitly opted-in.
ldisable_meshes = bool(layer_cfg.get("disable_meshes", False))
logger.info(
f"Pre-loading extra zarr layer: {lname} -> {lpath} "
f"(type={ltype}, disable_meshes={ldisable_meshes})"
)
try:
layer = get_raw_layer(
lpath, normalize=False, segmentation=is_seg,
disable_meshes=ldisable_meshes,
)
g._extra_startup_layers[lname] = (layer, lshader, lblend)
except Exception as e:
import traceback
logger.error(f"Failed to load extra layer {lname}: {e}\n{traceback.format_exc()}")

# Run the models
run_multiple(g.models_config, data_path, charge_group, queue,wrap_raw=wrap_raw)

Expand Down
20 changes: 18 additions & 2 deletions cellmap_flow/dashboard/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from cellmap_flow.dashboard.routes.blockwise import blockwise_bp
from cellmap_flow.dashboard.routes.bbx_generator import bbx_bp
from cellmap_flow.dashboard.routes.finetune import finetune_bp
from cellmap_flow.dashboard.routes.review_routes import review_bp

logger = logging.getLogger(__name__)

Expand All @@ -29,6 +30,21 @@
logger.addHandler(log_handler)
logger.setLevel(logging.INFO)

# Make INFO messages from cellmap_flow.dashboard.* submodules visible in the
# dashboard log file. Without this, logger.info() calls in submodules (e.g.
# finetune_utils) are filtered out — the default root level is WARNING and
# only app's module-local logger had INFO explicitly set. Attach a stream
# handler at the namespace level and stop propagation to avoid duplicate
# emission via Python's last-resort WARNING handler.
_cflow_dashboard_logger = logging.getLogger("cellmap_flow.dashboard")
_cflow_dashboard_logger.setLevel(logging.INFO)
_cflow_dashboard_stream_handler = logging.StreamHandler()
_cflow_dashboard_stream_handler.setFormatter(
logging.Formatter("%(levelname)s:%(name)s:%(message)s")
)
_cflow_dashboard_logger.addHandler(_cflow_dashboard_stream_handler)
_cflow_dashboard_logger.propagate = False

# Register all blueprints
app.register_blueprint(logging_bp)
app.register_blueprint(index_bp)
Expand All @@ -38,13 +54,13 @@
app.register_blueprint(blockwise_bp)
app.register_blueprint(bbx_bp)
app.register_blueprint(finetune_bp)
app.register_blueprint(review_bp)


def create_and_run_app(neuroglancer_url=None, inference_servers=None):
def create_and_run_app(neuroglancer_url=None, inference_servers=None, port=0):
g.NEUROGLANCER_URL = neuroglancer_url
g.INFERENCE_SERVER = inference_servers
hostname = socket.gethostname()
port = 0
logger.warning(f"Host name: {hostname}")
app.run(host="0.0.0.0", port=port, debug=False, use_reloader=False)

Expand Down
Loading