feat: new Total Metabolic Tumor Volume tmtv (#2866)

* fix: ptDisplaySet metadata problem

* fix: typo in xhrHeader field

* fix: temporary fix for crosshairs on reload data

* fix hotkey bug for input fields

* upgrade docusarusu to stable release

* circle ci node upgrade

* cypress node upgrade

* bump node in netlify

* bump node version

* remove versioned docs

* update resources

* fix: webpack injection of plugins

* fix meaningful label for panels

* add reviews and fix cli for creation

* copy over chunks if exists in the build

* edit templates

* edit templates

* fix yarn lock

* fix(plugin):Deploy on windows is broken

Co-authored-by: Bill Wallace <wayfarer3130@gmail.com>
This commit is contained in:
Alireza 2022-08-17 15:13:59 -04:00 committed by GitHub
parent ae897c47e0
commit 10b4ed5f8c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 2020 additions and 811 deletions

View File

@ -18,17 +18,17 @@ executors:
# Custom executor to override Cypress config # Custom executor to override Cypress config
deploy-to-prod-executor: deploy-to-prod-executor:
docker: docker:
- image: cimg/node:14.18 - image: cimg/node:16.14
environment: environment:
CYPRESS_BASE_URL: https://ohif-staging.netlify.com/ CYPRESS_BASE_URL: https://ohif-staging.netlify.com/
chrome-and-pacs: chrome-and-pacs:
docker: docker:
# Primary container image where all steps run. # Primary container image where all steps run.
- image: 'cypress/browsers:node14.17.0-chrome88-ff89' - image: 'cypress/browsers:node16.14.2-slim-chrome103-ff102'
defaults: &defaults defaults: &defaults
docker: docker:
- image: cimg/node:14.18-browsers - image: cimg/node:16.14-browsers
environment: environment:
TERM: xterm # Enable colors in term TERM: xterm # Enable colors in term
QUICK_BUILD: true QUICK_BUILD: true
@ -181,7 +181,7 @@ jobs:
DEPLOY_TO_DEV: DEPLOY_TO_DEV:
docker: docker:
- image: circleci/node:14.17.0 - image: circleci/node:16.14.0
environment: environment:
TERM: xterm TERM: xterm
NETLIFY_SITE_ID: 32708787-c9b0-4634-b50f-7ca41952da77 NETLIFY_SITE_ID: 32708787-c9b0-4634-b50f-7ca41952da77
@ -196,7 +196,7 @@ jobs:
DEPLOY_TO_STAGING: DEPLOY_TO_STAGING:
docker: docker:
- image: circleci/node:14.17.0 - image: circleci/node:16.14.0
environment: environment:
TERM: xterm TERM: xterm
NETLIFY_SITE_ID: c7502ae3-b150-493c-8422-05701e44a969 NETLIFY_SITE_ID: c7502ae3-b150-493c-8422-05701e44a969
@ -211,7 +211,7 @@ jobs:
DEPLOY_TO_PRODUCTION: DEPLOY_TO_PRODUCTION:
docker: docker:
- image: circleci/node:14.17.0 - image: circleci/node:16.14.0
environment: environment:
TERM: xterm TERM: xterm
NETLIFY_SITE_ID: 79c4a5da-5c95-4dc9-84f7-45fd9dfe21b0 NETLIFY_SITE_ID: 79c4a5da-5c95-4dc9-84f7-45fd9dfe21b0

View File

@ -1 +1 @@
14.18.0 16.14.0

View File

@ -4,5 +4,6 @@
"proseWrap": "always", "proseWrap": "always",
"tabWidth": 2, "tabWidth": 2,
"semi": true, "semi": true,
"singleQuote": true "singleQuote": true,
"arrowParens": "avoid"
} }

View File

@ -45,7 +45,7 @@
"dependencies": { "dependencies": {
"@babel/runtime": "7.16.3", "@babel/runtime": "7.16.3",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"@cornerstonejs/core": "^0.13.11", "@cornerstonejs/core": "^0.14.4",
"@cornerstonejs/tools": "^0.20.15" "@cornerstonejs/tools": "^0.21.7"
} }
} }

View File

@ -29,7 +29,7 @@
"peerDependencies": { "peerDependencies": {
"@ohif/core": "^3.0.0", "@ohif/core": "^3.0.0",
"@ohif/ui": "^2.0.0", "@ohif/ui": "^2.0.0",
"cornerstone-wado-image-loader": "^4.2.0", "cornerstone-wado-image-loader": "^4.2.1",
"dcmjs": "^0.24.5", "dcmjs": "^0.24.5",
"dicom-parser": "^1.8.9", "dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8", "hammerjs": "^2.0.8",
@ -43,9 +43,9 @@
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "7.17.9", "@babel/runtime": "7.17.9",
"@cornerstonejs/core": "^0.13.11", "@cornerstonejs/core": "^0.14.4",
"@cornerstonejs/streaming-image-volume-loader": "^0.4.10", "@cornerstonejs/streaming-image-volume-loader": "^0.4.16",
"@cornerstonejs/tools": "^0.20.15", "@cornerstonejs/tools": "^0.21.7",
"@kitware/vtk.js": "^24.18.7", "@kitware/vtk.js": "^24.18.7",
"dom-to-image": "^2.6.0", "dom-to-image": "^2.6.0",
"lodash.debounce": "4.0.8", "lodash.debounce": "4.0.8",

View File

@ -195,9 +195,11 @@ const OHIFCornerstoneViewport = React.memo(props => {
DisplaySetService DisplaySetService
); );
const keepCamera = true;
CornerstoneViewportService.updateViewport( CornerstoneViewportService.updateViewport(
viewportIndex, viewportIndex,
newViewportData newViewportData,
keepCamera
); );
setViewportData(newViewportData); setViewportData(newViewportData);

View File

@ -41,7 +41,7 @@ function CornerstoneOverlays(props) {
viewportIndex viewportIndex
); );
if (viewportInfo?.viewportOptions?.customViewportOptions?.hideOverlays) { if (viewportInfo?.viewportOptions?.customViewportProps?.hideOverlays) {
return null; return null;
} }
} }

View File

