SelectFromUrl

class controllers.SelectFromUrl

Bases: handle

SELECTFROMURL - Controller for Home -> Import -> URL / Zarr.

Opens a dataset from a URL. Two very different sources share the entry point:

  • an OME-Zarr container (http/https/s3), opened as a BigData, Virtual or Standard dataset through the normal loader chain;

  • any ordinary image URL, which keeps the original behaviour of this menu item - imfinfo + imread into a Standard dataset.

Which one applies is decided by probing the URL, not by asking the user.

An N5 container URL is redirected to the OME-Zarr copy published beside it (resolveN5Sibling()). OpenOrganelle dataset pages hand out the N5 form of every volume, from both the “Fiji” link and the “Copy data url” button, and MIB has no N5 reader - so without this a user following the website would paste a URL that could never work.

On an S3-compatible host the container is browsed one level per expand, costing a single ListObjectsV2 request each time. That matters because published containers hide large subtrees below the image group - the OpenOrganelle labels tree holds 42 crops of about 40 class groups each - so an eager recursive walk would fire hundreds of requests to build a list nobody wants to read. Hosts that cannot be listed degrade to typing the group path.

Launch as GUI tool:

obj.mibController.startController('controllers.SelectFromUrl');

Launch in batch mode:

BatchOpt.Url = 'https://janelia-cosem-datasets.s3.amazonaws.com/jrc_mus-liver-zon-1/jrc_mus-liver-zon-1.zarr';
BatchOpt.GroupPath = 'recon-1/em/fibsem-uint8';
obj.mibController.startController('controllers.SelectFromUrl', [], BatchOpt);

Trigger return of possible options:

obj.mibController.startController('controllers.SelectFromUrl', [], NaN);
Constructor Summary
SelectFromUrl(mibModel, varargin)

SELECTFROMURL - Constructor.

Syntax:
obj = SelectFromUrl(mibModel)
obj = SelectFromUrl(mibModel, [], BatchOpt)
obj = SelectFromUrl(mibModel, [], NaN)
Property Summary
BatchOpt

cell array of listener handles

connectedUrl

[dictionary] url -> struct describing a probed group

cropPlan

[char] how LoadAs = Labels will be honoured for the current selection: ‘model’ - the group matches the open image, load it straight onto it; ‘crop’ - it is a sub-volume, so its image region is opened too; ‘’ - it cannot be loaded as labels at all

isListable

[char] ‘zarr2’, ‘zarr3’, or ‘’ when the URL is not a zarr store

labelLoadRoute

[char] URL of the last successful connect; suppresses a repeated probe

listener

handle to the main MIB figure, parent for dialogs

mibGUI

handle to the view (views.SelectFromUrlGUI); empty in batch mode

mibModel
probeCache

[logical] whether the host supports directory listing

progressDialog

[struct] last result of planLabelCrop, valid while labelLoadRoute is ‘crop’

rootUrl

structure compatible with batch processing; field names match widget Tags

view

handle to MibModel

zarrFormat

[char] normalised URL of the container currently connected to

Method Summary
static ViewListner_Callback2(~, evnt)

VIEWLISTNER_CALLBACK2 - Static listener guard; safe when the view is gone.

addCallbacks()

ADDCALLBACKS - Wire every widget callback after the view exists.

No per-widget existence guards: the view creates every widget in its constructor, so if one is missing that is a bug in the view and should fail loudly here rather than be silently skipped.

showWaitbar has no widget deliberately - the dialog always shows progress, and the BatchOpt field exists only so a batch protocol can turn the bar off. Several groups at once, because a ground-truth crop keeps every class in its own group and a useful model blends them. Set here rather than on the canvas so the behaviour lives beside the callback that depends on it.

applySummary(groupUrl, groupSummary)

APPLYSUMMARY - Push a probe result into the widgets.

Also decides whether Open may be pressed. For LoadAs = Labels that includes the dimension check, so a store that cannot line up with the open image is refused here with an explanation rather than silently misplacing the labels later.

buildLoadImagesBatchOpt()

BUILDLOADIMAGESBATCHOPT - Map the dialog state onto MibModel.loadImages options.

Syntax:
loadOptions = obj.buildLoadImagesBatchOpt()

Kept pure - no network, no widgets, no side effects - so the mapping can be asserted in a unit test without opening anything.

Two details are load-bearing:

  • Filenames must be present. MibModel.loadImages only skips its fullfile path joins when the incoming BatchOpt carries that field, and fullfile would rewrite https://host/group as https://host\group on Windows.

  • DirectoryName gets the current MIB directory, never the URL, so the recent-directories list stays usable.

Output Arguments:
  • loadOptions - [struct] BatchOpt for MibModel.loadImages

chooseCropLevel(cropPlan)

CHOOSECROPLEVEL - Ask which pyramid level pair to read.

Syntax:
[cropPlan, proceed] = obj.chooseCropLevel(cropPlan)

