diff --git a/extensions/cornerstone/src/commandsModule.js b/extensions/cornerstone/src/commandsModule.js index 9f27164a0..6759b0a6b 100644 --- a/extensions/cornerstone/src/commandsModule.js +++ b/extensions/cornerstone/src/commandsModule.js @@ -186,7 +186,8 @@ const commandsModule = ({ servicesManager, commandsManager }) => { } const viewportInfo = getEnabledElement(i); - const hasCornerstoneContext = viewportInfo.context && + if (!viewportInfo) continue; + const hasCornerstoneContext = viewportInfo.context === 'ACTIVE_VIEWPORT::CORNERSTONE'; if (hasCornerstoneContext) { diff --git a/extensions/default/src/DicomWebDataSource/index.js b/extensions/default/src/DicomWebDataSource/index.js index 128120900..c83fabfdf 100644 --- a/extensions/default/src/DicomWebDataSource/index.js +++ b/extensions/default/src/DicomWebDataSource/index.js @@ -149,6 +149,33 @@ function createDicomWebApi(dicomWebConfig, UserAuthenticationService) { }, }, retrieve: { + /* Generates a URL that can be used for direct retrieve of the bulkdata */ + directURL: (params) => { + const { instance, tag = "PixelData", defaultPath = "/pixeldata", defaultType = "video/mp4" } = params; + const { StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID } = instance; + // If the BulkDataURI isn't present, then assume it uses the pixeldata endpoint + // The standard isn't quite clear on that, but appears to be what is expected + const value = instance[tag]; + const BulkDataURI = + (value && value.BulkDataURI) || + `series/${SeriesInstanceUID}/instances/${SOPInstanceUID}${defaultPath}`; + const hasQuery = BulkDataURI.indexOf('?') != -1; + const hasAccept = BulkDataURI.indexOf('accept=') != -1; + const acceptUri = + BulkDataURI + + (hasAccept ? '' : (hasQuery ? '&' : '?') + `accept=${defaultType}`); + if (BulkDataURI.indexOf('http') == 0) return acceptUri; + if (BulkDataURI.indexOf('/') == 0) { + return wadoRoot + acceptUri; + } + if (BulkDataURI.indexOf('series/') == 0) { + return `${wadoRoot}/studies/${StudyInstanceUID}/${acceptUri}`; + } + if (BulkDataURI.indexOf('instances/') === 0) { + return `${wadoRoot}/studies/${StudyInstanceUID}/series/${SeriesInstanceUID}/${acceptUri}`; + } + throw new Error('BulkDataURI in unknown format:' + BulkDataURI); + }, series: { metadata: async ({ StudyInstanceUID, diff --git a/extensions/dicom-video/.webpack/webpack.dev.js b/extensions/dicom-video/.webpack/webpack.dev.js new file mode 100644 index 000000000..1ae308448 --- /dev/null +++ b/extensions/dicom-video/.webpack/webpack.dev.js @@ -0,0 +1,8 @@ +const path = require('path'); +const webpackCommon = require('./../../../.webpack/webpack.commonjs.js'); +const SRC_DIR = path.join(__dirname, '../src'); +const DIST_DIR = path.join(__dirname, '../dist'); + +module.exports = (env, argv) => { + return webpackCommon(env, argv, { SRC_DIR, DIST_DIR }); +}; diff --git a/extensions/dicom-video/.webpack/webpack.prod.js b/extensions/dicom-video/.webpack/webpack.prod.js new file mode 100644 index 000000000..61802e5cc --- /dev/null +++ b/extensions/dicom-video/.webpack/webpack.prod.js @@ -0,0 +1,44 @@ +const webpack = require('webpack'); +const merge = require('webpack-merge'); +const path = require('path'); +const webpackCommon = require('./../../../.webpack/webpack.commonjs.js'); +const pkg = require('./../package.json'); + +const ROOT_DIR = path.join(__dirname, './..'); +const SRC_DIR = path.join(__dirname, '../src'); +const DIST_DIR = path.join(__dirname, '../dist'); + +module.exports = (env, argv) => { + const commonConfig = webpackCommon(env, argv, { SRC_DIR, DIST_DIR }); + + return merge(commonConfig, { + devtool: 'source-map', + stats: { + colors: true, + hash: true, + timings: true, + assets: true, + chunks: false, + chunkModules: false, + modules: false, + children: false, + warnings: true, + }, + optimization: { + minimize: true, + sideEffects: true, + }, + output: { + path: ROOT_DIR, + library: 'OHIFExtDICOMSR', + libraryTarget: 'umd', + libraryExport: 'default', + filename: pkg.main, + }, + plugins: [ + new webpack.optimize.LimitChunkCountPlugin({ + maxChunks: 1, + }), + ], + }); +}; diff --git a/extensions/dicom-video/LICENSE b/extensions/dicom-video/LICENSE new file mode 100644 index 000000000..19e20dd35 --- /dev/null +++ b/extensions/dicom-video/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Open Health Imaging Foundation + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/extensions/dicom-video/README.md b/extensions/dicom-video/README.md new file mode 100644 index 000000000..bc9d1a98b --- /dev/null +++ b/extensions/dicom-video/README.md @@ -0,0 +1,15 @@ +# DICOM Video +This extension adds support for displaying DICOM video objects in a script tag. +The video data must currently be available as video/mp4 on the BulkDataURI that +is provided in the DICOMweb metadata response, and the video must have one of the +specified SOP Class UID's in order to be recognized by the SOP class handler. + +Those are: +* Video Microscop Image Storage +* Video Photographic Image Storage +* Video Endoscopic Image Storage +* Secondary Capture Image Storage +* Multiframe True Color Secondary Capture Image Storage + +The extension is a "standard" extension in that it is installed and available +by default. diff --git a/extensions/dicom-video/babel.config.js b/extensions/dicom-video/babel.config.js new file mode 100644 index 000000000..325ca2a8e --- /dev/null +++ b/extensions/dicom-video/babel.config.js @@ -0,0 +1 @@ +module.exports = require('../../babel.config.js'); diff --git a/extensions/dicom-video/package.json b/extensions/dicom-video/package.json new file mode 100644 index 000000000..bbd0ce7ff --- /dev/null +++ b/extensions/dicom-video/package.json @@ -0,0 +1,49 @@ +{ + "name": "@ohif/extension-dicom-video", + "version": "0.0.1", + "description": "OHIF extension for video display", + "author": "OHIF", + "license": "MIT", + "repository": "OHIF/Viewers", + "main": "dist/index.umd.js", + "module": "src/index.js", + "engines": { + "node": ">=10", + "npm": ">=6", + "yarn": ">=1.16.0" + }, + "files": [ + "dist", + "README.md" + ], + "publishConfig": { + "access": "public" + }, + "scripts": { + "dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --debug --output-pathinfo", + "dev:cornerstone": "yarn run dev", + "build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js", + "build:package": "yarn run build", + "start": "yarn run dev", + "test:unit": "jest --watchAll", + "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" + }, + "peerDependencies": { + "@ohif/core": "^0.50.0", + "@ohif/ui": "^0.50.0", + "cornerstone-core": "^2.6.0", + "cornerstone-math": "^0.1.9", + "cornerstone-tools": "6.0.2", + "cornerstone-wado-image-loader": "4.0.4", + "dcmjs": "0.16.1", + "dicom-parser": "^1.8.9", + "hammerjs": "^2.0.8", + "prop-types": "^15.6.2", + "react": "^17.0.2", + "react-cornerstone-viewport": "4.1.2" + }, + "dependencies": { + "@babel/runtime": "7.7.6", + "classnames": "^2.2.6" + } +} diff --git a/extensions/dicom-video/src/getSopClassHandlerModule.js b/extensions/dicom-video/src/getSopClassHandlerModule.js new file mode 100644 index 000000000..5e6e8dce1 --- /dev/null +++ b/extensions/dicom-video/src/getSopClassHandlerModule.js @@ -0,0 +1,84 @@ +import { SOPClassHandlerName, SOPClassHandlerId } from './id'; +import { utils, classes } from '@ohif/core'; + +const { ImageSet } = classes; + +const SOP_CLASS_UIDS = { + VIDEO_MICROSCOPIC_IMAGE_STORAGE: '1.2.840.10008.5.1.4.1.1.77.1.2.1', + VIDEO_PHOTOGRAPHIC_IMAGE_STORAGE: '1.2.840.10008.5.1.4.1.1.77.1.4.1', + VIDEO_ENDOSCOPIC_IMAGE_STORAGE: '1.2.840.10008.5.1.4.1.1.77.1.1.1', + /** Need to use fallback, could be video or image */ + SECONDARY_CAPTURE_IMAGE_STORAGE: '1.2.840.10008.5.1.4.1.1.7', + MULTIFRAME_TRUE_COLOR_SECONDARY_CAPTURE_IMAGE_STORAGE: + '1.2.840.10008.5.1.4.1.1.7.4', +}; + +const sopClassUids = Object.values(SOP_CLASS_UIDS); + +const SupportedTransferSyntaxes = { + MPEG4_AVC_264_HIGH_PROFILE: '1.2.840.10008.1.2.4.102', + MPEG4_AVC_264_BD_COMPATIBLE_HIGH_PROFILE: '1.2.840.10008.1.2.4.103', + MPEG4_AVC_264_HIGH_PROFILE_FOR_2D_VIDEO: '1.2.840.10008.1.2.4.104', + MPEG4_AVC_264_HIGH_PROFILE_FOR_3D_VIDEO: '1.2.840.10008.1.2.4.105', + MPEG4_AVC_264_STEREO_HIGH_PROFILE: '1.2.840.10008.1.2.4.106', + HEVC_265_MAIN_PROFILE: '1.2.840.10008.1.2.4.107', + HEVC_265_MAIN_10_PROFILE: '1.2.840.10008.1.2.4.108', +}; + +const supportedTransferSyntaxUIDs = Object.values(SupportedTransferSyntaxes); + +const _getDisplaySetsFromSeries = (instances, servicesManager, extensionManager) => { + const dataSource = extensionManager.getActiveDataSource()[0]; + return instances + .filter(metadata => { + const tsuid = metadata.AvailableTransferSyntaxUID || + metadata.TransferSyntaxUID || metadata['00083002']; + return supportedTransferSyntaxUIDs.includes(tsuid); + }) + .map(instance => { + const { Modality, FrameOfReferenceUID, SOPInstanceUID } = instance; + const { SeriesDescription, ContentDate, ContentTime } = instance; + const { SeriesNumber, SeriesDate, SeriesInstanceUID, StudyInstanceUID, NumberOfFrames } = instance; + const displaySet = { + //plugin: id, + Modality, + displaySetInstanceUID: utils.guid(), + SeriesDescription, + SeriesNumber, + SeriesDate, + SOPInstanceUID, + SeriesInstanceUID, + StudyInstanceUID, + SOPClassHandlerId, + referencedImages: null, + measurements: null, + videoUrl: dataSource.retrieve.directURL({ instance }), + others: [instance], + thumbnailSrc: dataSource.retrieve.directURL({ instance, defaultPath: "/thumbnail", defaultType: "image/jpeg", tag: "Absent" }), + isDerivedDisplaySet: true, + isLoaded: false, + sopClassUids, + numImageFrames: NumberOfFrames, + instance, + }; + return displaySet; + }); +}; + +export default function getSopClassHandlerModule({ servicesManager, extensionManager }) { + const getDisplaySetsFromSeries = instances => { + return _getDisplaySetsFromSeries( + instances, + servicesManager, + extensionManager + ); + }; + + return [ + { + name: SOPClassHandlerName, + sopClassUids, + getDisplaySetsFromSeries, + }, + ]; +} diff --git a/extensions/dicom-video/src/id.js b/extensions/dicom-video/src/id.js new file mode 100644 index 000000000..6aa663316 --- /dev/null +++ b/extensions/dicom-video/src/id.js @@ -0,0 +1,7 @@ +const id = 'org.ohif.dicom-video'; + +export default id; + +const SOPClassHandlerName = 'dicom-video'; +const SOPClassHandlerId = `${id}.sopClassHandlerModule.${SOPClassHandlerName}`; +export { SOPClassHandlerName, SOPClassHandlerId }; diff --git a/extensions/dicom-video/src/index.js b/extensions/dicom-video/src/index.js new file mode 100644 index 000000000..fb563ab40 --- /dev/null +++ b/extensions/dicom-video/src/index.js @@ -0,0 +1,99 @@ +import React from 'react'; +import getSopClassHandlerModule from './getSopClassHandlerModule'; +import id from './id.js'; + +const Component = React.lazy(() => { + return import( + /* webpackPrefetch: true */ './viewports/OHIFCornerstoneVideoViewport' + ); +}); + +const OHIFCornerstoneVideoViewport = props => { + return ( + Loading...}> + + + ); +}; + +/** + * + */ +export default { + /** + * Only required property. Should be a unique value across all extensions. + */ + id, + dependencies: [ + // TODO -> This isn't used anywhere yet, but we do have a hard dependency, and need to check for these in the future. + // OHIF-229 + { + id: 'org.ohif.cornerstone', + version: '3.0.0', + }, + { + id: 'org.ohif.measurement-tracking', + version: '^0.0.1', + }, + ], + + preRegistration({ servicesManager, configuration = {} }) { + // No-op for now + }, + + /** + * + * + * @param {object} [configuration={}] + * @param {object|array} [configuration.csToolsConfig] - Passed directly to `initCornerstoneTools` + */ + getViewportModule({ servicesManager, extensionManager }) { + const ExtendedOHIFCornerstoneVideoViewport = props => { + return ( + + ); + }; + + return [{ name: 'dicom-video', component: ExtendedOHIFCornerstoneVideoViewport }]; + }, + getCommandsModule({ servicesManager }) { + return { + definitions: { + setToolActive: { + commandFn: ({ toolName, element }) => { + if (!toolName) { + console.warn('No toolname provided to setToolActive command'); + } + + // Set same tool or alt tool + const toolAlias = _getToolAlias(toolName); + + cornerstoneTools.setToolActiveForElement(element, toolAlias, { + mouseButtonMask: 1, + }); + }, + storeContexts: [], + options: {}, + }, + }, + defaultContext: 'ACTIVE_VIEWPORT::VIDEO', + }; + }, + getSopClassHandlerModule, +}; + +function _getToolAlias(toolName) { + let toolAlias = toolName; + + switch (toolName) { + case 'EllipticalRoi': + toolAlias = 'SREllipticalRoi'; + break; + } + + return toolAlias; +} diff --git a/extensions/dicom-video/src/tools/modules/dicomVideoModule.js b/extensions/dicom-video/src/tools/modules/dicomVideoModule.js new file mode 100644 index 000000000..3fbb3c360 --- /dev/null +++ b/extensions/dicom-video/src/tools/modules/dicomVideoModule.js @@ -0,0 +1,61 @@ +import cornerstone from 'cornerstone-core'; + +const state = { + TrackingUniqueIdentifier: null, + trackingIdentifiersByEnabledElementUUID: {}, +}; + +function setTrackingUniqueIdentifiersForElement( + element, + trackingUniqueIdentifiers, + activeIndex = 0 +) { + const enabledElement = cornerstone.getEnabledElement(element); + const { uuid } = enabledElement; + + state.trackingIdentifiersByEnabledElementUUID[uuid] = { + trackingUniqueIdentifiers, + activeIndex, + }; +} + +function setActiveTrackingUniqueIdentifierForElement( + element, + TrackingUniqueIdentifier +) { + const enabledElement = cornerstone.getEnabledElement(element); + const { uuid } = enabledElement; + + const trackingIdentifiersForElement = + state.trackingIdentifiersByEnabledElementUUID[uuid]; + + if (trackingIdentifiersForElement) { + const activeIndex = trackingIdentifiersForElement.trackingUniqueIdentifiers.findIndex( + tuid => tuid === TrackingUniqueIdentifier + ); + + trackingIdentifiersForElement.activeIndex = activeIndex; + } +} + +function getTrackingUniqueIdentifiersForElement(element) { + const enabledElement = cornerstone.getEnabledElement(element); + const { uuid } = enabledElement; + + if (state.trackingIdentifiersByEnabledElementUUID[uuid]) { + return state.trackingIdentifiersByEnabledElementUUID[uuid]; + } + + return { trackingUniqueIdentifiers: [] }; +} + +export default { + state, + getters: { + trackingUniqueIdentifiersForElement: getTrackingUniqueIdentifiersForElement, + }, + setters: { + trackingUniqueIdentifiersForElement: setTrackingUniqueIdentifiersForElement, + activeTrackingUniqueIdentifierForElement: setActiveTrackingUniqueIdentifierForElement, + }, +}; diff --git a/extensions/dicom-video/src/viewports/OHIFCornerstoneVideoViewport.js b/extensions/dicom-video/src/viewports/OHIFCornerstoneVideoViewport.js new file mode 100644 index 000000000..6943ef645 --- /dev/null +++ b/extensions/dicom-video/src/viewports/OHIFCornerstoneVideoViewport.js @@ -0,0 +1,51 @@ +import React, { useCallback, useContext, useEffect, useState } from 'react'; +import PropTypes from 'prop-types'; +import cornerstoneTools from 'cornerstone-tools'; +import cornerstone from 'cornerstone-core'; +import CornerstoneViewport from 'react-cornerstone-viewport'; +import OHIF, { DicomMetadataStore, utils } from '@ohif/core'; +import { + Notification, + ViewportActionBar, + useViewportGrid, + useViewportDialog, +} from '@ohif/ui'; +import { adapters } from 'dcmjs'; +import id from '../id'; + + +function OHIFCornerstoneVideoViewport({ + children, + dataSource, + displaySet, + viewportIndex, + servicesManager, + extensionManager, +}) { + const { + DisplaySetService, + MeasurementService, + ToolBarService, + } = servicesManager.services; + const { videoUrl } = displaySet; + const mimeType = "video/mp4"; + + // Need to copies of the source to fix a firefox bug + return ( +
+ +
+ ) +} + +export default OHIFCornerstoneVideoViewport; diff --git a/extensions/dicom-video/video-screenshot.jpg b/extensions/dicom-video/video-screenshot.jpg new file mode 100644 index 000000000..2a05ca7ed Binary files /dev/null and b/extensions/dicom-video/video-screenshot.jpg differ diff --git a/modes/longitudinal/src/index.js b/modes/longitudinal/src/index.js index 5bdc90a48..37a17e831 100644 --- a/modes/longitudinal/src/index.js +++ b/modes/longitudinal/src/index.js @@ -18,6 +18,11 @@ const dicomsr = { viewport: 'org.ohif.dicom-sr.viewportModule.dicom-sr', }; +const dicomvideo = { + sopClassHandler: 'org.ohif.dicom-video.sopClassHandlerModule.dicom-video', + viewport: 'org.ohif.dicom-video.viewportModule.dicom-video', +} + export default function mode({ modeConfiguration }) { return { // TODO: We're using this as a route segment @@ -87,6 +92,10 @@ export default function mode({ modeConfiguration }) { namespace: dicomsr.viewport, displaySetsToDisplay: [dicomsr.sopClassHandler], }, + { + namespace: dicomvideo.viewport, + displaySetsToDisplay: [dicomvideo.sopClassHandler], + }, ], }, }; @@ -98,9 +107,10 @@ export default function mode({ modeConfiguration }) { 'org.ohif.cornerstone', 'org.ohif.measurement-tracking', 'org.ohif.dicom-sr', + 'org.ohif.dicom-video', ], hangingProtocols: [ohif.hangingProtocols], - sopClassHandlers: [ohif.sopClassHandler, dicomsr.sopClassHandler], + sopClassHandlers: [dicomvideo.sopClassHandler, ohif.sopClassHandler, dicomsr.sopClassHandler,], hotkeys: [...hotkeys.defaults.hotkeyBindings], }; } diff --git a/platform/core/src/services/DisplaySetService/DisplaySetService.js b/platform/core/src/services/DisplaySetService/DisplaySetService.js index 9481559ad..f6610c862 100644 --- a/platform/core/src/services/DisplaySetService/DisplaySetService.js +++ b/platform/core/src/services/DisplaySetService/DisplaySetService.js @@ -3,6 +3,31 @@ import EVENTS from './EVENTS'; const displaySetCache = []; +/** + * Find an instance in a list of instances, comparing by SOP instance UID + */ +const findInSet = (instance, list) => { + if (!list) return false; + for (const elem of list) { + if (!elem) continue; + if (elem === instance) return true; + if (elem.SOPInstanceUID === instance.SOPInstanceUID) return true; + } + return false; +}; + +/** + * Find an instance in a display set + * @returns true if found + */ +const findInstance = (instance, displaySets) => { + for (const displayset of displaySets) { + if (findInSet(instance, displayset.images)) return true; + if (findInSet(instance, displayset.others)) return true; + } + return false; +} + export default class DisplaySetService { constructor() { this.activeDisplaySets = []; @@ -151,13 +176,15 @@ export default class DisplaySetService { } }; - makeDisplaySetForInstances(instances, settings) { + makeDisplaySetForInstances(instancesSrc, settings) { + let instances = instancesSrc; const instance = instances[0]; const existingDisplaySets = this.getDisplaySetsForSeries(instance.SeriesInstanceUID) || []; const SOPClassHandlerIds = this.SOPClassHandlerIds; + let allDisplaySets; for (let i = 0; i < SOPClassHandlerIds.length; i++) { const SOPClassHandlerId = SOPClassHandlerIds[i]; @@ -174,6 +201,8 @@ export default class DisplaySetService { } else { displaySets = handler.getDisplaySetsFromSeries(instances); + if (!displaySets || !displaySets.length) continue; + // applying hp-defined viewport settings to the displaysets displaySets.forEach(ds => { Object.keys(settings).forEach(key => { @@ -183,10 +212,15 @@ export default class DisplaySetService { this._addDisplaySetsToCache(displaySets); this._addActiveDisplaySets(displaySets); + + instances = instances.filter(instance => !findInstance(instance, displaySets)) } - return displaySets; + allDisplaySets = allDisplaySets ? [...allDisplaySets, ...displaySets] : displaySets; + + if (!instances.length) return allDisplaySets; } } + return allDisplaySets; } } diff --git a/platform/viewer/cypress/integration/OHIFVideoDisplay.spec.js b/platform/viewer/cypress/integration/OHIFVideoDisplay.spec.js new file mode 100644 index 000000000..5436956fc --- /dev/null +++ b/platform/viewer/cypress/integration/OHIFVideoDisplay.spec.js @@ -0,0 +1,24 @@ +describe('OHIF Video Display', function () { + before(() => { + cy.openStudyInViewer( + '2.25.96975534054447904995905761963464388233' + ); + }); + + beforeEach(function () { + cy.resetViewport().wait(50); + }); + + it('checks if series thumbnails are being displayed', function () { + cy.get('[data-cy="study-browser-thumbnail"]') + .its('length') + .should('be.gt', 1); + }); + + it('performs double-click to load thumbnail in active viewport', () => { + cy.get('[data-cy="study-browser-thumbnail"]:nth-child(2)').dblclick(); + + //const expectedText = 'Ser: 3'; + //cy.get('@viewportInfoBottomLeft').should('contains.text', expectedText); + }); +}); diff --git a/platform/viewer/src/index.js b/platform/viewer/src/index.js index 81ab612fc..33f2d7d13 100644 --- a/platform/viewer/src/index.js +++ b/platform/viewer/src/index.js @@ -23,6 +23,7 @@ import OHIFDefaultExtension from '@ohif/extension-default'; import OHIFCornerstoneExtension from '@ohif/extension-cornerstone'; import OHIFMeasurementTrackingExtension from '@ohif/extension-measurement-tracking'; import OHIFDICOMSRExtension from '@ohif/extension-dicom-sr'; +import OHIFDICOMVIDEOExtension from '@ohif/extension-dicom-video'; /** Combine our appConfiguration and "baked-in" extensions */ const appProps = { @@ -32,6 +33,7 @@ const appProps = { OHIFCornerstoneExtension, OHIFMeasurementTrackingExtension, OHIFDICOMSRExtension, + OHIFDICOMVIDEOExtension, ], }; diff --git a/testdata b/testdata index 47d51e15a..e8e18cb65 160000 --- a/testdata +++ b/testdata @@ -1 +1 @@ -Subproject commit 47d51e15a0d41c02f806570d8cf78ab39e782387 +Subproject commit e8e18cb65ff77e37273a8553f0f651ba4fd1a731