fix: cache thumbnail in display set (#4782)

This commit is contained in:
Bill Wallace 2025-02-18 18:19:09 -05:00 committed by GitHub
parent d26c9551b6
commit 2410c6a509
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 47 additions and 54 deletions

View File

@ -341,8 +341,10 @@ jobs:
steps: steps:
- install_bun - install_bun
- run: - run:
name: Install Xvfb name: Install System Dependencies
command: sudo apt-get update && sudo apt-get install -y xvfb command: |
sudo apt-get update
sudo apt-get install -y xvfb libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6
- run: - run:
name: Start Xvfb name: Start Xvfb
command: Xvfb :99 -screen 0 1920x1080x24 & command: Xvfb :99 -screen 0 1920x1080x24 &
@ -351,13 +353,11 @@ jobs:
name: Export Display Variable name: Export Display Variable
command: export DISPLAY=:99 command: export DISPLAY=:99
- cypress/install: - cypress/install:
install-browsers: true
install-command: bun install --no-save install-command: bun install --no-save
package-manager: yarn package-manager: yarn
- cypress/run-tests: - cypress/run-tests:
cypress-command: cypress-command: |
npx wait-on@latest http://localhost:3000 && cd platform/app && npx cypress run --record npx wait-on@latest http://localhost:3000 && cd platform/app && npx cypress run --record --parallel
--browser chrome --parallel
start-command: bun run test:data && bun run test:e2e:serve start-command: bun run test:data && bun run test:e2e:serve
DOCKER_MULTIARCH_MANIFEST: DOCKER_MULTIARCH_MANIFEST:

View File

@ -25,6 +25,14 @@ export function getToolbarModule({ servicesManager }: withAppTypes) {
}; };
} }
const activeSegmentation = segmentationService.getActiveSegmentation(viewportId);
if (!Object.keys(activeSegmentation.segments).length) {
return {
disabled: true,
disabledText: 'Add segment to enable this tool',
};
}
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId); const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);
if (!toolGroup) { if (!toolGroup) {

View File

@ -809,7 +809,8 @@ class SegmentationService extends PubSubService {
if (!segmentIndex) { if (!segmentIndex) {
// grab the next available segment index based on the object keys, // grab the next available segment index based on the object keys,
// so basically get the highest segment index value + 1 // so basically get the highest segment index value + 1
segmentIndex = Math.max(...Object.keys(csSegmentation.segments).map(Number)) + 1; const segmentKeys = Object.keys(csSegmentation.segments);
segmentIndex = segmentKeys.length === 0 ? 1 : Math.max(...segmentKeys.map(Number)) + 1;
} }
// update the segmentation // update the segmentation

View File

@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
import { useImageViewer } from '@ohif/ui'; import { useImageViewer } from '@ohif/ui';
import { useViewportGrid } from '@ohif/ui-next'; import { useViewportGrid } from '@ohif/ui-next';
import { StudyBrowser } from '@ohif/ui-next'; import { StudyBrowser } from '@ohif/ui-next';
import { utils } from '@ohif/core'; import { useSystem, utils } from '@ohif/core';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { Separator } from '@ohif/ui-next'; import { Separator } from '@ohif/ui-next';
import { PanelStudyBrowserHeader } from './PanelStudyBrowserHeader'; import { PanelStudyBrowserHeader } from './PanelStudyBrowserHeader';
@ -16,13 +16,12 @@ const { sortStudyInstances, formatDate, createStudyBrowserTabs } = utils;
* @param {*} param0 * @param {*} param0
*/ */
function PanelStudyBrowser({ function PanelStudyBrowser({
servicesManager,
getImageSrc, getImageSrc,
getStudiesForPatientByMRN, getStudiesForPatientByMRN,
requestDisplaySetCreationForStudy, requestDisplaySetCreationForStudy,
dataSource, dataSource,
commandsManager, }) {
}: withAppTypes) { const { servicesManager, commandsManager } = useSystem();
const { hangingProtocolService, displaySetService, uiNotificationService, customizationService } = const { hangingProtocolService, displaySetService, uiNotificationService, customizationService } =
servicesManager.services; servicesManager.services;
const navigate = useNavigate(); const navigate = useNavigate();

View File

@ -5,6 +5,7 @@ import PanelStudyBrowser from './StudyBrowser/PanelStudyBrowser';
import getImageSrcFromImageId from './getImageSrcFromImageId'; import getImageSrcFromImageId from './getImageSrcFromImageId';
import getStudiesForPatientByMRN from './getStudiesForPatientByMRN'; import getStudiesForPatientByMRN from './getStudiesForPatientByMRN';
import requestDisplaySetCreationForStudy from './requestDisplaySetCreationForStudy'; import requestDisplaySetCreationForStudy from './requestDisplaySetCreationForStudy';
import { useSystem } from '@ohif/core';
/** /**
* Wraps the PanelStudyBrowser and provides features afforded by managers/services * Wraps the PanelStudyBrowser and provides features afforded by managers/services
@ -13,7 +14,8 @@ import requestDisplaySetCreationForStudy from './requestDisplaySetCreationForStu
* @param {object} commandsManager * @param {object} commandsManager
* @param {object} extensionManager * @param {object} extensionManager
*/ */
function WrappedPanelStudyBrowser({ extensionManager, servicesManager, commandsManager }) { function WrappedPanelStudyBrowser() {
const { extensionManager } = useSystem();
// TODO: This should be made available a different way; route should have // TODO: This should be made available a different way; route should have
// already determined our datasource // already determined our datasource
const [dataSource] = extensionManager.getActiveDataSource(); const [dataSource] = extensionManager.getActiveDataSource();
@ -29,8 +31,6 @@ function WrappedPanelStudyBrowser({ extensionManager, servicesManager, commandsM
return ( return (
<PanelStudyBrowser <PanelStudyBrowser
servicesManager={servicesManager}
commandsManager={commandsManager}
dataSource={dataSource} dataSource={dataSource}
getImageSrc={_getImageSrcFromImageId} getImageSrc={_getImageSrcFromImageId}
getStudiesForPatientByMRN={_getStudiesForPatientByMRN} getStudiesForPatientByMRN={_getStudiesForPatientByMRN}
@ -61,10 +61,4 @@ function _createGetImageSrcFromImageIdFn(extensionManager) {
} }
} }
WrappedPanelStudyBrowser.propTypes = {
commandsManager: PropTypes.object.isRequired,
extensionManager: PropTypes.object.isRequired,
servicesManager: PropTypes.object.isRequired,
};
export default WrappedPanelStudyBrowser; export default WrappedPanelStudyBrowser;

View File

@ -293,9 +293,6 @@ function TrackedMeasurementsContextProvider(
TrackedMeasurementsContextProvider.propTypes = { TrackedMeasurementsContextProvider.propTypes = {
children: PropTypes.oneOf([PropTypes.func, PropTypes.node]), children: PropTypes.oneOf([PropTypes.func, PropTypes.node]),
servicesManager: PropTypes.object.isRequired,
commandsManager: PropTypes.object.isRequired,
extensionManager: PropTypes.object.isRequired,
appConfig: PropTypes.object, appConfig: PropTypes.object,
}; };

View File

@ -15,14 +15,7 @@ function getPanelModule({ commandsManager, extensionManager, servicesManager }):
iconName: 'tab-studies', iconName: 'tab-studies',
iconLabel: 'Studies', iconLabel: 'Studies',
label: i18n.t('SidePanel:Studies'), label: i18n.t('SidePanel:Studies'),
component: props => ( component: props => <PanelStudyBrowserTracking {...props} />,
<PanelStudyBrowserTracking
{...props}
commandsManager={commandsManager}
extensionManager={extensionManager}
servicesManager={servicesManager}
/>
),
}, },
{ {
name: 'trackedMeasurements', name: 'trackedMeasurements',

View File

@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { utils } from '@ohif/core'; import { useSystem, utils } from '@ohif/core';
import { useImageViewer, Dialog, ButtonEnums } from '@ohif/ui'; import { useImageViewer, Dialog, ButtonEnums } from '@ohif/ui';
import { useViewportGrid } from '@ohif/ui-next'; import { useViewportGrid } from '@ohif/ui-next';
import { StudyBrowser } from '@ohif/ui-next'; import { StudyBrowser } from '@ohif/ui-next';
@ -30,13 +30,12 @@ const thumbnailNoImageModalities = [
* @param {*} param0 * @param {*} param0
*/ */
export default function PanelStudyBrowserTracking({ export default function PanelStudyBrowserTracking({
servicesManager,
getImageSrc, getImageSrc,
getStudiesForPatientByMRN, getStudiesForPatientByMRN,
requestDisplaySetCreationForStudy, requestDisplaySetCreationForStudy,
dataSource, dataSource,
commandsManager, }) {
}: withAppTypes) { const { servicesManager, commandsManager } = useSystem();
const { const {
displaySetService, displaySetService,
uiDialogService, uiDialogService,
@ -183,7 +182,7 @@ export default function PanelStudyBrowserTracking({
// so wait a bit of time to allow the viewports preferential loading // so wait a bit of time to allow the viewports preferential loading
// which improves user experience of responsiveness significantly on slower // which improves user experience of responsiveness significantly on slower
// systems. // systems.
window.setTimeout(() => setHasLoadedViewports(true), 250); window.setTimeout(() => setHasLoadedViewports(true), 1000);
} }
return; return;
@ -211,7 +210,15 @@ export default function PanelStudyBrowserTracking({
return; return;
} }
// When the image arrives, render it and store the result in the thumbnailImgSrcMap // When the image arrives, render it and store the result in the thumbnailImgSrcMap
newImageSrcEntry[dSet.displaySetInstanceUID] = await getImageSrc(imageId); let { thumbnailSrc } = displaySet;
if (!thumbnailSrc && displaySet.getThumbnailSrc) {
thumbnailSrc = await displaySet.getThumbnailSrc();
}
if (!thumbnailSrc) {
let thumbnailSrc = await getImageSrc(imageId);
displaySet.thumbnailSrc = thumbnailSrc;
}
newImageSrcEntry[dSet.displaySetInstanceUID] = thumbnailSrc;
setThumbnailImageSrcMap(prevState => { setThumbnailImageSrcMap(prevState => {
return { ...prevState, ...newImageSrcEntry }; return { ...prevState, ...newImageSrcEntry };
@ -532,7 +539,6 @@ export default function PanelStudyBrowserTracking({
} }
PanelStudyBrowserTracking.propTypes = { PanelStudyBrowserTracking.propTypes = {
servicesManager: PropTypes.object.isRequired,
dataSource: PropTypes.shape({ dataSource: PropTypes.shape({
getImageIdsForDisplaySet: PropTypes.func.isRequired, getImageIdsForDisplaySet: PropTypes.func.isRequired,
}).isRequired, }).isRequired,
@ -593,13 +599,12 @@ function _mapDisplaySets(
displaySets displaySets
.filter(ds => !ds.excludeFromThumbnailBrowser) .filter(ds => !ds.excludeFromThumbnailBrowser)
.forEach(ds => { .forEach(ds => {
const imageSrc = thumbnailImageSrcMap[ds.displaySetInstanceUID]; const { thumbnailSrc, displaySetInstanceUID } = ds; // thumbnailImageSrcMap[ds.displaySetInstanceUID];
const componentType = _getComponentType(ds); const componentType = _getComponentType(ds);
const array = const array =
componentType === 'thumbnailTracked' ? thumbnailDisplaySets : thumbnailNoImageDisplaySets; componentType === 'thumbnailTracked' ? thumbnailDisplaySets : thumbnailNoImageDisplaySets;
const { displaySetInstanceUID } = ds;
const loadingProgress = displaySetLoadingState?.[displaySetInstanceUID]; const loadingProgress = displaySetLoadingState?.[displaySetInstanceUID];
const thumbnailProps = { const thumbnailProps = {
@ -614,7 +619,7 @@ function _mapDisplaySets(
messages: ds.messages, messages: ds.messages,
StudyInstanceUID: ds.StudyInstanceUID, StudyInstanceUID: ds.StudyInstanceUID,
componentType, componentType,
imageSrc, imageSrc: thumbnailSrc || thumbnailImageSrcMap[displaySetInstanceUID],
dragData: { dragData: {
type: 'displayset', type: 'displayset',
displaySetInstanceUID, displaySetInstanceUID,

View File

@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
import PanelStudyBrowserTracking from './PanelStudyBrowserTracking'; import PanelStudyBrowserTracking from './PanelStudyBrowserTracking';
import getImageSrcFromImageId from './getImageSrcFromImageId'; import getImageSrcFromImageId from './getImageSrcFromImageId';
import { requestDisplaySetCreationForStudy } from '@ohif/extension-default'; import { requestDisplaySetCreationForStudy } from '@ohif/extension-default';
import { useSystem } from '@ohif/core';
function _getStudyForPatientUtility(extensionManager) { function _getStudyForPatientUtility(extensionManager) {
const utilityModule = extensionManager.getModuleEntry( const utilityModule = extensionManager.getModuleEntry(
@ -21,11 +22,8 @@ function _getStudyForPatientUtility(extensionManager) {
* @param {object} commandsManager * @param {object} commandsManager
* @param {object} extensionManager * @param {object} extensionManager
*/ */
function WrappedPanelStudyBrowserTracking({ function WrappedPanelStudyBrowserTracking() {
commandsManager, const { extensionManager } = useSystem();
extensionManager,
servicesManager,
}: withAppTypes) {
const dataSource = extensionManager.getActiveDataSource()[0]; const dataSource = extensionManager.getActiveDataSource()[0];
const getStudiesForPatientByMRN = _getStudyForPatientUtility(extensionManager); const getStudiesForPatientByMRN = _getStudyForPatientUtility(extensionManager);
@ -41,8 +39,6 @@ function WrappedPanelStudyBrowserTracking({
return ( return (
<PanelStudyBrowserTracking <PanelStudyBrowserTracking
servicesManager={servicesManager}
commandsManager={commandsManager}
dataSource={dataSource} dataSource={dataSource}
getImageSrc={_getImageSrcFromImageId} getImageSrc={_getImageSrcFromImageId}
getStudiesForPatientByMRN={_getStudiesForPatientByMRN} getStudiesForPatientByMRN={_getStudiesForPatientByMRN}
@ -73,10 +69,4 @@ function _createGetImageSrcFromImageIdFn(extensionManager) {
} }
} }
WrappedPanelStudyBrowserTracking.propTypes = {
commandsManager: PropTypes.object.isRequired,
extensionManager: PropTypes.object.isRequired,
servicesManager: PropTypes.object.isRequired,
};
export default WrappedPanelStudyBrowserTracking; export default WrappedPanelStudyBrowserTracking;

View File

@ -12,6 +12,12 @@ export type DisplaySet = {
Modality?: string; Modality?: string;
imageIds?: string[]; imageIds?: string[];
images?: unknown[]; images?: unknown[];
// Details about how to display:
/** A URL that can be used to display the thumbnail. Typically a data url */
thumbnailSrc?: string;
/** A fetch method to get the thumbnail */
getThumbnailSrc?(imageId?: string): Promise<string>;
SeriesDate?: string; SeriesDate?: string;
SeriesTime?: string; SeriesTime?: string;
instance?: InstanceMetadata; instance?: InstanceMetadata;