The pairing settles which label level goes with which image level, but not which pair - and the difference is the resolution of the dataset the user ends up with, plus how much memory it costs. For jrc_mus-kidney the choice runs from 128 nm / 1010 MB to 2048 nm / a few MB, and nothing on screen would otherwise say so.

Only pairs that line up are offered. Image levels with no matching label level are not choices at all - offering them would mean resampling one pyramid to fit the other, which planLabelCrop() refuses to do. For jrc_mus-kidney that is EM s4..s8; s0..s3 have no counterpart.

A single candidate is not a question, and is taken silently. BatchOpt.ZarrLevel answers it for a protocol, which must never stop for a dialog; an unusable value there is reported rather than quietly replaced, so a stale protocol does not open a different resolution than it names.

Input Arguments:
Output Arguments:
  • cropPlan - [struct] with the chosen pair promoted

  • proceed - [logical] false when the user cancelled

chooseInstanceHandling(instanceNames)

CHOOSEINSTANCEHANDLING - Keep an instance segmentation’s objects, or merge them.

Syntax:
proceed = obj.chooseInstanceHandling(instanceNames)

This route builds an ordinary in-memory model, which holds up to 65535 materials, so an instance segmentation’s objects can simply be kept - one material each - and that is the default. Merging them all into a single material is a view, not a necessity: useful when the question is “where are the nuclei” and the 864 separate entries would only be in the way.

Keeping is lossless, so it needs no consent - headless and batch take it silently. Only the merge is asked about, and only where there is a window; a protocol states it outright with BatchOpt.MergeInstanceObjects.

Input Arguments:
  • instanceNames - {1xN cell} the selected groups that are instance segmentations

Output Arguments:
  • proceed - [logical] false only when the user cancelled; BatchOpt.``MergeInstanceObjects`` carries the choice

chunkCacheMBValueChanged(event)

CHUNKCACHEMBVALUECHANGED - Apply a new chunk cache budget at once.

Writes the IO.Zarr.ChunkCacheMB preference - the same one Preferences -> Input/output edits, so the two dialogs never disagree and the value persists with the other preferences - and hands it to io.zarr.ChunkCache.setBudgetMB straight away. Shrinking the budget evicts immediately; 0 turns caching off.

Deliberately not a BatchOpt field: the cache is process-wide, so a protocol that resized it would change the behaviour of every other zarr dataset open in the session.

closeWindow()

CLOSEWINDOW - Destroy the view and fire CloseEvent.

composeLabelModel(labelGroupUrls, labelPyramids, cropPlan, progressDialog)

COMPOSELABELMODEL - Blend the selected COSEM label groups into one MIB model.

Syntax:
[modelData, materialNames, compositionReport] = obj.composeLabelModel(labelGroupUrls, labelPyramids, cropPlan, progressDialog)

Three kinds of group, and they encode their values differently. Getting this wrong produces an empty model rather than an error, so the distinction is read from the store, never assumed:

  • A single-class group carries a cellmap.annotation block declaring semantic_segmentation and a present value. Its data is binary, and it becomes exactly one material.

  • An index map - the merged all group of a crop - carries no cellmap block at all, and every distinct non-zero value is a different class. It becomes one material per value present.

  • An instance segmentation declares instance_segmentation and holds an object id per voxel. By default every object is kept, one material each: this route builds an ordinary in-memory model, which holds up to 65535 materials, so there is no reason to discard them. BatchOpt.MergeInstanceObjects collapses them all into one material instead - a mask of where the objects are, which is a reasonable thing to want for viewing and is offered by chooseInstanceHandling(). Either way the object count is reported, since 864 objects reads very differently from 3.

Assuming the binary form for everything is what an earlier version did, and on crop266/all - whose values are 3, 4, 5, 8, … 48, with no voxel equal to 1 - it silently yielded a model with nothing in it.

Groups are applied in pick order, so a later pick wins where two overlap.

Three things are counted while blending, because each is invisible in the result and misleading if unreported:

  • Overlap. COSEM classes are not disjoint - er is er_mem plus er_lum, er_mem_all contains er_mem - so a pick order silently decides which one a shared voxel ends up as. The overlap is counted exactly, per pair, rather than guessed from class names, which cannot see how a particular crop was annotated.

  • ``unknown`` is not background. The value 255 means “not annotated here”, and it lands in material 0 alongside genuine background. Anything trained on the result would learn those voxels as negatives, so the count is reported.

  • Empty classes. Many groups exist in a crop but contain nothing. Counting contributions is free here, and it is the same answer a picker could only get by downloading every group speculatively.

Input Arguments:
  • labelGroupUrls - {1xN cell} URLs of the groups, in pick order

  • labelPyramids - {1xN cell} matching readGroupPyramid results

  • cropPlan - [struct] from planLabelCrop()

  • progressDialog - [handle] uiprogressdlg to advance, or []

