feat(PDF):Add PDF viewer (#2730)
Display DICOM encapsulated PDF documents in a PDF viewer. Addressed the remaining PR comments, and am merging this.
This commit is contained in:
parent
f3822f98a3
commit
82df3eac03
@ -25,7 +25,6 @@ import StaticWadoClient from './utils/StaticWadoClient.js';
|
||||
const { DicomMetaDictionary, DicomDict } = dcmjs.data;
|
||||
|
||||
const { naturalizeDataset, denaturalizeDataset } = DicomMetaDictionary;
|
||||
const { urlUtil } = utils;
|
||||
|
||||
const ImplementationClassUID =
|
||||
'2.25.270695996825855179949881587723571202391.2.0.0';
|
||||
@ -43,6 +42,7 @@ const EXPLICIT_VR_LITTLE_ENDIAN = '1.2.840.10008.1.2.1';
|
||||
* @param {string} thumbnailRendering - wadors | ? (unsure of where/how this is used)
|
||||
* @param {bool} supportsReject - Whether the server supports reject calls (i.e. DCM4CHEE)
|
||||
* @param {bool} lazyLoadStudy - "enableStudyLazyLoad"; Request series meta async instead of blocking
|
||||
* @param {string|bool} singlepart - indicates of the retrieves can fetch singlepart. Options are bulkdata, video, image or boolean true
|
||||
*/
|
||||
function createDicomWebApi(dicomWebConfig, UserAuthenticationService) {
|
||||
const {
|
||||
@ -53,17 +53,20 @@ function createDicomWebApi(dicomWebConfig, UserAuthenticationService) {
|
||||
supportsWildcard,
|
||||
supportsReject,
|
||||
staticWado,
|
||||
singlepart,
|
||||
} = dicomWebConfig;
|
||||
|
||||
const qidoConfig = {
|
||||
url: qidoRoot,
|
||||
staticWado,
|
||||
singlepart,
|
||||
headers: UserAuthenticationService.getAuthorizationHeader(),
|
||||
errorInterceptor: errorHandler.getHTTPErrorHandler(),
|
||||
};
|
||||
|
||||
const wadoConfig = {
|
||||
url: wadoRoot,
|
||||
singlepart,
|
||||
headers: UserAuthenticationService.getAuthorizationHeader(),
|
||||
errorInterceptor: errorHandler.getHTTPErrorHandler(),
|
||||
};
|
||||
@ -91,7 +94,7 @@ function createDicomWebApi(dicomWebConfig, UserAuthenticationService) {
|
||||
query: {
|
||||
studies: {
|
||||
mapParams: mapParams.bind(),
|
||||
search: async function(origParams) {
|
||||
search: async function (origParams) {
|
||||
const headers = UserAuthenticationService.getAuthorizationHeader();
|
||||
if (headers) {
|
||||
qidoDicomWebClient.headers = headers;
|
||||
@ -116,7 +119,7 @@ function createDicomWebApi(dicomWebConfig, UserAuthenticationService) {
|
||||
},
|
||||
series: {
|
||||
// mapParams: mapParams.bind(),
|
||||
search: async function(studyInstanceUid) {
|
||||
search: async function (studyInstanceUid) {
|
||||
const headers = UserAuthenticationService.getAuthorizationHeader();
|
||||
if (headers) {
|
||||
qidoDicomWebClient.headers = headers;
|
||||
@ -149,13 +152,46 @@ function createDicomWebApi(dicomWebConfig, UserAuthenticationService) {
|
||||
},
|
||||
},
|
||||
retrieve: {
|
||||
/* Generates a URL that can be used for direct retrieve of the bulkdata */
|
||||
/**
|
||||
* Generates a URL that can be used for direct retrieve of the bulkdata
|
||||
*
|
||||
* @param {object} params
|
||||
* @param {string} params.tag is the tag name of the URL to retrieve
|
||||
* @param {object} params.instance is the instance object that the tag is in
|
||||
* @param {string} params.defaultType is the mime type of the response
|
||||
* @param {string} params.singlepart is the type of the part to retrieve
|
||||
* @returns an absolute URL to the resource, if the absolute URL can be retrieved as singlepart,
|
||||
* or is already retrieved, or a promise to a URL for such use if a BulkDataURI
|
||||
*/
|
||||
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 {
|
||||
instance,
|
||||
tag = "PixelData",
|
||||
defaultPath = "/pixeldata",
|
||||
defaultType = "video/mp4",
|
||||
singlepart: fetchPart = "video",
|
||||
} = params;
|
||||
const value = instance[tag];
|
||||
if (!value) return undefined;
|
||||
|
||||
if (value.DirectRetrieveURL) return value.DirectRetrieveURL;
|
||||
if (value.InlineBinary) {
|
||||
const blob = utils.b64toBlob(value.InlineBinary, defaultType);
|
||||
value.DirectRetrieveURL = URL.createObjectURL(blob);
|
||||
return value.DirectRetrieveURL;
|
||||
}
|
||||
if (!singlepart || singlepart !== true && singlepart.indexOf(fetchPart) === -1) {
|
||||
if (value.retrieveBulkData) {
|
||||
return value.retrieveBulkData().then(arr => {
|
||||
value.DirectRetrieveURL = URL.createObjectURL(new Blob([arr], { type: defaultType }));
|
||||
return value.DirectRetrieveURL;
|
||||
});
|
||||
}
|
||||
console.warn('Unable to retrieve', tag, 'from', instance);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const { StudyInstanceUID, SeriesInstanceUID, SOPInstanceUID } = instance;
|
||||
const BulkDataURI =
|
||||
(value && value.BulkDataURI) ||
|
||||
`series/${SeriesInstanceUID}/instances/${SOPInstanceUID}${defaultPath}`;
|
||||
@ -164,8 +200,8 @@ function createDicomWebApi(dicomWebConfig, UserAuthenticationService) {
|
||||
const acceptUri =
|
||||
BulkDataURI +
|
||||
(hasAccept ? '' : (hasQuery ? '&' : '?') + `accept=${defaultType}`);
|
||||
if (BulkDataURI.indexOf('http') == 0) return acceptUri;
|
||||
if (BulkDataURI.indexOf('/') == 0) {
|
||||
if (BulkDataURI.indexOf('http') === 0) return acceptUri;
|
||||
if (BulkDataURI.indexOf('/') === 0) {
|
||||
return wadoRoot + acceptUri;
|
||||
}
|
||||
if (BulkDataURI.indexOf('series/') == 0) {
|
||||
@ -174,6 +210,9 @@ function createDicomWebApi(dicomWebConfig, UserAuthenticationService) {
|
||||
if (BulkDataURI.indexOf('instances/') === 0) {
|
||||
return `${wadoRoot}/studies/${StudyInstanceUID}/series/${SeriesInstanceUID}/${acceptUri}`;
|
||||
}
|
||||
if (BulkDataURI.indexOf('bulkdata/') === 0) {
|
||||
return `${wadoRoot}/studies/${StudyInstanceUID}/${acceptUri}`;
|
||||
}
|
||||
throw new Error('BulkDataURI in unknown format:' + BulkDataURI);
|
||||
},
|
||||
series: {
|
||||
|
||||
@ -70,7 +70,7 @@ export default class StaticWadoClient extends api.DICOMwebClient {
|
||||
if (actual.length === 0) return true;
|
||||
if (desired.length === 0 || desired === '*') return true;
|
||||
if (desired[0] === '*' && desired[desired.length - 1] === '*') {
|
||||
console.log(`Comparing ${actual} to ${desired.substring(1, desired.length - 1)}`)
|
||||
// console.log(`Comparing ${actual} to ${desired.substring(1, desired.length - 1)}`)
|
||||
return actual.indexOf(desired.substring(1, desired.length - 1)) != -1;
|
||||
} else if (desired[desired.length - 1] === '*') {
|
||||
return actual.indexOf(desired.substring(0, desired.length - 1)) != -1;
|
||||
|
||||
21
extensions/dicom-pdf/LICENSE
Normal file
21
extensions/dicom-pdf/LICENSE
Normal file
@ -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.
|
||||
5
extensions/dicom-pdf/README.md
Normal file
5
extensions/dicom-pdf/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# DICOM Encapsulated PDF
|
||||
This extension adds support for displaying DICOM encapsulated PDF documents.
|
||||
|
||||
The extension is a "standard" extension in that it is installed and available
|
||||
by default.
|
||||
49
extensions/dicom-pdf/package.json
Normal file
49
extensions/dicom-pdf/package.json
Normal file
@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "@ohif/extension-dicom-pdf",
|
||||
"version": "3.0.1",
|
||||
"description": "OHIF extension for PDF 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"
|
||||
}
|
||||
}
|
||||
70
extensions/dicom-pdf/src/getSopClassHandlerModule.js
Normal file
70
extensions/dicom-pdf/src/getSopClassHandlerModule.js
Normal file
@ -0,0 +1,70 @@
|
||||
import { Name, SOPClassHandlerId } from './id';
|
||||
import { utils, classes } from '@ohif/core';
|
||||
|
||||
const { ImageSet } = classes;
|
||||
|
||||
const SOP_CLASS_UIDS = {
|
||||
ENCAPSULATED_PDF: '1.2.840.10008.5.1.4.1.1.104.1',
|
||||
};
|
||||
|
||||
const sopClassUids = Object.values(SOP_CLASS_UIDS);
|
||||
|
||||
|
||||
const _getDisplaySetsFromSeries = (instances, servicesManager, extensionManager) => {
|
||||
const dataSource = extensionManager.getActiveDataSource()[0];
|
||||
return instances
|
||||
.map(instance => {
|
||||
const { Modality, SOPInstanceUID, EncapsulatedDocument } = instance;
|
||||
const { SeriesDescription = "PDF", MIMETypeOfEncapsulatedDocument, } = instance;
|
||||
const { SeriesNumber, SeriesDate, SeriesInstanceUID, StudyInstanceUID, } = instance;
|
||||
const pdfUrl = dataSource.retrieve.directURL({
|
||||
instance,
|
||||
tag: 'EncapsulatedDocument',
|
||||
defaultType: MIMETypeOfEncapsulatedDocument || "application/pdf",
|
||||
singlepart: "pdf",
|
||||
});
|
||||
|
||||
const displaySet = {
|
||||
//plugin: id,
|
||||
Modality,
|
||||
displaySetInstanceUID: utils.guid(),
|
||||
SeriesDescription,
|
||||
SeriesNumber,
|
||||
SeriesDate,
|
||||
SOPInstanceUID,
|
||||
SeriesInstanceUID,
|
||||
StudyInstanceUID,
|
||||
SOPClassHandlerId,
|
||||
referencedImages: null,
|
||||
measurements: null,
|
||||
pdfUrl,
|
||||
others: [instance],
|
||||
thumbnailSrc: dataSource.retrieve.directURL({ instance, defaultPath: "/thumbnail", defaultType: "image/jpeg", tag: "Absent" }),
|
||||
isDerivedDisplaySet: true,
|
||||
isLoaded: false,
|
||||
sopClassUids,
|
||||
numImageFrames: 0,
|
||||
numInstances: 1,
|
||||
instance,
|
||||
};
|
||||
return displaySet;
|
||||
});
|
||||
};
|
||||
|
||||
export default function getSopClassHandlerModule({ servicesManager, extensionManager }) {
|
||||
const getDisplaySetsFromSeries = instances => {
|
||||
return _getDisplaySetsFromSeries(
|
||||
instances,
|
||||
servicesManager,
|
||||
extensionManager
|
||||
);
|
||||
};
|
||||
|
||||
return [
|
||||
{
|
||||
name: Name,
|
||||
sopClassUids,
|
||||
getDisplaySetsFromSeries,
|
||||
},
|
||||
];
|
||||
}
|
||||
8
extensions/dicom-pdf/src/id.js
Normal file
8
extensions/dicom-pdf/src/id.js
Normal file
@ -0,0 +1,8 @@
|
||||
const Name = 'dicom-pdf';
|
||||
const id = `org.ohif.${Name}`;
|
||||
|
||||
export default id;
|
||||
|
||||
const SOPClassHandlerId = `${id}.sopClassHandlerModule.${Name}`;
|
||||
|
||||
export { Name, SOPClassHandlerId, };
|
||||
76
extensions/dicom-pdf/src/index.js
Normal file
76
extensions/dicom-pdf/src/index.js
Normal file
@ -0,0 +1,76 @@
|
||||
import React from 'react';
|
||||
import getSopClassHandlerModule from './getSopClassHandlerModule';
|
||||
import id from './id.js';
|
||||
|
||||
const Component = React.lazy(() => {
|
||||
return import(
|
||||
/* webpackPrefetch: true */ './viewports/OHIFCornerstonePdfViewport'
|
||||
);
|
||||
});
|
||||
|
||||
const OHIFCornerstonePdfViewport = props => {
|
||||
return (
|
||||
<React.Suspense fallback={<div>Loading...</div>}>
|
||||
<Component {...props} />
|
||||
</React.Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
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 ExtendedOHIFCornerstonePdfViewport = props => {
|
||||
return (
|
||||
<OHIFCornerstonePdfViewport
|
||||
servicesManager={servicesManager}
|
||||
extensionManager={extensionManager}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return [{ name: 'dicom-pdf', component: ExtendedOHIFCornerstonePdfViewport }];
|
||||
},
|
||||
getCommandsModule({ servicesManager }) {
|
||||
return {
|
||||
definitions: {
|
||||
setToolActive: {
|
||||
commandFn: () => null,
|
||||
storeContexts: [],
|
||||
options: {},
|
||||
},
|
||||
},
|
||||
defaultContext: 'ACTIVE_VIEWPORT::PDF',
|
||||
};
|
||||
},
|
||||
getSopClassHandlerModule,
|
||||
};
|
||||
@ -0,0 +1,26 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
function OHIFCornerstonePdfViewport({
|
||||
displaySet,
|
||||
}) {
|
||||
const [url, setUrl] = useState(null);
|
||||
const { pdfUrl } = displaySet;
|
||||
useEffect(async () => {
|
||||
setUrl(await pdfUrl);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="bg-primary-black w-full h-full">
|
||||
<object data={url} type="application/pdf" className="w-full h-full">
|
||||
<div>No online PDF viewer installed</div>
|
||||
</object>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
OHIFCornerstonePdfViewport.propTypes = {
|
||||
displaySet: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default OHIFCornerstonePdfViewport;
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-dicom-video",
|
||||
"version": "0.0.1",
|
||||
"version": "3.0.1",
|
||||
"description": "OHIF extension for video display",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
import { SOPClassHandlerName, SOPClassHandlerId } from './id';
|
||||
import { utils, classes } from '@ohif/core';
|
||||
|
||||
const { ImageSet } = classes;
|
||||
import { Name, SOPClassHandlerId } from './id';
|
||||
import { utils, } from '@ohif/core';
|
||||
|
||||
const SOP_CLASS_UIDS = {
|
||||
VIDEO_MICROSCOPIC_IMAGE_STORAGE: '1.2.840.10008.5.1.4.1.1.77.1.2.1',
|
||||
@ -36,8 +34,7 @@ const _getDisplaySetsFromSeries = (instances, servicesManager, extensionManager)
|
||||
return supportedTransferSyntaxUIDs.includes(tsuid);
|
||||
})
|
||||
.map(instance => {
|
||||
const { Modality, FrameOfReferenceUID, SOPInstanceUID } = instance;
|
||||
const { SeriesDescription, ContentDate, ContentTime } = instance;
|
||||
const { Modality, SOPInstanceUID, SeriesDescription = "VIDEO" } = instance;
|
||||
const { SeriesNumber, SeriesDate, SeriesInstanceUID, StudyInstanceUID, NumberOfFrames } = instance;
|
||||
const displaySet = {
|
||||
//plugin: id,
|
||||
@ -52,7 +49,7 @@ const _getDisplaySetsFromSeries = (instances, servicesManager, extensionManager)
|
||||
SOPClassHandlerId,
|
||||
referencedImages: null,
|
||||
measurements: null,
|
||||
videoUrl: dataSource.retrieve.directURL({ instance }),
|
||||
videoUrl: dataSource.retrieve.directURL({ instance, singlepart: "video", tag: "PixelData", }),
|
||||
others: [instance],
|
||||
thumbnailSrc: dataSource.retrieve.directURL({ instance, defaultPath: "/thumbnail", defaultType: "image/jpeg", tag: "Absent" }),
|
||||
isDerivedDisplaySet: true,
|
||||
@ -76,7 +73,7 @@ export default function getSopClassHandlerModule({ servicesManager, extensionMan
|
||||
|
||||
return [
|
||||
{
|
||||
name: SOPClassHandlerName,
|
||||
name: Name,
|
||||
sopClassUids,
|
||||
getDisplaySetsFromSeries,
|
||||
},
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
const id = 'org.ohif.dicom-video';
|
||||
const Name = 'dicom-video';
|
||||
const id = `org.ohif.${Name}`;
|
||||
|
||||
export default id;
|
||||
|
||||
const SOPClassHandlerName = 'dicom-video';
|
||||
const SOPClassHandlerId = `${id}.sopClassHandlerModule.${SOPClassHandlerName}`;
|
||||
export { SOPClassHandlerName, SOPClassHandlerId };
|
||||
const SOPClassHandlerId = `${id}.sopClassHandlerModule.${Name}`;
|
||||
|
||||
export { Name, SOPClassHandlerId, };
|
||||
|
||||
@ -1,51 +1,36 @@
|
||||
import React, { useCallback, useContext, useEffect, useState } from 'react';
|
||||
import React, { 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";
|
||||
const [url, setUrl] = useState(null);
|
||||
useEffect(async () => {
|
||||
setUrl(await videoUrl);
|
||||
});
|
||||
|
||||
// Need to copies of the source to fix a firefox bug
|
||||
return (
|
||||
<div className="bg-primary-black w-full h-full">
|
||||
<video
|
||||
src={videoUrl}
|
||||
src={url}
|
||||
controls
|
||||
controlsList="nodownload"
|
||||
preload="auto"
|
||||
className="w-full h-full"
|
||||
>
|
||||
<source src={videoUrl} type={mimeType} />
|
||||
<source src={videoUrl} type={mimeType} />
|
||||
Video src/type not supported: <a href={videoUrl}>{videoUrl} of type {mimeType}</a>
|
||||
<source src={url} type={mimeType} />
|
||||
<source src={url} type={mimeType} />
|
||||
Video src/type not supported: <a href={url}>{url} of type {mimeType}</a>
|
||||
</video>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
OHIFCornerstoneVideoViewport.propTypes = {
|
||||
displaySet: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default OHIFCornerstoneVideoViewport;
|
||||
|
||||
@ -23,6 +23,11 @@ const dicomvideo = {
|
||||
viewport: 'org.ohif.dicom-video.viewportModule.dicom-video',
|
||||
}
|
||||
|
||||
const dicompdf = {
|
||||
sopClassHandler: 'org.ohif.dicom-pdf.sopClassHandlerModule.dicom-pdf',
|
||||
viewport: 'org.ohif.dicom-pdf.viewportModule.dicom-pdf',
|
||||
}
|
||||
|
||||
export default function mode({ modeConfiguration }) {
|
||||
return {
|
||||
// TODO: We're using this as a route segment
|
||||
@ -96,6 +101,10 @@ export default function mode({ modeConfiguration }) {
|
||||
namespace: dicomvideo.viewport,
|
||||
displaySetsToDisplay: [dicomvideo.sopClassHandler],
|
||||
},
|
||||
{
|
||||
namespace: dicompdf.viewport,
|
||||
displaySetsToDisplay: [dicompdf.sopClassHandler],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
@ -108,9 +117,18 @@ export default function mode({ modeConfiguration }) {
|
||||
'org.ohif.measurement-tracking',
|
||||
'org.ohif.dicom-sr',
|
||||
'org.ohif.dicom-video',
|
||||
'org.ohif.dicom-pdf',
|
||||
],
|
||||
hangingProtocols: [ohif.hangingProtocols],
|
||||
sopClassHandlers: [dicomvideo.sopClassHandler, ohif.sopClassHandler, dicomsr.sopClassHandler,],
|
||||
// Order is important in sop class handlers when two handlers both use
|
||||
// the same sop class under different situations. In that case, the more
|
||||
// general handler needs to come last. For this case, the dicomvideo msut
|
||||
// come first to remove video transfer syntax before ohif uses images
|
||||
sopClassHandlers: [
|
||||
dicomvideo.sopClassHandler,
|
||||
ohif.sopClassHandler,
|
||||
dicompdf.sopClassHandler,
|
||||
dicomsr.sopClassHandler,],
|
||||
hotkeys: [...hotkeys.defaults.hotkeyBindings],
|
||||
};
|
||||
}
|
||||
|
||||
@ -52,7 +52,6 @@
|
||||
"link-list": "npm ls --depth=0 --link=true"
|
||||
},
|
||||
"dependencies": {
|
||||
"config-point": "^0.3.4",
|
||||
"@babel/runtime": "7.16.3",
|
||||
"core-js": "^3.2.1",
|
||||
"cornerstone-core": "2.6.0",
|
||||
|
||||
@ -24,12 +24,16 @@ const ENTRY_TARGET = process.env.ENTRY_TARGET || `${SRC_DIR}/index.js`;
|
||||
const Dotenv = require('dotenv-webpack');
|
||||
|
||||
const setHeaders = (res, path) => {
|
||||
res.setHeader('Content-Type', 'text/plain')
|
||||
if (path.indexOf('.gz') !== -1) {
|
||||
res.setHeader('Content-Encoding', 'gzip')
|
||||
} else if (path.indexOf('.br') !== -1) {
|
||||
res.setHeader('Content-Encoding', 'br')
|
||||
}
|
||||
if (path.indexOf('.pdf') !== -1) {
|
||||
res.setHeader('Content-Type', 'application/pdf');
|
||||
} else {
|
||||
res.setHeader('Content-Type', 'application/json')
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = (env, argv) => {
|
||||
|
||||
18
platform/viewer/cypress/integration/OHIFPdfDisplay.spec.js
Normal file
18
platform/viewer/cypress/integration/OHIFPdfDisplay.spec.js
Normal file
@ -0,0 +1,18 @@
|
||||
describe('OHIF PDF Display', function () {
|
||||
before(() => {
|
||||
cy.openStudyInViewer(
|
||||
'2.25.317377619501274872606137091638706705333'
|
||||
);
|
||||
});
|
||||
|
||||
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', 0);
|
||||
});
|
||||
|
||||
});
|
||||
@ -57,7 +57,6 @@
|
||||
"@ohif/ui": "^2.0.0",
|
||||
"@types/react": "^16.0.0",
|
||||
"classnames": "^2.2.6",
|
||||
"config-point": "^0.3.4",
|
||||
"core-js": "^3.16.1",
|
||||
"cornerstone-math": "^0.1.9",
|
||||
"cornerstone-tools": "6.0.2",
|
||||
|
||||
@ -23,6 +23,7 @@ window.config = {
|
||||
supportsFuzzyMatching: false,
|
||||
supportsWildcard: true,
|
||||
staticWado: true,
|
||||
singlepart: "video,thumbnail,pdf",
|
||||
},
|
||||
},
|
||||
// {
|
||||
|
||||
@ -23,6 +23,7 @@ window.config = {
|
||||
supportsFuzzyMatching: false,
|
||||
supportsWildcard: true,
|
||||
staticWado: true,
|
||||
singlepart: "bulkdata,video,pdf",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@ -17,7 +17,6 @@ import {
|
||||
errorHandler
|
||||
// utils,
|
||||
} from '@ohif/core';
|
||||
import ConfigPoint from 'config-point';
|
||||
|
||||
/**
|
||||
* @param {object|func} appConfigOrFunc - application configuration, or a function that returns application configuration
|
||||
@ -50,10 +49,6 @@ function appInit(appConfigOrFunc, defaultExtensions) {
|
||||
appConfig,
|
||||
});
|
||||
|
||||
// Load the default theme settings
|
||||
const defaultTheme = config && config.defaultTheme || 'theme';
|
||||
ConfigPoint.load(defaultTheme, '/theme', 'theme');
|
||||
|
||||
servicesManager.registerServices([
|
||||
UINotificationService,
|
||||
UIModalService,
|
||||
|
||||
@ -24,6 +24,7 @@ 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';
|
||||
import OHIFDICOMPDFExtension from '@ohif/extension-dicom-pdf';
|
||||
|
||||
/** Combine our appConfiguration and "baked-in" extensions */
|
||||
const appProps = {
|
||||
@ -34,11 +35,11 @@ const appProps = {
|
||||
OHIFMeasurementTrackingExtension,
|
||||
OHIFDICOMSRExtension,
|
||||
OHIFDICOMVIDEOExtension,
|
||||
OHIFDICOMPDFExtension,
|
||||
],
|
||||
};
|
||||
|
||||
/** Create App */
|
||||
const app = React.createElement(App, appProps, null);
|
||||
|
||||
/** Render */
|
||||
ReactDOM.render(app, document.getElementById('root'));
|
||||
|
||||
10
yarn.lock
10
yarn.lock
@ -1236,7 +1236,7 @@
|
||||
core-js-pure "^3.20.2"
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@7.1.2", "@babel/runtime@7.16.3", "@babel/runtime@7.7.6", "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.6", "@babel/runtime@^7.14.8", "@babel/runtime@^7.16.3", "@babel/runtime@^7.16.7", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
|
||||
"@babel/runtime@7.1.2", "@babel/runtime@7.16.3", "@babel/runtime@7.7.6", "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.8", "@babel/runtime@^7.16.3", "@babel/runtime@^7.16.7", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
|
||||
version "7.16.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.3.tgz#b86f0db02a04187a3c17caa77de69840165d42d5"
|
||||
integrity sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==
|
||||
@ -7943,14 +7943,6 @@ config-chain@^1.1.11:
|
||||
ini "^1.3.4"
|
||||
proto-list "~1.2.1"
|
||||
|
||||
config-point@^0.3.4:
|
||||
version "0.3.4"
|
||||
resolved "https://registry.yarnpkg.com/config-point/-/config-point-0.3.4.tgz#ac4ec8f39432400b6f31b0fe5d6edcc18fe8650a"
|
||||
integrity sha512-/5heLx9DqfZKduBF09w64FPxLzPBD+yYepNvGS6rexUVfIBCKiE0hQkALvriqEzX6MvEA5U2gxdrd+mQLlTCtw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.14.6"
|
||||
json5 "^2.2.0"
|
||||
|
||||
configstore@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/configstore/-/configstore-5.0.1.tgz#d365021b5df4b98cdd187d6a3b0e3f6a7cc5ed96"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user