diff --git a/extensions/default/src/DicomWebDataSource/index.js b/extensions/default/src/DicomWebDataSource/index.js
index c83fabfdfb4..c1df31e461d 100644
--- a/extensions/default/src/DicomWebDataSource/index.js
+++ b/extensions/default/src/DicomWebDataSource/index.js
@@ -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: {
diff --git a/extensions/default/src/DicomWebDataSource/utils/StaticWadoClient.js b/extensions/default/src/DicomWebDataSource/utils/StaticWadoClient.js
index 92f40f66b0e..d250ecacc74 100644
--- a/extensions/default/src/DicomWebDataSource/utils/StaticWadoClient.js
+++ b/extensions/default/src/DicomWebDataSource/utils/StaticWadoClient.js
@@ -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;
diff --git a/extensions/dicom-pdf/LICENSE b/extensions/dicom-pdf/LICENSE
new file mode 100644
index 00000000000..19e20dd35ca
--- /dev/null
+++ b/extensions/dicom-pdf/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-pdf/README.md b/extensions/dicom-pdf/README.md
new file mode 100644
index 00000000000..1e2c6085d2e
--- /dev/null
+++ b/extensions/dicom-pdf/README.md
@@ -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.
diff --git a/extensions/dicom-pdf/package.json b/extensions/dicom-pdf/package.json
new file mode 100644
index 00000000000..17ea43990d1
--- /dev/null
+++ b/extensions/dicom-pdf/package.json
@@ -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"
+ }
+}
diff --git a/extensions/dicom-pdf/src/getSopClassHandlerModule.js b/extensions/dicom-pdf/src/getSopClassHandlerModule.js
new file mode 100644
index 00000000000..fc34fcf8385
--- /dev/null
+++ b/extensions/dicom-pdf/src/getSopClassHandlerModule.js
@@ -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,
+ },
+ ];
+}
diff --git a/extensions/dicom-pdf/src/id.js b/extensions/dicom-pdf/src/id.js
new file mode 100644
index 00000000000..6f5978f4059
--- /dev/null
+++ b/extensions/dicom-pdf/src/id.js
@@ -0,0 +1,8 @@
+const Name = 'dicom-pdf';
+const id = `org.ohif.${Name}`;
+
+export default id;
+
+const SOPClassHandlerId = `${id}.sopClassHandlerModule.${Name}`;
+
+export { Name, SOPClassHandlerId, };
diff --git a/extensions/dicom-pdf/src/index.js b/extensions/dicom-pdf/src/index.js
new file mode 100644
index 00000000000..b40fc2de8aa
--- /dev/null
+++ b/extensions/dicom-pdf/src/index.js
@@ -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 (
+