Output Arguments:
  • modelData - [y x z] index map, uint8 for up to 255 materials

  • materialNames - {1xM cell} one entry per material; M exceeds the number of selected groups when an index map contributes several

  • compositionReport - [struct] .lines (cell of text for the user), .voxelCounts, .unknownCounts, .overlaps, .isHomogeneous, .instanceObjectCounts (per group, 0 unless it was an instance group)

confirmLoadAs(targetUrl)

CONFIRMLOADAS - Ask what a second remote dataset should be opened as.

Opening a different group while a remote dataset is already up is the one case where Load as is genuinely ambiguous, and the two outcomes are far apart: Image replaces the buffer, Labels puts the group on top of what is already there. The browse that leads here - open the EM volume, then go back for its annotations - ends on the wrong setting more often than not, because Image is the default and nothing about picking a label group changes it.

Only asked in the GUI: a batch protocol states what it wants and must never stop for a question. Re-opening the same group is not ambiguous either, so that goes through untouched.

Cancel returns to the dialog with the selection intact, which is why this reports back rather than deciding on its own - and why there is nothing to ask without a window to return to.

connectBtn_Callback(autoOpenPlainImage)

CONNECTBTN_CALLBACK - Inspect the URL and prepare the dialog for it.

Syntax:
obj.connectBtn_Callback()
obj.connectBtn_Callback(true)   % Enter was pressed in the dialog

Decides between the three cases the dialog has to handle, cheaply and in this order:

  1. a listable OME-Zarr container - seed the tree with the root node, the user expands from there, one request per level;

  2. an OME-Zarr container on a host with no directory listing - hide the tree and let the group path be typed instead. Most non-AWS OME-Zarr hosts are in this category, so this path is not a corner case;

  3. anything else - if imfinfo recognises it, fall back to the plain image import this menu item has always done.

Before any of that, an N5 container URL is redirected to the OME-Zarr copy published beside it - see resolveN5Sibling(), which exists because the OpenOrganelle dataset pages hand out the N5 form of every volume.

Input Arguments:
  • autoOpenPlainImage - (optional) [logical] true when Enter was pressed rather than the Connect button. Case 3 then imports straight away instead of waiting for Open, because a plain image has nothing to browse or configure. Default: false

ensureDatasetMode(datasetId, targetMode)

ENSUREDATASETMODE - Put a dataset buffer into the mode the import needs.

Syntax:
switched = obj.ensureDatasetMode(datasetId, targetMode)

A no-op when the buffer is already in that mode, which is the common case and worth keeping free: switchDatasetMode re-initialises the buffer with a placeholder, so calling it needlessly discards whatever is open.

The placeholder has to match the target mode, and the two forms are not interchangeable - this is the whole reason the function exists rather than the call being inlined:

  • 'Standard' wants a numeric matrix, or [] to let core.MibImage.initialize build its own 512x512 uint8 placeholder;

  • 'Virtual' / 'BigData' want a cell array of file paths, because those modes open a reader rather than hold pixels.

Handing the cell form to Standard puts a cell in MibImage.data, and the first thing initialize does with it is intmax(class(data)), which raises “Class name must be a class that supports INTMAX” from four frames below the caller with nothing in the message about dataset modes.

The Datasets panel cache is written here, on every success including the no-op, because this is the one place the mode is changed. Leaving it to the caller is what left the label-crop route showing “BigData” over a buffer that had been switched to Standard: the image branch of openBtn_Callback synced the cache and the crop branch never did.

The repaint is NOT notified here, and must not be. DatasetsPanelUpdate reaches MibActiveDataset.update_fromModel -> buffers_Callback -> ShowImage, so it repaints the buffer - which at this point holds nothing but the mode-switch placeholder. For a Virtual/BigData target that is a path to default.h5 with no reader attached, and the repaint dies inside MibVirtualImage.getDataVirt. Each caller notifies once its own load has finished and the buffer is paintable.

Input Arguments:
  • datasetId - [numeric] buffer index

  • targetMode - [char] 'Standard', 'Virtual' or 'BigData'

Output Arguments:
  • switched - [logical] true when the buffer is now in targetMode; false means the failure was already reported to the user

groupPathValueChanged(event)

GROUPPATHVALUECHANGED - Typed group path, the fallback for hosts that cannot be listed; probe it so Open can be enabled.

guiFigure()

GUIFIGURE - Dialog parent, or [] when running without a view.

Keeps every dialog and progress bar call in this controller safe in batch mode, where there is no window to parent them to.

hasView()

HASVIEW - True when widgets can be written to.

helpButton_Callback()

HELPBUTTON_CALLBACK - Open the documentation page.

imageBoxContains(~, candidatePyramid, cropBoxUm, cropVoxelSizeUm)

IMAGEBOXCONTAINS - Does this candidate image pyramid enclose the crop?

Syntax:
tf = obj.imageBoxContains(candidatePyramid, cropBoxUm, cropVoxelSizeUm)