@ -115,19 +115,18 @@ const commandsModule = ({ servicesManager }) => {
const renderingEngine = CornerstoneViewportService.getRenderingEngine(); const renderingEngine = CornerstoneViewportService.getRenderingEngine();
const viewport = renderingEngine.getViewport(viewportId); const viewport = renderingEngine.getViewport(viewportId);
const lower = windowCenterNum - windowWidthNum / 2.0; const { lower, upper } = csUtils.windowLevel.toLowHighRange(
const upper = windowCenterNum + windowWidthNum / 2.0; windowWidthNum,
windowCenterNum
);
if (viewport instanceof StackViewport) { viewport.setProperties({
viewport.setProperties({ voiRange: {
voiRange: { upper,
upper, lower,
lower, },
}, });
}); viewport.render();
viewport.render();
}
}, },
toggleCrosshairs({ toolGroupId, toggledState }) { toggleCrosshairs({ toolGroupId, toggledState }) {
const toolName = 'Crosshairs'; const toolName = 'Crosshairs';

View File

@ -166,14 +166,12 @@ class CornerstoneViewportService implements IViewportService {
const viewportInfo = this.viewportsInfo.get(viewportIndex); const viewportInfo = this.viewportsInfo.get(viewportIndex);
viewportInfo.setRenderingEngineId(renderingEngine.id); viewportInfo.setRenderingEngineId(renderingEngine.id);
const { const { viewportOptions, displaySetOptions } =
viewportOptions, this._getViewportAndDisplaySetOptions(
displaySetOptions, publicViewportOptions,
} = this._getViewportAndDisplaySetOptions( publicDisplaySetOptions,
publicViewportOptions, viewportInfo
publicDisplaySetOptions, );
viewportInfo
);
viewportInfo.setViewportOptions(viewportOptions); viewportInfo.setViewportOptions(viewportOptions);
viewportInfo.setDisplaySetOptions(displaySetOptions); viewportInfo.setDisplaySetOptions(displaySetOptions);
@ -400,48 +398,57 @@ class CornerstoneViewportService implements IViewportService {
volume.load(); volume.load();
}); });
this.setVolumesForViewport(viewport, volumeInputArray); // This returns the async continuation only
return this.setVolumesForViewport(viewport, volumeInputArray);
} }
public setVolumesForViewport(viewport, volumeInputArray) { public async setVolumesForViewport(viewport, volumeInputArray) {
viewport.setVolumes(volumeInputArray).then(() => { await viewport.setVolumes(volumeInputArray);
const viewportInfo = this.getViewportInfo(viewport.id);
const initialImageOptions = viewportInfo.getInitialImageOptions();
if ( const viewportInfo = this.getViewportInfo(viewport.id);
initialImageOptions && const initialImageOptions = viewportInfo.getInitialImageOptions();
(initialImageOptions.preset !== undefined ||
initialImageOptions.index !== undefined)
) {
const { index, preset } = initialImageOptions;
const { numberOfSlices } = csUtils.getImageSliceDataForVolumeViewport( if (
viewport initialImageOptions &&
); (initialImageOptions.preset !== undefined ||
initialImageOptions.index !== undefined)
) {
const { index, preset } = initialImageOptions;
const imageIndex = this._getInitialImageIndex( const { numberOfSlices } =
numberOfSlices, csUtils.getImageSliceDataForVolumeViewport(viewport);
index,
preset
);
csToolsUtils.jumpToSlice(viewport.element, { const imageIndex = this._getInitialImageIndex(
imageIndex, numberOfSlices,
}); index,
} preset
);
viewport.render(); csToolsUtils.jumpToSlice(viewport.element, {
}); imageIndex,
});
}
viewport.render();
} }
public updateViewport(viewportIndex, viewportData) { // Todo: keepCamera is an interim solution until we have a better solution for
// keeping the camera position when the viewport data is changed
public updateViewport(viewportIndex, viewportData, keepCamera = false) {
const viewportInfo = this.getViewportInfoByIndex(viewportIndex); const viewportInfo = this.getViewportInfoByIndex(viewportIndex);
const viewportId = viewportInfo.getViewportId(); const viewportId = viewportInfo.getViewportId();
const viewport = this.getCornerstoneViewport(viewportId); const viewport = this.getCornerstoneViewport(viewportId);
const viewportCamera = viewport.getCamera();
if (viewport instanceof VolumeViewport) { if (viewport instanceof VolumeViewport) {
this._setVolumeViewport(viewport, viewportData, viewportInfo); this._setVolumeViewport(viewport, viewportData, viewportInfo).then(() => {
if (keepCamera) {
viewport.setCamera(viewportCamera);
viewport.render();
}
});
return; return;
} }

View File

@ -19,7 +19,7 @@ export type ViewportOptions = {
initialView?: string; initialView?: string;
syncGroups?: SyncGroup[]; syncGroups?: SyncGroup[];
initialImageOptions?: InitialImageOptions; initialImageOptions?: InitialImageOptions;
customViewportOptions?: Record<string, unknown>; customViewportProps?: Record<string, unknown>;
}; };
export type PublicViewportOptions = { export type PublicViewportOptions = {
@ -31,7 +31,7 @@ export type PublicViewportOptions = {
initialView?: string; initialView?: string;
syncGroups?: SyncGroup[]; syncGroups?: SyncGroup[];
initialImageOptions?: InitialImageOptions; initialImageOptions?: InitialImageOptions;
customViewportOptions?: Record<string, unknown>; customViewportProps?: Record<string, unknown>;
}; };
export type PublicDisplaySetOptions = { export type PublicDisplaySetOptions = {

View File

@ -172,8 +172,8 @@ function getDisplayText(mappedAnnotations, displaySet) {
// Area is the same for all series // Area is the same for all series
const { length, width, SeriesNumber, SOPInstanceUID } = mappedAnnotations[0]; const { length, width, SeriesNumber, SOPInstanceUID } = mappedAnnotations[0];
const roundedLength = utils.roundNumber(length, 1); const roundedLength = utils.roundNumber(length, 2);
const roundedWidth = utils.roundNumber(width, 1); const roundedWidth = utils.roundNumber(width, 2);
const instance = displaySet.images.find( const instance = displaySet.images.find(
image => image.SOPInstanceUID === SOPInstanceUID image => image.SOPInstanceUID === SOPInstanceUID

View File

@ -23,7 +23,6 @@ export default function getPTImageIdInstanceMetadata(
dicomMetaData.CorrectedImage === undefined || dicomMetaData.CorrectedImage === undefined ||
dicomMetaData.Units === undefined || dicomMetaData.Units === undefined ||
!dicomMetaData.RadiopharmaceuticalInformationSequence || !dicomMetaData.RadiopharmaceuticalInformationSequence ||
!dicomMetaData.RadiopharmaceuticalInformationSequence.length ||
dicomMetaData.RadiopharmaceuticalInformationSequence[0] dicomMetaData.RadiopharmaceuticalInformationSequence[0]
.RadionuclideHalfLife === undefined || .RadionuclideHalfLife === undefined ||
dicomMetaData.RadiopharmaceuticalInformationSequence[0] dicomMetaData.RadiopharmaceuticalInformationSequence[0]

View File

@ -36,22 +36,22 @@ const handlePETImageMetadata = ({ SeriesInstanceUID, StudyInstanceUID }) => {
return; return;
} }
const imageIds = instances.map(instance => instance.imageId); const imageIds = instances.map(instance => instance.imageId);
const InstanceMetadataArray = []; const instanceMetadataArray = [];
imageIds.forEach(imageId => { imageIds.forEach(imageId => {
const instanceMetadata = getPTImageIdInstanceMetadata(imageId); const instanceMetadata = getPTImageIdInstanceMetadata(imageId);
if (instanceMetadata) { if (instanceMetadata) {
InstanceMetadataArray.push(instanceMetadata); instanceMetadataArray.push(instanceMetadata);
} }
}); });
if (!InstanceMetadataArray.length) { if (!instanceMetadataArray.length) {
return; return;
} }
// try except block to prevent errors when the metadata is not correct // try except block to prevent errors when the metadata is not correct
let suvScalingFactors; let suvScalingFactors;
try { try {
suvScalingFactors = calculateSUVScalingFactors(InstanceMetadataArray); suvScalingFactors = calculateSUVScalingFactors(instanceMetadataArray);
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }
@ -60,7 +60,7 @@ const handlePETImageMetadata = ({ SeriesInstanceUID, StudyInstanceUID }) => {
return; return;
} }
InstanceMetadataArray.forEach((instanceMetadata, index) => { instanceMetadataArray.forEach((instanceMetadata, index) => {
metadataProvider.addCustomMetadata( metadataProvider.addCustomMetadata(
imageIds[index], imageIds[index],
'scalingModule', 'scalingModule',

View File

@ -32,8 +32,8 @@
"peerDependencies": { "peerDependencies": {
"@ohif/core": "^3.0.0", "@ohif/core": "^3.0.0",
"classnames": "^2.2.6", "classnames": "^2.2.6",
"@cornerstonejs/core": "^0.13.11", "@cornerstonejs/core": "^0.14.4",
"@cornerstonejs/tools": "^0.20.15", "@cornerstonejs/tools": "^0.21.7",
"@ohif/extension-cornerstone-dicom-sr": "^3.0.0", "@ohif/extension-cornerstone-dicom-sr": "^3.0.0",
"dcmjs": "^0.24.5", "dcmjs": "^0.24.5",
"prop-types": "^15.6.2", "prop-types": "^15.6.2",

View File

@ -1,7 +1,7 @@
import React, { useEffect, useState, useCallback } from 'react'; import React, { useEffect, useState, useCallback } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Input, Button } from '@ohif/ui'; import { Input, Button } from '@ohif/ui';
import { classes, DicomMetadataStore } from '@ohif/core'; import { DicomMetadataStore } from '@ohif/core';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
const DEFAULT_MEATADATA = { const DEFAULT_MEATADATA = {
@ -24,7 +24,8 @@ const DEFAULT_MEATADATA = {
*/ */
export default function PanelPetSUV({ servicesManager, commandsManager }) { export default function PanelPetSUV({ servicesManager, commandsManager }) {
const { t } = useTranslation('PanelSUV'); const { t } = useTranslation('PanelSUV');
const { DisplaySetService } = servicesManager.services; const { DisplaySetService, ToolGroupService, ToolBarService } =
servicesManager.services;
const [metadata, setMetadata] = useState(DEFAULT_MEATADATA); const [metadata, setMetadata] = useState(DEFAULT_MEATADATA);
const [ptDisplaySet, setPtDisplaySet] = useState(null); const [ptDisplaySet, setPtDisplaySet] = useState(null);
@ -108,6 +109,26 @@ export default function PanelPetSUV({ servicesManager, commandsManager }) {
if (!ptDisplaySet) { if (!ptDisplaySet) {
throw new Error('No ptDisplaySet found'); throw new Error('No ptDisplaySet found');
} }
const toolGroupIds = ToolGroupService.getToolGroupIds();
// Todo: we don't have a proper way to perform a toggle command and update the
// state for the toolbar, so here, we manually toggle the toolbar
// Todo: Crosshairs have bugs for the camera reset currently, so we need to
// force turn it off before we update the metadata
toolGroupIds.forEach(toolGroupId => {
commandsManager.runCommand('toggleCrosshairs', {
toolGroupId,
toggledState: false,
});
});
ToolBarService.state.toggles['Crosshairs'] = false;
ToolBarService._broadcastEvent(
ToolBarService.EVENTS.TOOL_BAR_STATE_MODIFIED
);
// metadata should be dcmjs naturalized // metadata should be dcmjs naturalized
DicomMetadataStore.updateMetadataForSeries( DicomMetadataStore.updateMetadataForSeries(
ptDisplaySet.StudyInstanceUID, ptDisplaySet.StudyInstanceUID,
@ -140,7 +161,7 @@ export default function PanelPetSUV({ servicesManager, commandsManager }) {
}} }}
/> />
<Input <Input
label={t('Patient Weight')} label={t('Patient Weight (kg)')}
labelClassName="text-white" labelClassName="text-white"
className="mt-1 mb-2 bg-black border-primary-main" className="mt-1 mb-2 bg-black border-primary-main"
type="text" type="text"
@ -153,7 +174,7 @@ export default function PanelPetSUV({ servicesManager, commandsManager }) {
}} }}
/> />
<Input <Input
label={t('Total Dose')} label={t('Total Dose (Mbq)')}
labelClassName="text-white" labelClassName="text-white"
className="mt-1 mb-2 bg-black border-primary-main" className="mt-1 mb-2 bg-black border-primary-main"
type="text" type="text"
@ -171,7 +192,7 @@ export default function PanelPetSUV({ servicesManager, commandsManager }) {
}} }}
/> />
<Input <Input
label={t('Half Life')} label={t('Half Life (s)')}
labelClassName="text-white" labelClassName="text-white"
className="mt-1 mb-2 bg-black border-primary-main" className="mt-1 mb-2 bg-black border-primary-main"
type="text" type="text"
@ -189,7 +210,7 @@ export default function PanelPetSUV({ servicesManager, commandsManager }) {
}} }}
/> />
<Input <Input
label={t('Injection Time')} label={t('Injection Time (Seconds)')}
labelClassName="text-white" labelClassName="text-white"
className="mt-1 mb-2 bg-black border-primary-main" className="mt-1 mb-2 bg-black border-primary-main"
type="text" type="text"
@ -207,7 +228,7 @@ export default function PanelPetSUV({ servicesManager, commandsManager }) {
}} }}
/> />
<Input <Input
label={t('Acquisition Time')} label={t('Acquisition Time (Seconds)')}
labelClassName="text-white" labelClassName="text-white"
className="mt-1 mb-2 bg-black border-primary-main" className="mt-1 mb-2 bg-black border-primary-main"
type="text" type="text"

View File

@ -297,7 +297,7 @@ export default function PanelRoiThresholdSegmentation({
name={'info'} name={'info'}
className={'ml-4 mr-3 text-primary-active'} className={'ml-4 mr-3 text-primary-active'}
/> />
<span>{'About'}</span> <span>{'User Guide'}</span>
</div> </div>
</div> </div>
); );

View File

@ -325,6 +325,12 @@ const ptCT = {
source: true, source: true,
target: true, target: true,
}, },
{
type: 'voi',
id: 'ptFusionWLSync',
source: true,
target: false,
},
], ],
}, },
displaySets: [ displaySets: [
@ -360,6 +366,12 @@ const ptCT = {
source: true, source: true,
target: true, target: true,
}, },
{
type: 'voi',
id: 'ptFusionWLSync',
source: true,
target: false,
},
], ],
}, },
displaySets: [ displaySets: [
@ -395,6 +407,12 @@ const ptCT = {
source: true, source: true,
target: true, target: true,
}, },
{
type: 'voi',
id: 'ptFusionWLSync',
source: true,
target: false,
},
], ],
}, },
displaySets: [ displaySets: [
@ -439,6 +457,12 @@ const ptCT = {
source: true, source: true,
target: true, target: true,
}, },
{
type: 'voi',
id: 'ptFusionWLSync',
source: false,
target: true,
},
], ],
}, },
displaySets: [ displaySets: [
@ -486,6 +510,12 @@ const ptCT = {
source: true, source: true,
target: true, target: true,
}, },
{
type: 'voi',
id: 'ptFusionWLSync',
source: false,
target: true,
},
], ],
}, },
displaySets: [ displaySets: [
@ -533,6 +563,12 @@ const ptCT = {
source: true, source: true,
target: true, target: true,
}, },
{
type: 'voi',
id: 'ptFusionWLSync',
source: false,
target: true,
},
], ],
}, },
displaySets: [ displaySets: [
@ -558,9 +594,24 @@ const ptCT = {
orientation: 'sagittal', orientation: 'sagittal',
background: [1, 1, 1], background: [1, 1, 1],
toolGroupId: 'mipToolGroup', toolGroupId: 'mipToolGroup',
syncGroups: [
{
type: 'voi',
id: 'ptWLSync',
source: true,
target: true,
},
{
type: 'voi',
id: 'ptFusionWLSync',
source: true,
target: false,
},
],
// Custom props can be used to set custom properties which extensions // Custom props can be used to set custom properties which extensions
// can react on. // can react on.
customViewportOptions: { customViewportProps: {
// We use viewportDisplay to filter the viewports which are displayed // We use viewportDisplay to filter the viewports which are displayed
// in mip and we set the scrollbar according to their rotation index // in mip and we set the scrollbar according to their rotation index
// in the cornerstone extension. // in the cornerstone extension.

View File

@ -36,14 +36,14 @@ function getPanelModule({
name: 'petSUV', name: 'petSUV',
iconName: 'edit-patient', iconName: 'edit-patient',
iconLabel: 'PET SUV', iconLabel: 'PET SUV',
label: 'PET-SUV', label: 'PET SUV',
component: wrappedPanelPetSuv, component: wrappedPanelPetSuv,
}, },
{ {
name: 'ROIThresholdSeg', name: 'ROIThresholdSeg',
iconName: 'tool-create-threshold', iconName: 'tool-create-threshold',
iconLabel: 'Threshold Seg', iconLabel: 'ROI Threshold',
label: 'Threshold-Seg', label: 'ROI Threshold',
component: wrappedROIThresholdSeg, component: wrappedROIThresholdSeg,
}, },
]; ];

View File

@ -1,7 +1,5 @@
# Total Metabolic Tumor Volume # Total Metabolic Tumor Volume
# NOTE: THIS MODE IS STILL A WORK IN PROGRESS
## Introduction ## Introduction
Total Metabolic Tumor Volume (TMTV) workflow mode enables quantitatively measurement of a tumor volume in a patient's body. Total Metabolic Tumor Volume (TMTV) workflow mode enables quantitatively measurement of a tumor volume in a patient's body.

View File

@ -3,7 +3,7 @@ import toolbarButtons from './toolbarButtons.js';
import { id } from './id.js'; import { id } from './id.js';
import initToolGroups, { toolGroupIds } from './initToolGroups.js'; import initToolGroups, { toolGroupIds } from './initToolGroups.js';
import setCrosshairsConfiguration from './utils/setCrosshairsConfiguration.js'; import setCrosshairsConfiguration from './utils/setCrosshairsConfiguration.js';
import setEllipticalROIConfiguration from './utils/setEllipticalROIConfiguration.js'; import setFusionActiveVolume from './utils/setFusionActiveVolume.js';
const ohif = { const ohif = {
layout: '@ohif/extension-default.layoutTemplateModule.viewerLayout', layout: '@ohif/extension-default.layoutTemplateModule.viewerLayout',
@ -58,7 +58,7 @@ function modeFactory({ modeConfiguration }) {
// Init Default and SR ToolGroups // Init Default and SR ToolGroups
initToolGroups(toolNames, Enums, ToolGroupService, commandsManager); initToolGroups(toolNames, Enums, ToolGroupService, commandsManager);
const activateWindowLevel = () => { const setWindowLevelActive = () => {
ToolBarService.recordInteraction({ ToolBarService.recordInteraction({
groupId: 'WindowLevel', groupId: 'WindowLevel',
itemId: 'WindowLevel', itemId: 'WindowLevel',
@ -97,7 +97,6 @@ function modeFactory({ modeConfiguration }) {
const { unsubscribe } = ToolGroupService.subscribe( const { unsubscribe } = ToolGroupService.subscribe(
ToolGroupService.EVENTS.VIEWPORT_ADDED, ToolGroupService.EVENTS.VIEWPORT_ADDED,
() => { () => {
activateWindowLevel();
// For fusion toolGroup we need to add the volumeIds for the crosshairs // For fusion toolGroup we need to add the volumeIds for the crosshairs
// since in the fusion viewport we don't want both PT and CT to render MIP // since in the fusion viewport we don't want both PT and CT to render MIP
// when slabThickness is modified // when slabThickness is modified
@ -110,12 +109,14 @@ function modeFactory({ modeConfiguration }) {
DisplaySetService DisplaySetService
); );
setEllipticalROIConfiguration( setFusionActiveVolume(
matches, matches,
toolNames, toolNames,
ToolGroupService, ToolGroupService,
DisplaySetService DisplaySetService
); );
setWindowLevelActive();
} }
); );

View File

@ -67,7 +67,7 @@ function _createWwwcPreset(preset, title, subtitle) {
{ {
commandName: 'setWindowLevel', commandName: 'setWindowLevel',
commandOptions: { commandOptions: {
windowLevel: windowLevelPresets[preset], ...windowLevelPresets[preset],
}, },
context: 'CORNERSTONE', context: 'CORNERSTONE',
}, },
@ -272,8 +272,7 @@ const toolbarButtons = [
commandName: 'displayNotification', commandName: 'displayNotification',
commandOptions: { commandOptions: {
title: 'RectangleROI Threshold Tip', title: 'RectangleROI Threshold Tip',
text: text: 'RectangleROI Threshold tool should be used on PT Axial Viewport',
'RectangleROI Threshold tool should be used on PT Axial Viewport',
type: 'info', type: 'info',
}, },
}, },
@ -307,9 +306,10 @@ const toolbarButtons = [
isAction: true, // ? isAction: true, // ?
renderer: WindowLevelMenuItem, renderer: WindowLevelMenuItem,
items: [ items: [
_createColormap('HSV', 'hsv'),
_createColormap('Hot Iron', 'hot_iron'), _createColormap('Hot Iron', 'hot_iron'),
_createColormap('S PET', 's_pet'), _createColormap('S PET', 's_pet'),
_createColormap('Ret Hot', 'red_hot'), _createColormap('Red Hot', 'red_hot'),
_createColormap('Perfusion', 'perfusion'), _createColormap('Perfusion', 'perfusion'),
_createColormap('Rainbow', 'rainbow_2'), _createColormap('Rainbow', 'rainbow_2'),
_createColormap('SUV', 'suv'), _createColormap('SUV', 'suv'),

View File

@ -1,6 +1,6 @@
import { toolGroupIds } from '../initToolGroups'; import { toolGroupIds } from '../initToolGroups';
export default function setEllipticalROIConfiguration( export default function setFusionActiveVolume(
matches, matches,
toolNames, toolNames,
ToolGroupService, ToolGroupService,
@ -22,16 +22,32 @@ export default function setEllipticalROIConfiguration(
return; return;
} }
const toolConfig = ToolGroupService.getToolConfiguration( const wlToolConfig = ToolGroupService.getToolConfiguration(
toolGroupIds.Fusion,
toolNames.WindowLevel
);
const ellipticalToolConfig = ToolGroupService.getToolConfiguration(
toolGroupIds.Fusion, toolGroupIds.Fusion,
toolNames.EllipticalROI toolNames.EllipticalROI
); );
const ellipticalROIConfig = { const windowLevelConfig = {
...toolConfig, ...wlToolConfig,
volumeId: displaySets[0].displaySetInstanceUID, volumeId: displaySets[0].displaySetInstanceUID,
}; };
const ellipticalROIConfig = {
...ellipticalToolConfig,
volumeId: displaySets[0].displaySetInstanceUID,
};
ToolGroupService.setToolConfiguration(
toolGroupIds.Fusion,
toolNames.WindowLevel,
windowLevelConfig
);
ToolGroupService.setToolConfiguration( ToolGroupService.setToolConfiguration(
toolGroupIds.Fusion, toolGroupIds.Fusion,
toolNames.EllipticalROI, toolNames.EllipticalROI,

View File

@ -17,7 +17,8 @@ async function editPackageJson(options) {
description, description,
author, author,
license, license,
files: ['dist', 'README.md'], main: `dist/umd/${name}/index.umd.js`,
files: ['dist/**', 'public/**', 'README.md'],
}, },
packageJson packageJson
); );

View File

@ -17,7 +17,7 @@ function getPathQuestions(packageType) {
{ {
type: 'input', type: 'input',
name: 'baseDir', name: 'baseDir',
message: `What is the target path to create your ${packageType} (we recommend you do not use the OHIF ${packageType} folder (./${packageType}s) unless you are developing a core ${packageType}):`, message: `What is the target absolute path to create your ${packageType} (we recommend you do not use the OHIF ${packageType} folder (./${packageType}s) unless you are developing a core ${packageType}):`,
validate: (input) => { validate: (input) => {
if (!input) { if (!input) {
console.log('Please provide a valid target directory path'); console.log('Please provide a valid target directory path');

View File

@ -3,7 +3,9 @@ const pkg = require('../package.json');
const outputFile = 'index.umd.js'; const outputFile = 'index.umd.js';
const rootDir = path.resolve(__dirname, '../'); const rootDir = path.resolve(__dirname, '../');
const outputFolder = path.join(__dirname, '../dist'); const outputFolder = path.join(__dirname, `../dist/umd/${pkg.name}/`);
// Todo: add ESM build for the extension in addition to umd build
const config = { const config = {
mode: 'production', mode: 'production',
@ -14,6 +16,7 @@ const config = {
filename: outputFile, filename: outputFile,
library: pkg.name, library: pkg.name,
libraryTarget: 'umd', libraryTarget: 'umd',
chunkFilename: '[name].chunk.js',
umdNamedDefine: true, umdNamedDefine: true,
globalObject: "typeof self !== 'undefined' ? self : this", globalObject: "typeof self !== 'undefined' ? self : this",
}, },
@ -46,14 +49,14 @@ const config = {
loader: 'babel-loader', loader: 'babel-loader',
exclude: /(node_modules|bower_components)/, exclude: /(node_modules|bower_components)/,
resolve: { resolve: {
extensions: ['.js', '.jsx', '.ts', '.tsx',], extensions: ['.js', '.jsx', '.ts', '.tsx'],
}, },
}, },
], ],
}, },
resolve: { resolve: {
modules: [path.resolve('./node_modules'), path.resolve('./src')], modules: [path.resolve('./node_modules'), path.resolve('./src')],
extensions: ['.json', '.js', '.jsx', '.tsx', '.ts',], extensions: ['.json', '.js', '.jsx', '.tsx', '.ts'],
}, },
}; };

View File

@ -1,7 +1,6 @@
{ {
"repository": "OHIF/Viewers", "repository": "OHIF/Viewers",
"keywords": ["ohif-extension"], "keywords": ["ohif-extension"],
"main": "dist/index.umd.js",
"module": "src/index.js", "module": "src/index.js",
"engines": { "engines": {
"node": ">=14", "node": ">=14",
@ -51,8 +50,8 @@
"dotenv": "^14.1.0", "dotenv": "^14.1.0",
"eslint": "^5.0.1", "eslint": "^5.0.1",
"eslint-loader": "^2.0.0", "eslint-loader": "^2.0.0",
"uglifyjs-webpack-plugin": "^1.2.7", "webpack": "^5.50.0",
"webpack": "^4.12.2", "webpack-merge": "^5.7.3",
"webpack-cli": "^3.0.8" "webpack-cli": "^4.7.2"
} }
} }

View File

@ -3,8 +3,9 @@ const pkg = require('../package.json');
const outputFile = 'index.umd.js'; const outputFile = 'index.umd.js';
const rootDir = path.resolve(__dirname, '../'); const rootDir = path.resolve(__dirname, '../');
const outputFolder = path.join(__dirname, '../dist'); const outputFolder = path.join(__dirname, `../dist/umd/${pkg.name}/`);
// Todo: add ESM build for the mode in addition to umd build
const config = { const config = {
mode: 'production', mode: 'production',
entry: rootDir + '/' + pkg.module, entry: rootDir + '/' + pkg.module,
@ -14,6 +15,7 @@ const config = {
filename: outputFile, filename: outputFile,
library: pkg.name, library: pkg.name,
libraryTarget: 'umd', libraryTarget: 'umd',
chunkFilename: '[name].chunk.js',
umdNamedDefine: true, umdNamedDefine: true,
globalObject: "typeof self !== 'undefined' ? self : this", globalObject: "typeof self !== 'undefined' ? self : this",
}, },
@ -46,14 +48,14 @@ const config = {
loader: 'babel-loader', loader: 'babel-loader',
exclude: /(node_modules|bower_components)/, exclude: /(node_modules|bower_components)/,
resolve: { resolve: {
extensions: ['.js', '.jsx', '.ts', '.tsx',], extensions: ['.js', '.jsx', '.ts', '.tsx'],
}, },
}, },
], ],
}, },
resolve: { resolve: {
modules: [path.resolve('./node_modules'), path.resolve('./src')], modules: [path.resolve('./node_modules'), path.resolve('./src')],
extensions: ['.json', '.js', '.jsx', '.tsx', '.ts',], extensions: ['.json', '.js', '.jsx', '.tsx', '.ts'],
}, },
}; };

View File

@ -1,7 +1,6 @@
{ {
"repository": "OHIF/Viewers", "repository": "OHIF/Viewers",
"keywords": ["ohif-mode"], "keywords": ["ohif-mode"],
"main": "dist/index.umd.js",
"module": "src/index.js", "module": "src/index.js",
"engines": { "engines": {
"node": ">=14", "node": ">=14",
@ -43,8 +42,8 @@
"dotenv": "^14.1.0", "dotenv": "^14.1.0",
"eslint": "^5.0.1", "eslint": "^5.0.1",
"eslint-loader": "^2.0.0", "eslint-loader": "^2.0.0",
"uglifyjs-webpack-plugin": "^1.2.7", "webpack": "^5.50.0",
"webpack": "^4.12.2", "webpack-merge": "^5.7.3",
"webpack-cli": "^3.0.8" "webpack-cli": "^4.7.2"
} }
} }

View File

@ -31,7 +31,7 @@
}, },
"peerDependencies": { "peerDependencies": {
"cornerstone-math": "0.1.9", "cornerstone-math": "0.1.9",
"cornerstone-wado-image-loader": "^4.2.0", "cornerstone-wado-image-loader": "^4.2.1",
"dicom-parser": "^1.8.9", "dicom-parser": "^1.8.9",
"@ohif/ui": "^2.0.0" "@ohif/ui": "^2.0.0"
}, },

View File

@ -109,7 +109,7 @@ Below, you can find the gap analysis between the `OHIF-v2` and `OHIF-v3`:
</tr> </tr>
<tr> <tr>
<td align="left">Segmentation support</td> <td align="left">Segmentation support</td>
<td align="center">🔜</td> <td align="center"></td>
<td align="center">Not Started</td> <td align="center">Not Started</td>
</tr> </tr>
<tr> <tr>
@ -129,7 +129,7 @@ Below, you can find the gap analysis between the `OHIF-v2` and `OHIF-v3`:
</tr> </tr>
<tr> <tr>
<td align="left">VTK Extension + MIP / MPR layout</td> <td align="left">VTK Extension + MIP / MPR layout</td>
<td align="center">🔜</td> <td align="center"></td>
<td align="center">3D rendering and 3D annotation tools via Cornerstone3D</td> <td align="center">3D rendering and 3D annotation tools via Cornerstone3D</td>
</tr> </tr>
<tr> <tr>

View File

@ -11,6 +11,15 @@ and other resources that we have provided to the community in the past:
## 2022 ## 2022
### OHIF Demo to Interns
[[Slides]](https://docs.google.com/presentation/d/1a2PkUnqkVMaXaBsuFn7-PPlBJULU3dBwzI_44gKFeYI/edit?usp=sharing)
### SIIM 2022 - Updates from the Imaging Informatics Community
We participated in the SIIM 2022 conference to give update for the imaging
informatics community.
[[Slides]](https://docs.google.com/presentation/d/1EUGaUzQtGhZbZWpGLe6ONqChpVMw9Qr9l3KHODevMow/edit?usp=sharing)
[[Video]](https://vimeo.com/734463662/dbd5a88371)
### The Imaging Network Ontario - Remote ### The Imaging Network Ontario - Remote
The Imaging Network Ontario (ImNO) is an annual symposium that brings together The Imaging Network Ontario (ImNO) is an annual symposium that brings together

View File

@ -218,7 +218,6 @@ module.exports = {
// We want users to submit doc updates to the upstream/next version! // We want users to submit doc updates to the upstream/next version!
// Otherwise we risk losing the update on the next release. // Otherwise we risk losing the update on the next release.
const nextVersionDocsDirPath = 'docs';
return `https://github.com/OHIF/Viewers/edit/v3-stable/platform/docs/docs/${docPath}`; return `https://github.com/OHIF/Viewers/edit/v3-stable/platform/docs/docs/${docPath}`;
}, },
showLastUpdateAuthor: true, showLastUpdateAuthor: true,
@ -234,11 +233,10 @@ module.exports = {
// : undefined, // : undefined,
versions: { versions: {
current: { current: {
label: 'Version 3.1 - Cornerstone3D 🚧', label: 'Version 3.2 - Cornerstone3D Volume API 🚧',
}, },
'2.0': { '2.0': {
label: 'Version 2.0 - Master branch', label: 'Version 2.0 - Master branch',
//path: `2.0`,
}, },
}, },
}, },

View File

@ -16,7 +16,7 @@
[build.environment] [build.environment]
# If 'production', `yarn install` does not install devDependencies # If 'production', `yarn install` does not install devDependencies
NODE_ENV = "development" NODE_ENV = "development"
NODE_VERSION = "14.19.1" NODE_VERSION = "16.14.0"
YARN_VERSION = "1.22.5" YARN_VERSION = "1.22.5"
RUBY_VERSION = "2.6.2" RUBY_VERSION = "2.6.2"
YARN_FLAGS = "--no-ignore-optional --pure-lockfile" YARN_FLAGS = "--no-ignore-optional --pure-lockfile"

View File

@ -27,14 +27,14 @@
"write-heading-ids": "docusaurus write-heading-ids" "write-heading-ids": "docusaurus write-heading-ids"
}, },
"dependencies": { "dependencies": {
"@docusaurus/core": "2.0.0-beta.20", "@docusaurus/core": "2.0.1",
"@docusaurus/plugin-client-redirects": "2.0.0-beta.20", "@docusaurus/plugin-client-redirects": "2.0.1",
"@docusaurus/plugin-ideal-image": "2.0.0-beta.20", "@docusaurus/plugin-ideal-image": "2.0.1",
"@docusaurus/plugin-pwa": "2.0.0-beta.20", "@docusaurus/plugin-pwa": "2.0.1",
"@docusaurus/preset-classic": "2.0.0-beta.20", "@docusaurus/preset-classic": "2.0.1",
"@docusaurus/remark-plugin-npm2yarn": "2.0.0-beta.20", "@docusaurus/remark-plugin-npm2yarn": "2.0.1",
"@docusaurus/theme-live-codeblock": "2.0.0-beta.20", "@docusaurus/theme-live-codeblock": "2.0.1",
"@docusaurus/plugin-google-gtag": "2.0.0-beta.20", "@docusaurus/plugin-google-gtag": "2.0.1",
"@mdx-js/react": "^1.6.21", "@mdx-js/react": "^1.6.21",
"@svgr/webpack": "^5.5.0", "@svgr/webpack": "^5.5.0",
"classnames": "^2.3.1", "classnames": "^2.3.1",

View File

@ -1,4 +1,5 @@
{ {
"Version 3.1 - Cornerstone3D Basics": "https://62e92bf05fc5050008aa1300--ohif-platform-docs.netlify.app/",
"Version 3.0 - Cornerstone Legacy": "https://deploy-preview-2791--ohif-platform-docs.netlify.app/", "Version 3.0 - Cornerstone Legacy": "https://deploy-preview-2791--ohif-platform-docs.netlify.app/",
"Version 1.0": "https://deploy-preview-2791--ohif-platform-docs.netlify.app/1.0/" "Version 1.0": "https://deploy-preview-2791--ohif-platform-docs.netlify.app/1.0/"
} }

View File

@ -23,7 +23,9 @@ class ImageScrollbar extends PureComponent {
<div className="scroll"> <div className="scroll">
<div className="scroll-holder"> <div className="scroll-holder">
<input <input
className="imageSlider" // adding mousetrap let the mousetrap know about the scrollbar otherwise,
// it will not capture the keyboard event
className="imageSlider mousetrap"
style={this.style} style={this.style}
type="range" type="range"
min="0" min="0"

View File

@ -1,5 +1,6 @@
const pluginConfig = require('../pluginConfig.json'); const pluginConfig = require('../pluginConfig.json');
const fs = require('fs'); const fs = require('fs');
const glob = require('glob');
const autogenerationDisclaimer = ` const autogenerationDisclaimer = `
// THIS FILE IS AUTOGENERATED AS PART OF THE EXTENSION AND MODE PLUGIN PROCESS. // THIS FILE IS AUTOGENERATED AS PART OF THE EXTENSION AND MODE PLUGIN PROCESS.
@ -44,11 +45,12 @@ function getFormattedImportBlock(importLines) {
} }
function getFormattedWindowBlock(addToWindowLines) { function getFormattedWindowBlock(addToWindowLines) {
let content = "const extensions = [];\n" + let content =
"const modes = [];\n" + 'const extensions = [];\n' +
"const modesFactory = [];\n" + 'const modes = [];\n' +
"window.extensions = extensions;\n" + 'const modesFactory = [];\n' +
"window.modes = modes;\n\n"; 'window.extensions = extensions;\n' +
'window.modes = modes;\n\n';
addToWindowLines.forEach(addToWindowLine => { addToWindowLines.forEach(addToWindowLine => {
content += addToWindowLine; content += addToWindowLine;
@ -58,34 +60,67 @@ function getFormattedWindowBlock(addToWindowLines) {
} }
function getRuntimeLoadModesExtensions() { function getRuntimeLoadModesExtensions() {
return "\n\n// Add a dynamic runtime loader\n" + return (
"export default async () => {\n" + '\n\n// Add a dynamic runtime loader\n' +
" for(const modeFactory of modesFactory) {\n" + 'export default async () => {\n' +
" const newModes = await modeFactory(modes,extensions);\n" + ' for(const modeFactory of modesFactory) {\n' +
" newModes.forEach(newMode => modes.push(newMode));\n" + ' const newModes = await modeFactory(modes,extensions);\n' +
"}\n}\n"; ' newModes.forEach(newMode => modes.push(newMode));\n' +
'}\n}\n'
);
} }
const createCopyPluginFromExtensions = (SRC_DIR, DIST_DIR, plugins) => { const createCopyPluginToDistForLink = (
SRC_DIR,
DIST_DIR,
plugins,
folderName
) => {
return plugins
.map(plugin => {
const from = `${SRC_DIR}/../node_modules/${plugin.packageName}/${folderName}/`;
const exists = fs.existsSync(from);
return exists
? {
from,
to: DIST_DIR,
toType: 'dir',
}
: undefined;
})
.filter(x => !!x);
};
return plugins.map(plugin => { const createCopyPluginToDistForBuild = (
const from = `${SRC_DIR}/../node_modules/${plugin.packageName}/public/`; SRC_DIR,
const exists = fs.existsSync(from); DIST_DIR,
return exists ? { plugins,
from, folderName
to: DIST_DIR, ) => {
toType: 'dir', return plugins
} : undefined; .map(plugin => {
} const from = `${SRC_DIR}/../../../node_modules/${plugin.packageName}/${folderName}/`;
).filter(x => !!x); const exists = fs.existsSync(from);
} return exists
? {
from,
to: DIST_DIR,
toType: 'dir',
}
: undefined;
})
.filter(x => !!x);
};
function writePluginImportsFile(SRC_DIR, DIST_DIR) { function writePluginImportsFile(SRC_DIR, DIST_DIR) {
let pluginImportsJsContent = autogenerationDisclaimer; let pluginImportsJsContent = autogenerationDisclaimer;
const extensionLines = constructLines(pluginConfig.extensions, 'extensions'); const extensionLines = constructLines(pluginConfig.extensions, 'extensions');
const modeLines = constructLines(pluginConfig.modes, 'modes'); const modeLines = constructLines(pluginConfig.modes, 'modes');
const modesFactoryLines = constructLines(pluginConfig.modesFactory, 'modesFactory'); const modesFactoryLines = constructLines(
pluginConfig.modesFactory,
'modesFactory'
);
pluginImportsJsContent += getFormattedImportBlock([ pluginImportsJsContent += getFormattedImportBlock([
...extensionLines.importLines, ...extensionLines.importLines,
@ -112,17 +147,71 @@ function writePluginImportsFile(SRC_DIR, DIST_DIR) {
} }
); );
const copyPluginFromExtensions = createCopyPluginFromExtensions( // Build packages using cli add-mode and add-extension
SRC_DIR, DIST_DIR, // will get added to the root node_modules, but the linked packages
// will be hosted at the viewer node_modules.
const copyPluginPublicToDistBuild = createCopyPluginToDistForBuild(
SRC_DIR,
DIST_DIR,
[ [
...pluginConfig.modesFactory, ...pluginConfig.modesFactory,
...pluginConfig.modes, ...pluginConfig.modes,
...pluginConfig.extensions, ...pluginConfig.extensions,
...pluginConfig.umd, ...pluginConfig.umd,
] ],
) 'public'
);
return copyPluginFromExtensions; const copyPluginPublicToDistLink = createCopyPluginToDistForLink(
SRC_DIR,
DIST_DIR,
[
...pluginConfig.modesFactory,
...pluginConfig.modes,
...pluginConfig.extensions,
...pluginConfig.umd,
],
'public'
);
// Temporary way to copy chunks from the dist folder so that the become
// available
const copyPluginDistToDistBuild = createCopyPluginToDistForBuild(
SRC_DIR,
DIST_DIR,
[
...pluginConfig.modesFactory,
...pluginConfig.modes,
...pluginConfig.extensions,
...pluginConfig.umd,
],
'dist'
);
const copyPluginDistToDistLink = createCopyPluginToDistForLink(
SRC_DIR,
DIST_DIR,
[
...pluginConfig.modesFactory,
...pluginConfig.modes,
...pluginConfig.extensions,
...pluginConfig.umd,
],
'dist'
);
console.log('copy plugins', [...copyPluginPublicToDistBuild,
...copyPluginPublicToDistLink,
...copyPluginDistToDistBuild,
...copyPluginDistToDistLink,
])
return [
...copyPluginPublicToDistBuild,
...copyPluginPublicToDistLink,
...copyPluginDistToDistBuild,
...copyPluginDistToDistLink,
];
} }
module.exports = writePluginImportsFile; module.exports = writePluginImportsFile;

View File

@ -20,7 +20,7 @@
[build.environment] [build.environment]
# If 'production', `yarn install` does not install devDependencies # If 'production', `yarn install` does not install devDependencies
NODE_ENV = "development" NODE_ENV = "development"
NODE_VERSION = "14.19.1" NODE_VERSION = "16.14.0"
YARN_VERSION = "1.22.5" YARN_VERSION = "1.22.5"
RUBY_VERSION = "2.6.2" RUBY_VERSION = "2.6.2"
YARN_FLAGS = "--no-ignore-optional --pure-lockfile" YARN_FLAGS = "--no-ignore-optional --pure-lockfile"

View File

@ -62,7 +62,7 @@
"config-point": "^0.4.8", "config-point": "^0.4.8",
"core-js": "^3.16.1", "core-js": "^3.16.1",
"cornerstone-math": "^0.1.9", "cornerstone-math": "^0.1.9",
"cornerstone-wado-image-loader": "^4.2.0", "cornerstone-wado-image-loader": "^4.2.1",
"dcmjs": "^0.24.5", "dcmjs": "^0.24.5",
"detect-gpu": "^4.0.16", "detect-gpu": "^4.0.16",
"dicom-parser": "^1.8.9", "dicom-parser": "^1.8.9",
@ -90,6 +90,7 @@
"cypress-file-upload": "^3.5.3", "cypress-file-upload": "^3.5.3",
"identity-obj-proxy": "3.0.x", "identity-obj-proxy": "3.0.x",
"lodash": "4.17.15", "lodash": "4.17.15",
"glob": "^8.0.3",
"tailwindcss": "2.2.4", "tailwindcss": "2.2.4",
"terser-webpack-plugin": "^5.1.1", "terser-webpack-plugin": "^5.1.1",
"webpack": "^5.50.0", "webpack": "^5.50.0",

View File

@ -33,10 +33,12 @@
{ {
"packageName": "@ohif/mode-longitudinal", "packageName": "@ohif/mode-longitudinal",
"version": "3.0.0" "version": "3.0.0"
},
{
"packageName": "@ohif/mode-tmtv",
"version": "3.0.0"
} }
], ],
"modesFactory": [ "modesFactory": [],
], "umd": []
"umd": [
]
} }

2294
yarn.lock

File diff suppressed because it is too large Load Diff