sam — Segment-anything helpers¶
The +utils/+sam package holds the controller-independent helpers of the
interactive segment-anything tools (segmentationSAM, segmentationSAM2).
Those tools read and write the layers in the block mode, i.e. only the part of the dataset that is currently shown, and they segment an object with a series of clicks. The state of the layers taken on the first click therefore has to survive a zoom, a pan or a new seeded slice; this package describes the shown block, re-maps a cached image between two blocks and serves the cached states to the segmenters.
It also holds the post-processing applied to what the model returns, currently the removal of the small low-confidence islands that appear beside the segmented object.
- utils.sam.alignInitialImage(cachedImage, cachedBox, currentImage, currentBox)¶
ALIGNINITIALIMAGE - Re-map an image cached over one shown block onto another block.
Syntax:
alignedImage = utils.sam.alignInitialImage(cachedImage, cachedBox, currentImage, currentBox)The interactive SAM tools cache the state of the destination layer as it was before the segmentation of the current object started, so that every refinement click can be merged with that initial state instead of with the result of the previous click. The cache is taken in the block mode and becomes invalid as soon as the user zooms, pans or seeds a new slice.
This function rebuilds the initial state for the new block: it starts from currentImage (the layer as it is right now, which outside of cachedBox was never touched by SAM and therefore still holds the initial state) and pastes cachedImage over the overlapping part. When the two blocks cannot be related to each other (different orientation or different pyramid level) currentImage is returned unchanged.
- Input Arguments:
cachedImage - [numeric] image cached over cachedBox,
[height, width, depth]cachedBox - struct returned by
utils.sam.blockBox()at the moment cachedImage was taken; can be emptycurrentImage - [numeric] the destination layer fetched over currentBox
currentBox - struct returned by
utils.sam.blockBox()for the current view
- Output Arguments:
alignedImage - [numeric] currentImage with cachedImage pasted into the overlapping area; same size and class as currentImage
Usage:
Example 1 - restore the pre-SAM state after the view was zoomed out
currentBox = utils.sam.blockBox(dataset, [z1 z2]); initialImage = utils.sam.alignInitialImage(cachedImage, cachedBox, currentImage, currentBox);
- utils.sam.blockBox(dataset, zRange)¶
BLOCKBOX - Describe the currently shown block used by the SAM segmenters.
Syntax:
box = utils.sam.blockBox(dataset) box = utils.sam.blockBox(dataset, zRange)The interactive SAM tools read and write the destination layer in the block mode, i.e. only the part of the dataset that is currently visible in the image view. The extent of that block changes as soon as the user zooms or pans, so any image cached between two clicks has to be tagged with the block it was taken from. This function returns that tag; it mirrors the cropping performed by
core.MibDataset.getData3D()whenoptions.blockModeSwitch = true.- Input Arguments:
dataset - [core.MibDataset] dataset that is being segmented
zRange (optional) - [numeric]
[zMin, zMax]range of slices of the block; when missing or empty, the currently shown slice is used
- Output Arguments:
box - struct describing the shown block:
.x-[xMin, xMax]horizontal extent in the dataset coordinates.y-[yMin, yMax]vertical extent in the dataset coordinates.z-[zMin, zMax]range of slices.orientation- orientation of the dataset the block was taken in.magFactor- scaling between the dataset coordinates and the pixels of the fetched block:1for the memory-resident datasets anddataset.magFactorfor the pyramidal (BigData, Virtual) datasets, where the block is fetched at the displayed pyramid level
Usage:
Example 1 - tag a cached image with the block it was taken from
box = utils.sam.blockBox(dataset, [z1 z2]);
- utils.sam.initialImage(mibModel, dataset, cacheName, layerName, timePoint, materialIndex, getDataOptions, zRange, targetSize)¶
INITIALIMAGE - Return a layer of the dataset as it was before the current SAM interaction started.
Syntax:
initialImage = utils.sam.initialImage(mibModel, dataset, cacheName, layerName, timePoint, materialIndex, getDataOptions, zRange) initialImage = utils.sam.initialImage(mibModel, dataset, cacheName, layerName, timePoint, materialIndex, getDataOptions, zRange, targetSize)The interactive SAM tools segment an object with a series of clicks: the first click creates the object and each Shift/Ctrl-click re-runs the model with an extra positive/negative seed. Both the layer the results are added to and the material the results are restricted to have to be taken as they were before the first click, because every run overwrites what the previous one produced:
'initialImageAddTo'- the destination layer; merging with its current state instead would keep the discarded parts of the previous run and make the negative seeds useless'initialImageSelected'- the “fix selection to material” mask; using its current state instead would exclude the area already taken by the object itself (the first click moved those pixels out of the restricting material), so every second click would erase the object and the next one would bring it back
Those states are cached in
mibModel.sessionSettings.SAMsegmenterbycontrollers.MibImageDocument.gui_WindowButtonDownFcnon the first click, together with the shown block they were taken from (field<cacheName>Box, seeutils.sam.blockBox()). A cache is used as-is while the block is unchanged; after a zoom, a pan or a new seeded slice it is re-mapped onto the current block byutils.sam.alignInitialImage()and, when the block has grown, stored back so that the following clicks keep the pre-interaction state over the wider area.- Input Arguments:
mibModel - [models.MibModel] the model, keeps the caches in
sessionSettingsdataset - [core.MibDataset] dataset that is being segmented
cacheName - [char] name of the cached field:
'initialImageAddTo'or'initialImageSelected'layerName - [char] layer to fetch:
'selection','mask'or'labels'timePoint - [numeric] index of the time point to fetch
materialIndex - [numeric] index of the material to fetch
getDataOptions - struct with options for
models.MibModel.getData3D();.blockModeSwitchand.zare set by this functionzRange - [numeric]
[zMin, zMax]range of slices of the current runtargetSize (optional) - [numeric] size of the segmentation result the returned image has to be combined with; the image is cropped or zero-padded to it. Guards against the one-pixel differences between the image and the model blocks of the pyramidal datasets
- Output Arguments:
initialImage - [uint8] the requested layer as it was before the current SAM interaction started, cropped to the shown block over zRange,
[height, width, depth]
Usage:
Example 1 - merge the results of the current run with the initial state
initialImage = utils.sam.initialImage(obj.mibModel, dataset, 'initialImageAddTo', 'labels', t, selMaterialIndex, getDataOpt, [z1 z2], size(imgDataset)); obj.mibModel.setData3D({bitor(initialImage, imgDataset)}, 'labels', t, dataset.orientation, selMaterialIndex, getDataOpt);Example 2 - restrict the results to the selected material
materialMask = utils.sam.initialImage(obj.mibModel, dataset, 'initialImageSelected', 'labels', t, selectedFixToMaterial, getDataOpt, [z1 z2], size(imgDataset)); imgDataset = bitand(imgDataset, materialMask);
- utils.sam.removeSmallRegions(mask, minArea)¶
REMOVESMALLREGIONS - Drop the small islands SAM returns beside the real object.
Syntax:
mask = utils.sam.removeSmallRegions(mask, minArea)Both segment-anything predictors binarize a continuous field of mask logits at a hard zero, so any structure elsewhere in the view that scores just above it is promoted to full membership with the same weight as the object under the seed. In practice the object comes back at a logit of several units while these islands sit at 0.3-0.8, which makes them small: a handful of pixels of the low-resolution mask the model predicts before it is upsampled to the size of the shown block. An area threshold separates the two cleanly, whereas raising the logit threshold enough to lose the islands also eats into the object and eventually breaks it apart.
Components are measured slice by slice, matching the per-2D-mask semantics of
min_mask_region_areain the automatic mask generator, which receives the same preference. Note that for pyramidal (BigData, Virtual) datasets SAM works on the block at the displayed resolution, so the threshold counts pixels of the shown image and not of the full-resolution dataset.- Input Arguments:
mask - [numeric | logical] segmentation mask as
[height, width]or[height, width, depth]; non-zero pixels are treated as foregroundminArea - [numeric] smallest connected component to keep, in pixels.
0, negative and empty all disable the filter and returnmaskunchanged
- Output Arguments:
mask - the input mask with every 8-connected component smaller than
minAreaset to zero; class and pixel values of the surviving components are preserved
Usage:
Example 1 - filter the output of an interactive SAM prediction
minRegionArea = obj.mibModel.preferences.SegmTools.SAM2.min_mask_region_area; imgOut = utils.sam.removeSmallRegions(imgOut, minRegionArea);