The containment test behind resolveSiblingImageGroup(), kept separate so it can be asserted offline. A candidate that carries a cellmap annotation is rejected outright: that block is what marks a group as an annotation, so a label group that happens to enclose a smaller one must never be offered as the image to load it onto.

Two different roundings have to be tolerated, which is why the crop’s own voxel size is an argument rather than something derived from the box:

  • the candidate’s - half of its own voxel, so a crop reaching exactly to the volume’s edge is not rejected by a rounding decimal in the store’s declared translation.

  • the crop’s - one whole label voxel. A label pyramid published as a downsample of the image rounds its shape UP, so its declared extent overshoots the image’s. In jrc_ctl-id8-1 the nuc segmentation is the EM downsampled 16x and 18500/16 rounds up to 1157, so it claims 1157 * 64 = 74048 nm against the EM’s 18500 * 4 = 74000 nm - a 48 nm overshoot that half an image voxel (2 nm) rejects outright. The excess can never exceed one label voxel, which is therefore the bound.

Input Arguments:
  • candidatePyramid - [struct] a readGroupPyramid result

  • cropBoxUm - [1x6] the crop’s outer extent in micrometres

  • cropVoxelSizeUm - [1x3] [x y z] voxel size of the crop’s finest level, in micrometres. Pass zeros to compare on the candidate’s rounding alone

Output Arguments:
  • tf - [logical] true when the candidate is an image enclosing the crop

isAnnotationPath(groupUrl)

ISANNOTATIONPATH - Does this group live inside an OME-NGFF labels container?

Syntax:
tf = obj.isAnnotationPath(groupUrl)

True when any component of the group’s path below the container root is named labels. OME-NGFF makes that name the container convention for annotations, so this is reading a declared structure rather than guessing from a name.

Used to keep resolveSiblingImageGroup() from offering an annotation as the image to load annotations onto. The case that forces it is crop1/all, the merged ground truth: it is a genuine multiscales pyramid, it carries no cellmap annotation block, and it encloses the crop exactly, so nothing about its own content distinguishes it from the EM volume. Its path does.

Input Arguments:
  • groupUrl - [char] URL of the group to test

Output Arguments:
  • tf - [logical] true when the group is inside a labels container

keyPress_Callback(event)

KEYPRESS_CALLBACK - Enter anywhere in the dialog connects the URL.

Enter has to be handled here rather than in the URL field’s own ValueChangedFcn, for two reasons that pull in the same direction:

  • ValueChangedFcn does not fire when Enter is pressed on text the user never edited - which is exactly the clipboard pre-fill case, the one this shortcut exists for;

  • it does fire when the field merely loses focus, so driving the connect from there would fetch over the network every time the user clicked away, and could import a plain image on the way to pressing Close.

The figure sees the key before the field commits, so drawnow flushes any pending commit first - verified ordering, not a guess. Re-connecting to a URL already connected to is skipped, which is what keeps Enter on a button from repeating the whole probe.

labelEncodingValues(~, pyramidInfo)

LABELENCODINGVALUES - Read how a label group encodes its values.

Syntax:
[presentValue, unknownValue, isSemantic] = obj.labelEncodingValues(pyramidInfo)

Every COSEM single-class group states its own encoding in cellmap.annotation.annotation_type.encoding, so nothing here is a convention MIB imposes.

The third output is the one that matters. A group with no cellmap block is not a binary mask with default values - it is an index map holding several classes, which is what a crop’s merged all group is. Treating it as binary looks for voxels equal to 1 and, on a crop whose ids start at 3, finds none, giving an empty model and no error. isSemantic is therefore false unless the store actually declares a present value, never by default.

Input Arguments:
Output Arguments:
  • presentValue - [numeric] value meaning “this class is here”; only meaningful when isSemantic is true

  • unknownValue - [numeric] value meaning “not annotated” (default 255, which every group of the reference store uses, declared or not)

  • isSemantic - [logical] true for a single-class binary group, false for a multi-class index map

labelGroupName(~, groupUrl, pyramidInfo)

LABELGROUPNAME - Short, readable name for a label group.

Syntax:
groupName = obj.labelGroupName(groupUrl, pyramidInfo)

Prefers the class_name the store declares, which is what a single-class COSEM group carries. Groups without a cellmap block - the merged all of a crop - have no declared name, so the last path segment of the URL is used.

That fallback used to be relativePath(join(url, '..'), url), which does not work: io.RemoteStore.join appends .. rather than resolving it, so relativePath found no common prefix and returned the whole URL - which then became the material name shown in the Segmentation panel.

Input Arguments:
Output Arguments:
  • groupName - [char] e.g. 'mito_mem' or 'all'

loadAsValueChanged(event)

LOADASVALUECHANGED - Switching Image/Labels re-checks the selection.

The Labels branch has an extra requirement the Image branch does not - the label store must match the open image - so the currently selected group has to be re-evaluated, otherwise Open could stay enabled for a combination that is about to be rejected.

nodeLabel(~, ~, displayName)

