This commit is contained in:
mario
2025-03-07 13:47:44 +07:00
commit c4efec5a14
3358 changed files with 303774 additions and 0 deletions

View File

@@ -0,0 +1,114 @@
import { SOPClassHandlerId } from './id';
import { utils } from '@ohif/core';
import { utilities as csUtils, Enums as csEnums } from '@cornerstonejs/core';
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 secondaryCaptureSopClassUids = [
SOP_CLASS_UIDS.SECONDARY_CAPTURE_IMAGE_STORAGE,
SOP_CLASS_UIDS.MULTIFRAME_TRUE_COLOR_SECONDARY_CAPTURE_IMAGE_STORAGE,
];
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'];
if (supportedTransferSyntaxUIDs.includes(tsuid)) {
return true;
}
if (metadata.SOPClassUID === SOP_CLASS_UIDS.VIDEO_PHOTOGRAPHIC_IMAGE_STORAGE) {
return true;
}
// Assume that an instance with one of the secondary capture SOPClassUIDs and
// with at least 90 frames (i.e. typically 3 seconds of video) is indeed a video.
return (
secondaryCaptureSopClassUids.includes(metadata.SOPClassUID) && metadata.NumberOfFrames >= 90
);
})
.map(instance => {
const { Modality, SOPInstanceUID, SeriesDescription = 'VIDEO', imageId } = instance;
const { SeriesNumber, SeriesDate, SeriesInstanceUID, StudyInstanceUID, NumberOfFrames, url } =
instance;
const videoUrl = dataSource.retrieve.directURL({
instance,
singlepart: 'video',
tag: 'PixelData',
url,
});
const displaySet = {
//plugin: id,
Modality,
displaySetInstanceUID: utils.guid(),
SeriesDescription,
SeriesNumber,
SeriesDate,
SOPInstanceUID,
SeriesInstanceUID,
StudyInstanceUID,
SOPClassHandlerId,
referencedImages: null,
measurements: null,
viewportType: csEnums.ViewportType.VIDEO,
// The videoUrl is deprecated, the preferred URL is renderedUrl
videoUrl,
renderedUrl: videoUrl,
instances: [instance],
thumbnailSrc: dataSource.retrieve.directURL({
instance,
defaultPath: '/thumbnail',
defaultType: 'image/jpeg',
tag: 'Absent',
}),
imageIds: [imageId],
isDerivedDisplaySet: true,
isLoaded: false,
sopClassUids,
numImageFrames: NumberOfFrames,
instance,
};
csUtils.genericMetadataProvider.add(imageId, {
type: 'imageUrlModule',
metadata: { rendered: videoUrl },
});
return displaySet;
});
};
export default function getSopClassHandlerModule({ servicesManager, extensionManager }) {
const getDisplaySetsFromSeries = instances => {
return _getDisplaySetsFromSeries(instances, servicesManager, extensionManager);
};
return [
{
name: 'dicom-video',
sopClassUids,
getDisplaySetsFromSeries,
},
];
}

View File

@@ -0,0 +1,6 @@
import packageJson from '../package.json';
const id = packageJson.name;
const SOPClassHandlerId = `${id}.sopClassHandlerModule.dicom-video`;
export { SOPClassHandlerId, id };

View File

@@ -0,0 +1,60 @@
import React from 'react';
import getSopClassHandlerModule from './getSopClassHandlerModule';
import { id } from './id';
const Component = React.lazy(() => {
return import(/* webpackPrefetch: true */ './viewports/OHIFCornerstoneVideoViewport');
});
const OHIFCornerstoneVideoViewport = props => {
return (
<React.Suspense fallback={<div>Loading...</div>}>
<Component {...props} />
</React.Suspense>
);
};
/**
*
*/
const dicomVideoExtension = {
/**
* Only required property. Should be a unique value across all extensions.
*/
id,
/**
*
*
* @param {object} [configuration={}]
* @param {object|array} [configuration.csToolsConfig] - Passed directly to `initCornerstoneTools`
*/
getViewportModule({ servicesManager, extensionManager }) {
const ExtendedOHIFCornerstoneVideoViewport = props => {
return (
<OHIFCornerstoneVideoViewport
servicesManager={servicesManager}
extensionManager={extensionManager}
{...props}
/>
);
};
return [{ name: 'dicom-video', component: ExtendedOHIFCornerstoneVideoViewport }];
},
getSopClassHandlerModule,
};
function _getToolAlias(toolName) {
let toolAlias = toolName;
switch (toolName) {
case 'EllipticalRoi':
toolAlias = 'SREllipticalRoi';
break;
}
return toolAlias;
}
export default dicomVideoExtension;

View File

@@ -0,0 +1,55 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
function OHIFCornerstoneVideoViewport({ displaySets }) {
if (displaySets && displaySets.length > 1) {
throw new Error(
'OHIFCornerstoneVideoViewport: only one display set is supported for dicom video right now'
);
}
const { videoUrl } = displaySets[0];
const mimeType = 'video/mp4';
const [url, setUrl] = useState(null);
useEffect(() => {
const load = async () => {
setUrl(await videoUrl);
};
load();
}, [videoUrl]);
// Need to copies of the source to fix a firefox bug
return (
<div className="bg-primary-black h-full w-full">
<video
src={url}
controls
controlsList="nodownload"
preload="auto"
className="h-full w-full"
crossOrigin="anonymous"
>
<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 = {
displaySets: PropTypes.arrayOf(PropTypes.object).isRequired,
};
export default OHIFCornerstoneVideoViewport;