Skip to content

Commit ca2cd23

Browse files
committed
fix: improve zoomToSegment for scattered segments
For TILED_SPARSE segmentations with scattered features (e.g., nuclei annotations spread across a large region), the bounding box encompasses all frames and can be extremely large. Previously, zoomToSegment would fit this large bounding box, resulting in a very zoomed-out view. Now, if the segment's bounding box is larger than 10x the viewport at the target zoom level, the method zooms to the segment's max zoom level centered on the bounding box instead of fitting the entire box. The 10x threshold is intentionally high to avoid affecting normal segments like tumor regions or lesions that may span several tiles but should still be shown in full. Only truly scattered segments (nuclei across an entire slide region) trigger the centered zoom.
1 parent 26494ad commit ca2cd23

1 file changed

Lines changed: 65 additions & 15 deletions

File tree

src/viewer.js

Lines changed: 65 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5752,6 +5752,11 @@ class VolumeImageViewer {
57525752
/**
57535753
* Zoom to a segment's bounding box.
57545754
*
5755+
* For segments with compact bounding boxes, fits the view to show the
5756+
* entire segment with some context. For segments with large bounding boxes
5757+
* (e.g., TILED_SPARSE with scattered features), zooms to the segment's
5758+
* max zoom level centered on the bounding box.
5759+
*
57555760
* @param {string} segmentUID - Unique tracking identifier of a segment
57565761
*/
57575762
zoomToSegment(segmentUID) {
@@ -5768,22 +5773,67 @@ class VolumeImageViewer {
57685773
if (segment.boundingBox != null) {
57695774
const extent = segment.boundingBox
57705775
const center = getCenter(extent)
5771-
const width = getWidth(extent)
5772-
const height = getHeight(extent)
5773-
5774-
/** Expand extent slightly for context (scale factor 1.5) */
5775-
const scale = 1.5
5776-
const expandedExtent = [
5777-
center[0] - (width * scale) / 2,
5778-
center[1] - (height * scale) / 2,
5779-
center[0] + (width * scale) / 2,
5780-
center[1] + (height * scale) / 2,
5781-
]
5776+
const extentWidth = getWidth(extent)
5777+
const extentHeight = getHeight(extent)
57825778

5783-
view.fit(expandedExtent, {
5784-
duration: 500,
5785-
maxZoom: segment.maxZoomLevel,
5786-
})
5779+
/**
5780+
* Get the viewport size in map coordinates at the target zoom level.
5781+
* If the segment bounding box is extremely large relative to the viewport
5782+
* (indicating scattered features like TILED_SPARSE nuclei spread across
5783+
* a large region), zoom to the max zoom level centered on the segment
5784+
* rather than fitting the entire bounding box.
5785+
*
5786+
* We use a high threshold (10x) to avoid affecting normal segments like
5787+
* tumor regions that may span several tiles but should still be shown
5788+
* in full. Only truly scattered segments (e.g., nuclei across an entire
5789+
* slide region) will trigger the centered zoom behavior.
5790+
*/
5791+
const viewportSize = this[_map].getSize()
5792+
if (!viewportSize) {
5793+
console.warn('Cannot get map size for zoom calculation')
5794+
return
5795+
}
5796+
5797+
const targetZoom = segment.maxZoomLevel
5798+
const targetResolution = view.getResolutionForZoom(targetZoom)
5799+
const viewportWidthAtTarget = viewportSize[0] * targetResolution
5800+
const viewportHeightAtTarget = viewportSize[1] * targetResolution
5801+
5802+
/**
5803+
* Threshold of 10x viewport size - only extremely large/scattered
5804+
* segments trigger centered zoom behavior. Normal segments (tumor
5805+
* regions, lesions, etc.) will still fit their bounding box.
5806+
*/
5807+
const scatterThreshold = 10
5808+
const isScatteredSegment =
5809+
extentWidth > viewportWidthAtTarget * scatterThreshold ||
5810+
extentHeight > viewportHeightAtTarget * scatterThreshold
5811+
5812+
if (isScatteredSegment) {
5813+
/**
5814+
* Bounding box is extremely large (scattered features across a wide
5815+
* region), zoom to max zoom centered on the segment's bounding box.
5816+
*/
5817+
view.animate({
5818+
center: center,
5819+
zoom: targetZoom,
5820+
duration: 500,
5821+
})
5822+
} else {
5823+
/** Expand extent slightly for context (scale factor 1.5) */
5824+
const scale = 1.5
5825+
const expandedExtent = [
5826+
center[0] - (extentWidth * scale) / 2,
5827+
center[1] - (extentHeight * scale) / 2,
5828+
center[0] + (extentWidth * scale) / 2,
5829+
center[1] + (extentHeight * scale) / 2,
5830+
]
5831+
5832+
view.fit(expandedExtent, {
5833+
duration: 500,
5834+
maxZoom: segment.maxZoomLevel,
5835+
})
5836+
}
57875837
} else {
57885838
console.warn(`Segment "${segmentUID}" has no bounding box to zoom to`)
57895839
}

0 commit comments

Comments
 (0)