NODELABEL - Text for a tree node.

Deliberately just the name: deciding whether a node holds a pyramid would need a metadata fetch per sibling, turning one request per expand into one per child. The info panel answers that question for the selected node instead.

openBtn_Callback(batchModeSwitch)

OPENBTN_CALLBACK - Perform the import.

Syntax:
obj.openBtn_Callback()
obj.openBtn_Callback(true)     % batch mode, no dialog to close

Routes to one of three destinations, in the order the checks get cheaper to recover from:

  • not a zarr store -> the original imread import;

  • LoadAs = Labels -> MibModel.loadModel onto the open dataset, or openLabelCrop() when the group is a sub-volume needing its own image region. loadModel picks between an in-place model and a read-only overlay itself, from whether the store’s finest level matches the image, so the model and overlay routes arrive here the same way;

  • otherwise -> switch the buffer to the requested dataset mode and call MibModel.loadImages.

Input Arguments:
  • batchModeSwitch - (optional) [logical] true when running headless; suppresses closing the (nonexistent) window. Default: false

openLabelCrop(batchModeSwitch)

OPENLABELCROP - Open a ground-truth crop: the image region plus the selected labels.

Syntax:
obj.openLabelCrop(batchModeSwitch)

Fetching the image region is mandatory here, not a convenience. Loading a group as Labels needs an open dataset whose dimensions match, and when a crop is selected the open dataset is the whole parent volume - a 200^3 island inside a 25-gigavoxel EM stack. The combination a user could otherwise pick has no working outcome at all, so the image sub-volume is opened first and the labels go onto that.

Sequence:

  1. read every selected label group’s pyramid, confirming any instance group

  2. resolve the image pyramid the crop came from (or take the override)

  3. plan the pairing - which level of each, and assert the shapes agree

  4. open the image region through MibModel.loadImages with Region

  5. blend the label groups into one index map and attach it as the model

Input Arguments:
  • batchModeSwitch - [logical] true when running headless

openPlainImageUrl()

OPENPLAINIMAGEURL - Import an ordinary image from a URL with imread.

Syntax:
obj.openPlainImageUrl()

The original behaviour of Home -> Import -> URL / Zarr, moved here unchanged when the menu item grew the OME-Zarr path, so a plain image URL still works exactly as before.

Note that a multi-page file is fetched once per page: imread re-reads the remote file for every index. That was true before this move as well, and is only worth revisiting if someone actually opens large multi-page files this way.

openRemoteContainer()

OPENREMOTECONTAINER - Where the active dataset came from, split in two.

Returns empty strings unless the dataset in the active buffer was itself opened from a URL. A local dataset, an empty buffer (none.tif) and a plain image URL all give ''.

Split into container plus group rather than handed back whole, because the stored filename is the group URL - something like .../jrc_hela-2.zarr/recon-1/em/fibsem-uint8. Connecting to that roots the tree at the image group, which holds nothing but its own pyramid levels, so the labels next door would be unreachable without editing the URL by hand. Rooting at the container instead keeps the whole dataset browsable while GroupPath still names exactly what is open.

planLabelCrop(~, labelPyramid, imagePyramid, memoryLimitBytes)

PLANLABELCROP - Work out how a label crop pairs with its parent image pyramid.

Syntax:
cropPlan = obj.planLabelCrop(labelPyramid, imagePyramid)
cropPlan = obj.planLabelCrop(labelPyramid, imagePyramid, memoryLimitBytes)

Pure - takes two readGroupPyramid() results and touches nothing else, so the pairing arithmetic can be asserted offline against embedded metadata. The one environment-dependent input, how much memory may be used, is an argument with a machine-derived default rather than a lookup inside.

What has to be decided. The two pyramids do not line up level for level, and which one has to give up levels depends on what the label group is:

  • A ground-truth crop is annotated FINER than the volume it came from - the OpenOrganelle crops are 2 nm against 4 nm EM - so the image opens at its finest level and the labels supply the matching coarser one. For the COSEM crops that is label s1 against image s0.

  • A whole-volume inference segmentation is the other way round: it is often published only from a coarse level down. jrc_ctl-id8-1’s nuc starts at 64 nm, which is the EM’s s4 exactly, so there the IMAGE is the pyramid that gives up levels and the dataset opens at 64 nm.

Image levels are therefore searched from the finest, which keeps the first case picking s0 and lets the second fall through to the level that matches.

The shapes are asserted, never forced. A pyramid pair that shares no common scale is reported and refused; resampling one to fit the other would turn a metadata problem into silently wrong labels.

Input Arguments:
  • labelPyramid - [struct] from readGroupPyramid, the label group

  • imagePyramid - [struct] from readGroupPyramid, the candidate image group

  • memoryLimitBytes - (optional) [numeric] ceiling for the image region plus its model. Default: 60% of what MATLAB reports it could still allocate, or 8 GiB where the platform cannot say (memory is Windows-only). Pass a value to make the decision deterministic in a test

