-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
[WIP] feat/decimated loading #6034
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
e460845
37cf727
250e62e
c28e8ca
76ca64c
9c8d76e
260643a
a0693a9
3a2139f
e80bc11
c491996
492091a
b12b15c
911ad12
6a8efa6
508e4fd
67a9aa7
0db6564
4df9a6e
b980c73
777b594
4ee5756
6e689e7
98a132a
aa91164
3dcfa0a
e4d8e01
cf272d6
2bf13a8
52467f0
9b25201
2bb981f
b970eab
d64a2ae
dd16c01
291f93d
9c5347b
4ae3180
61ae3d8
7e996ff
82b2b0f
1da2a55
281d3cb
7f5c6e3
4670a08
b568417
5d75e7e
0f96344
a74b484
2648ec1
cb20091
8bb999b
edda830
8026b4b
bf00639
f661f74
5d0337e
59448bf
9392bec
487f7d2
dcedf30
8b878dd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| import React from 'react'; | ||
| import { useViewportRendering } from '../../hooks'; | ||
|
|
||
| /** | ||
| * Renders the auto-decimation message in the viewport top-right, same style as | ||
| * demographic overlay items (Study Date, Series Description). | ||
| */ | ||
| function AutoDecimationOverlay({ | ||
| viewportId, | ||
| servicesManager, | ||
| }: { | ||
| viewportId: string; | ||
| servicesManager: AppTypes.ServicesManager; | ||
| }) { | ||
| const { isViewportBackgroundLight: isLight } = useViewportRendering(viewportId); | ||
| const options = servicesManager?.services?.cornerstoneViewportService | ||
| ?.getViewportInfo(viewportId) | ||
| ?.getViewportOptions?.(); | ||
| const info = options?.autoDecimationInfo; | ||
| const viewportType = options?.viewportType; | ||
| const isVolume = | ||
| viewportType === 'orthographic' || | ||
| viewportType === 'volume3d'; | ||
|
|
||
| if (!info?.message || !isVolume) { | ||
| return null; | ||
| } | ||
|
Comment on lines
+15
to
+27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Inspect useViewportRendering to confirm what it subscribes to / re-renders on.
fd -t f 'useViewportRendering' extensions/cornerstone/src/hooks
fd -t f 'useViewportRendering' extensions/cornerstone/src/hooks --exec sed -n '1,200p' {}
# Check how reloadVolumeWithDecimation updates viewport options / broadcasts.
rg -nP 'autoDecimationInfo|VIEWPORT_DATA_CHANGED|reloadVolumeWithDecimation' extensions/cornerstone/src -C2Repository: OHIF/Viewers Length of output: 18977 Ensure
Fix by subscribing directly to Affected code const { isViewportBackgroundLight: isLight } = useViewportRendering(viewportId);
const options = servicesManager?.services?.cornerstoneViewportService
?.getViewportInfo(viewportId)
?.getViewportOptions?.();
const info = options?.autoDecimationInfo;
const viewportType = options?.viewportType;
const isVolume =
viewportType === 'orthographic' ||
viewportType === 'volume3d';
if (!info?.message || !isVolume) {
return null;
}🤖 Prompt for AI Agents |
||
|
|
||
| const colorClass = 'text-yellow-400'; | ||
| const shadowClass = isLight ? 'shadow-light' : 'shadow-dark'; | ||
|
|
||
| return ( | ||
| <div | ||
| className={`absolute viewport-overlay auto-decimation-overlay pointer-events-none ${colorClass} ${shadowClass} text-base leading-5 text-right`} | ||
| style={{ top: '0.5rem', right: '3.5rem', maxWidth: 'calc(100% - 4rem)' }} | ||
| title="Volume auto-decimated" | ||
| data-cy="auto-decimation-overlay" | ||
| > | ||
| <div className="overlay-item"> | ||
| <span className="break-words whitespace-pre-line">{info.message}</span> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| export default AutoDecimationOverlay; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,200 @@ | ||
| import React, { useMemo } from 'react'; | ||
| import PropTypes from 'prop-types'; | ||
| import { VolumeViewport3D, utilities as csUtils } from '@cornerstonejs/core'; | ||
| import { | ||
| SmartScrollbar, | ||
| SmartScrollbarTrack, | ||
| SmartScrollbarFill, | ||
| SmartScrollbarIndicator, | ||
| SmartScrollbarEndpoints, | ||
| } from '@ohif/ui-next'; | ||
| import { getViewportImageIds } from './helpers'; | ||
| import { | ||
| useLoadedSliceBytes, | ||
| useProgressScrollbarMode, | ||
| useViewedSliceBytes, | ||
| useViewportSliceSync, | ||
| } from './hooks'; | ||
| import { ViewportSliceProgressScrollbarProps } from './types'; | ||
|
|
||
| function ViewportSliceProgressScrollbar({ | ||
| viewportData, | ||
| viewportId, | ||
| element, | ||
| imageSliceData, | ||
| setImageSliceData, | ||
| servicesManager, | ||
| }: ViewportSliceProgressScrollbarProps) { | ||
| const { cineService, cornerstoneViewportService, customizationService, viewedDataService } = | ||
| servicesManager.services; | ||
|
|
||
| const showLoadedEndpoints = | ||
| customizationService.getCustomization('viewportScrollbar.showLoadedEndpoints') !== false; | ||
| const showLoadedFill = | ||
| customizationService.getCustomization('viewportScrollbar.showLoadedFill') !== false; | ||
| const showViewedFill = | ||
| customizationService.getCustomization('viewportScrollbar.showViewedFill') !== false; | ||
| const showLoadingPattern = | ||
| customizationService.getCustomization('viewportScrollbar.showLoadingPattern') !== false; | ||
| const viewedDwellMsRaw = customizationService.getCustomization('viewportScrollbar.viewedDwellMs'); | ||
| const loadedBatchIntervalMsRaw = customizationService.getCustomization( | ||
| 'viewportScrollbar.loadedBatchIntervalMs' | ||
| ); | ||
| const viewedDwellMs = | ||
| typeof viewedDwellMsRaw === 'number' && viewedDwellMsRaw >= 0 ? viewedDwellMsRaw : 0; | ||
| const loadedBatchIntervalMs = | ||
| typeof loadedBatchIntervalMsRaw === 'number' && loadedBatchIntervalMsRaw >= 0 | ||
| ? loadedBatchIntervalMsRaw | ||
| : 200; | ||
|
|
||
| const { numberOfSlices, imageIndex } = imageSliceData; | ||
|
|
||
| const imageIds = useMemo(() => getViewportImageIds(viewportData), [viewportData]); | ||
| const imageIdToIndex = useMemo(() => { | ||
| const map = new Map<string, number>(); | ||
| for (let i = 0; i < imageIds.length; i++) { | ||
| const imageId = imageIds[i]; | ||
| if (imageId) { | ||
| map.set(imageId, i); | ||
| } | ||
| } | ||
| return map; | ||
| }, [imageIds]); | ||
|
|
||
| const isFullMode = useProgressScrollbarMode({ | ||
| viewportData, | ||
| viewportId, | ||
| element, | ||
| cornerstoneViewportService, | ||
| }); | ||
|
|
||
| useViewportSliceSync({ | ||
| viewportData, | ||
| viewportId, | ||
| element, | ||
| cornerstoneViewportService, | ||
| setImageSliceData, | ||
| }); | ||
|
|
||
| const { | ||
| bytes: loadedBytes, | ||
| version: loadedVersion, | ||
| isFull: isFullyLoaded, | ||
| } = useLoadedSliceBytes({ | ||
| isFullMode, | ||
| numberOfSlices, | ||
| viewportData, | ||
| imageIds, | ||
| imageIdToIndex, | ||
| loadedBatchIntervalMs, | ||
| }); | ||
|
|
||
| const { bytes: viewedBytes, version: viewedVersion } = useViewedSliceBytes({ | ||
| isFullMode, | ||
| numberOfSlices, | ||
| imageIndex, | ||
| imageIds, | ||
| imageIdToIndex, | ||
| viewedDwellMs, | ||
| viewedDataService, | ||
| }); | ||
|
|
||
| const onScrollbarValueChange = targetImageIndex => { | ||
| const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); | ||
|
|
||
| if (!viewport || viewport instanceof VolumeViewport3D) { | ||
| return; | ||
| } | ||
|
|
||
| const { isCineEnabled } = cineService.getState(); | ||
|
|
||
| if (isCineEnabled) { | ||
| cineService.stopClip(element, { viewportId }); | ||
| cineService.setCine({ id: viewportId, frameRate: undefined, isPlaying: false }); | ||
| } | ||
|
|
||
| csUtils.jumpToSlice(viewport.element, { | ||
| imageIndex: targetImageIndex, | ||
| debounceLoading: true, | ||
| }); | ||
| }; | ||
|
|
||
| const isLoading = isFullMode && showLoadingPattern ? !isFullyLoaded : false; | ||
|
|
||
| if (!numberOfSlices || numberOfSlices <= 1) { | ||
| return null; | ||
| } | ||
|
|
||
| return ( | ||
| <div | ||
| style={{ | ||
| position: 'absolute', | ||
| right: 0, | ||
| top: 0, | ||
| height: '100%', | ||
| padding: '8px 5px', | ||
| zIndex: 10, | ||
| }} | ||
| > | ||
| <div | ||
| style={{ | ||
| position: 'relative', | ||
| height: '100%', | ||
| width: '11px', | ||
| }} | ||
| > | ||
| <SmartScrollbar | ||
| className="absolute inset-0" | ||
| value={imageIndex || 0} | ||
| total={numberOfSlices} | ||
| onValueChange={onScrollbarValueChange} | ||
| isLoading={isLoading} | ||
| enableKeyboardNavigation={false} | ||
| aria-label="Image navigation scrollbar" | ||
| indicator={ | ||
| customizationService.getCustomization('viewportScrollbar.indicator') as | ||
| | Record<string, unknown> | ||
| | undefined | ||
| } | ||
| > | ||
| <SmartScrollbarTrack> | ||
| {isFullMode && showLoadedFill && ( | ||
| <SmartScrollbarFill | ||
| marked={loadedBytes} | ||
| version={loadedVersion} | ||
| className="bg-neutral/25" | ||
| loadingClassName="bg-neutral/50" | ||
| /> | ||
| )} | ||
| {isFullMode && showViewedFill && ( | ||
| <SmartScrollbarFill | ||
| marked={viewedBytes} | ||
| version={viewedVersion} | ||
| className="bg-primary/35" | ||
| loadingClassName="bg-primary/35" | ||
| /> | ||
| )} | ||
| </SmartScrollbarTrack> | ||
| <SmartScrollbarIndicator /> | ||
| {isFullMode && showLoadedEndpoints && ( | ||
| <SmartScrollbarEndpoints | ||
| marked={loadedBytes} | ||
| version={loadedVersion} | ||
| /> | ||
| )} | ||
| </SmartScrollbar> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| ViewportSliceProgressScrollbar.propTypes = { | ||
| viewportData: PropTypes.object, | ||
| viewportId: PropTypes.string.isRequired, | ||
| element: PropTypes.instanceOf(Element), | ||
| imageSliceData: PropTypes.object.isRequired, | ||
| setImageSliceData: PropTypes.func.isRequired, | ||
| servicesManager: PropTypes.object.isRequired, | ||
| }; | ||
|
|
||
| export default ViewportSliceProgressScrollbar; |
Uh oh!
There was an error while loading. Please reload this page.