Output Arguments:
  • cropPlan - [struct] with fields:

    • .ok - [logical] true when the pair can be opened

    • .reason - [char] why not, when ok is false

    • .cropOuterBoxUm - [1x6] the crop’s outer extent in micrometres, ready for BatchOpt.Region

    • .labelLevel / .imageLevel - [numeric] 1-based level indices

    • .imageLevelName - [char] the image level’s own name ('s4'), for reporting which resolution the dataset will actually open at

    • .shapeYXZ - [1x3] agreed voxel counts

    • .voxelSizeUm - [1x3] [x y z] voxel size of the chosen pair

    • .requiredBytes - [numeric] what opening it costs in RAM, 0 until the shapes are known

    • .candidatePairs - [1xN struct] every level pair that lines up, finest image level first, each with imageLevel, labelLevel, imageLevelName, shapeYXZ, voxelSizeUm, requiredBytes and fits. The chosen pair above is the first one whose fits is true; the rest are what the level dialog offers

probeGroup(groupUrl)

PROBEGROUP - Read a group’s metadata and decide whether it can be opened.

Syntax:
groupSummary = obj.probeGroup(groupUrl)

A group is openable when it carries an OME-NGFF multiscales attribute. When it does, the pyramid is described in the info panel and Open is enabled; otherwise Open stays disabled and the user is told to keep looking. Results are cached per URL for the session, so re-selecting a node is free.

For LoadAs = Labels the dimensions are compared against the open image here, before Open is pressed, because a mismatch is silent otherwise: core.MibBigDataLabelsZarr2 takes its extent from the label store itself and has no origin concept, so a sub-volume crop would be placed at the origin at the wrong scale rather than where it belongs.

Input Arguments:
  • groupUrl - [char] URL of the group to inspect

Output Arguments:
  • groupSummary - [struct] with .hasMultiscales, .sizeYXZ, .levelCount, .dataType, .voxelSize, .units, .annotationType, .lines

annotationType is carried here rather than left to readGroupPyramid() because the attributes are already in hand and resolveLabelRoute() needs it on the path where the dimensions already match - which returns before any pyramid is read, and would otherwise pay a request per level on the common working case just to ask one question.

pythonNote()

PYTHONNOTE - Suffix warning that the selected backend needs Python packages.

Both zarr v2 and v3 are read natively over HTTP range requests, so there is normally nothing to say. The note only appears when the user has selected the python backend in Preferences and that interpreter cannot reach the network, which is the one combination that fails after Open is pressed.

Not covered here: an array whose codec configuration the native engine refuses falls back to python on its first read even under the native setting (see io.zarr.Array.fallbackToPython). Nothing known before Open distinguishes such a store, so it reports itself when the fallback happens rather than being predicted here.

readGroupPyramid(groupUrl)

READGROUPPYRAMID - Read everything about one OME-Zarr group needed to place it in space.

Syntax:
pyramidInfo = obj.readGroupPyramid(groupUrl)

Goes further than probeGroup(), which only describes a group well enough to show it in the info panel. Placing a group against another one needs every level’s shape and world box, not just level 0’s, because the level that pairs with a sibling pyramid is rarely level 0 - for the OpenOrganelle ground-truth crops it is s1, since the crops are annotated at 2 nm and the EM they came from is 4 nm.

Costs one metadata read per level, so results are cached per URL for the session alongside the ordinary probe cache.

Input Arguments:
  • groupUrl - [char] URL of the group

Output Arguments:
  • pyramidInfo - [struct] with fields:

    • .ok - [logical] false when the group has no usable multiscales

    • .multiscale - [struct] the first multiscales entry

    • .axisOrder - [char] e.g. 'zyx'

    • .levelNames - {1xN cell} level paths

    • .levelShapesYXZ - [N x 3] voxel counts per level

    • .levelVoxelSizesXYZ - [N x 3] voxel size per level, store units

    • .levelWorldBoxes - [N x 6] centre-based boxes, store units

    • .unit - [char] the store’s length unit

    • .dataType - [char] MATLAB class of level 0

    • .annotation - [struct] the cellmap.annotation block, or empty

    • .className - [char] annotated class name, or ''

    • .annotationType - [char] 'semantic_segmentation' / 'instance_segmentation' / ''

    • .encoding - [struct] the absent/present/unknown values

reportCropResult(imageGroupPath, cropPlan, materialNames, compositionReport)

REPORTCROPRESULT - Tell the user what was actually loaded, and what to watch for.

Syntax:
obj.reportCropResult(imageGroupPath, cropPlan, materialNames, compositionReport)

Silent when there is nothing to say. A dialog appears only when the composition found something the result itself cannot show - overlap between classes, unknown voxels sitting in material 0, classes that turned out empty, or a crop that is a single material throughout. Each of those looks like a normal model on screen, which is exactly why they are worth a sentence.

Input Arguments:
  • imageGroupPath - [char] image group the region came from, for context

  • cropPlan - [struct] from planLabelCrop()

  • materialNames - {1xN cell} composed materials

  • compositionReport - [struct] from composeLabelModel()

resetDialog()

RESETDIALOG - Clear the dialog back to its “nothing connected” state.

App Designer stores whatever placeholder tree nodes were drawn on the canvas, and they are restored every time the app is built. They are useful for laying the dialog out but must never be shown to a user, so the tree is emptied here rather than by hand in the designer, where they would have to be deleted to see the layout.

resolveLabelRoute(groupUrl, groupSummary)

RESOLVELABELROUTE - Decide how this group can be loaded as a model.

Three routes exist and they are not interchangeable:

  • model - the group’s finest level already matches the open image, so MibModel.loadModel puts it straight on top. This is what a label store published beside its own volume looks like.

  • overlay - the group covers the same volume as the open image but at a coarser resolution, which a whole-volume inference segmentation published from a coarse level down looks like. With Dataset mode = BigData it is served over the open dataset slice by slice, read-only, nothing downloaded in bulk. See resolveOverlayRoute().

  • crop - the group is a sub-volume of a larger image, an OpenOrganelle ground-truth crop being the case this was built for. Placing it on the open parent volume is impossible - loadModel requires matching dimensions and core.MibBigDataLabelsZarr2 has no origin concept - so instead the crop’s own image region is opened and the labels go onto that. See openLabelCrop().

Deciding here rather than at Open matters: both failure modes are silent otherwise, and the answer is what the info panel should be showing while the user is still choosing.

Input Arguments:
  • groupUrl - [char] URL of the selected group

  • groupSummary - [struct] from probeGroup()

Output Arguments:
  • labelsFit - [logical] whether Open may be pressed

  • reason - [char] the explanation to show, either why it cannot be loaded or what loading it will do

resolveN5Sibling(~, url)

RESOLVEN5SIBLING - Swap an N5 container URL for the OME-Zarr copy beside it.

Syntax:
[resolvedUrl, swapped] = obj.resolveN5Sibling(url)

OpenOrganelle publishes each volume twice, as an N5 container and as an OME-Zarr one, under the same stem:

jrc_hela-2/jrc_hela-2.n5/     <- what the dataset page hands out
jrc_hela-2/jrc_hela-2.zarr/   <- the same volume, readable here

Both the “Fiji” link and the “Copy data url” button on a dataset page give the N5 form, and MIB has no N5 reader at all, so a user following the website lands on a URL that can never work. Rather than reporting that, look for the OME-Zarr copy and use it. Verified present for every dataset checked in the janelia-cosem-datasets bucket.

The swap is confirmed against the store, never assumed: a URL is only rewritten once probeRemoteZarr finds real zarr metadata at the candidate. A deep path inside the container is carried across intact, so .../jrc_hela-2.n5/recon-1/em/fibsem-uint8 resolves to the matching group of the zarr copy rather than to its root.

Input Arguments:
  • url - [char] URL as typed, already normalised by io.RemoteStore.normalise

Output Arguments:
  • resolvedUrl - [char] the OME-Zarr URL when one was found, otherwise url unchanged

  • swapped - [logical] true when the URL was rewritten; the caller says so in the status line rather than changing the address silently

resolveOverlayRoute(~, labelPyramid, openImage)

RESOLVEOVERLAYROUTE - Can these labels be shown over the open BigData image?

The third route out of resolveLabelRoute(), and the only one that leaves the open dataset alone: the labels are served slice by slice from their own store as the view is read, upsampled where the image is finer, with nothing downloaded in bulk. It applies when the label pyramid registers against the open image’s scale space - see io.loaders.OmeZarrMetadataUtils.registerLevelScales, which is what refuses a pyramid describing a different volume.

A fractional scale is refused even though it registers. Nothing in the read path breaks on it; a label would simply be split across an image voxel with no way to say which side it belongs to, and refusing is honest where the crop route would give an exact answer.

Deciding here rather than at Open is what lets the info panel say which of the three things Open will do. It costs no further network traffic: the label pyramid is already in hand and the image is open.

Input Arguments:
  • labelPyramid - [struct] from readGroupPyramid()

  • openImage - [core.MibImage] the open dataset’s image layer

Output Arguments:
  • overlayFits - [logical] whether the overlay route applies

  • reason - [char] what Open will do when it fits, or why it does not

resolveSiblingImageGroup(labelGroupUrl, labelPyramid)

RESOLVESIBLINGIMAGEGROUP - Find the image volume a label crop was cut from.

Syntax:
[imageGroupUrl, imagePyramid] = obj.resolveSiblingImageGroup(labelGroupUrl, labelPyramid)

Walks up from the label group towards the container root, listing each level on the way, and takes the first multiscales group that is not an annotation and whose world box contains the crop.

Two tests, and both are needed:

  • Not under a ``labels`` path. OME-NGFF makes labels/ the container convention for annotations, so this is a rule rather than a guess about names. It is also the only thing that rejects the merged ground truth: crop1/all is a pyramid, carries no cellmap annotation block, and encloses the crop exactly, so every content-based test accepts it. It is a sibling annotation, not the image.

  • Containment. A container can publish several volumes, and a group name says nothing reliable about which one a crop came from, while the coordinates say it exactly.

The path test is applied before any metadata is fetched, which is what keeps the walk cheap: passing back up through groundtruth rejects all 26 crops of the reference store on their paths alone, without a request each.

Walking up rather than searching down from the root also bounds the cost - the sibling of a crop is a handful of levels away, whereas a search from the root would descend into the ground-truth subtree first.

Returns empty when nothing qualifies; the caller then asks the user for the path rather than guessing.

Input Arguments:
  • labelGroupUrl - [char] URL of the selected label group

  • labelPyramid - [struct] its readGroupPyramid() result

Output Arguments:
  • imageGroupUrl - [char] URL of the image group, '' when none found

  • imagePyramid - [struct] its readGroupPyramid result, [] when none

returnBatchOpt(BatchOptOut)

RETURNBATCHOPT - Send BatchOpt to the batch controller.

selectedLabelGroupUrls()

SELECTEDLABELGROUPURLS - Absolute URLs of every label group currently picked.

Syntax:
labelGroupUrls = obj.selectedLabelGroupUrls()

BatchOpt.LabelGroups holds the whole multi-selection as a semicolon- separated list of paths relative to the container root - text, so a recorded protocol replays without a network browse, and semicolons because a URL path can contain a comma but not one of these.

Order is the pick order and it is load-bearing: where two classes overlap, the later pick wins, so the list must never be sorted on the way through.

Falls back to the single GroupPath when no multi-selection was made, which is what makes a one-class crop work with no extra state.

Output Arguments:
  • labelGroupUrls - {1xN cell} absolute URLs, in pick order

setStatus(message)

SETSTATUS - Show a one-line status message, no-op without a view.

startProgress(message)

STARTPROGRESS - Raise the import’s progress bar in Indeterminate mode.

Indeterminate because nothing here can report a percentage. Opening a remote dataset probes the store format, lists a container, resolves a group and reads level metadata, all before a single pixel is fetched - a sequence of round trips whose count is not known in advance and whose duration depends on the host. A bar that sat at 0% through all of it would say less than a moving one.

openLabelCrop() takes this same bar over and switches it to a real percentage once it knows how many label groups it will read.

A no-op in batch mode (no figure to parent to) and when the caller asked for no waitbar.

stopProgress()

STOPPROGRESS - Close the progress bar if one is up.

Must be called before every error dialog, not only on success: a modal progress bar left on screen sits in front of the message explaining why the import stopped.

The isvalid(obj) guard is for the onCleanup handlers in openBtn_Callback and openLabelCrop: a successful import ends with closeWindow, whose CloseEvent makes MibController delete this controller, and the onCleanup then fires on a deleted handle. Touching any property there raises “Invalid or deleted object” from inside a destructor, where it can only be warned about.

treeNodeExpanded_Callback(event)

TREENODEEXPANDED_CALLBACK - Fill a tree node on its first expand.

Syntax:
tree.NodeExpandedFcn = @(~,e) obj.treeNodeExpanded_Callback(e)

One ListObjectsV2 request per expand, and only the first time a node is opened - the same deferred-placeholder pattern as controllers.DatasetInfo.treeNodeExpanded_Callback. Lazy expansion is the whole point of this dialog: enumerating a published container up front would cost hundreds of requests, because the interesting image group usually sits beside a very large labels subtree.

Input Arguments:
  • event - [event data] carries .Node, the node being expanded

treeSelectionChanged_Callback(event)

TREESELECTIONCHANGED_CALLBACK - Describe the newly selected group.

Syntax:
tree.SelectionChangedFcn = @(~,e) obj.treeSelectionChanged_Callback(e)

Two small cached metadata reads per selection - the group’s attributes and its level-0 array header. Selecting is how the user asks “what is this?”, so it has to answer without opening anything.

Several nodes may be selected at once. That is how a ground-truth crop is loaded: each COSEM class is its own group, and a useful model is a blend of them. The full selection is recorded in BatchOpt.LabelGroups in click order, which is load-bearing - where two classes overlap, the later pick wins. Only the first node is described in the info panel, since the pyramid geometry is shared by every group of a crop.

Input Arguments:
  • event - [event data] carries .SelectedNodes

updateBatchOptFromGUI(event)

UPDATEBATCHOPTFROMGUI - Sync BatchOpt from a widget change.

updateWidgets()

UPDATEWIDGETS - Refresh widgets from BatchOpt and model state.

urlValueChanged(event)

URLVALUECHANGED - A new URL invalidates whatever was browsed before.

Deliberately does no network work: this fires on focus loss as well as on Enter, and only Enter means “act on this”. See keyPress_Callback, which runs right after this one.