Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7b5f1847a | ||
|
|
5cb6e63b88 | ||
|
|
48eb4a9ec3 | ||
|
|
d9231fd431 | ||
|
|
e5b578bd8e | ||
|
|
f38979037d | ||
|
|
45ca88f5b8 | ||
|
|
a1dee16a3d | ||
|
|
6a37a2a661 | ||
|
|
a8caf4bba4 | ||
|
|
29dcc82e28 | ||
|
|
dbf0501031 |
No files matched your search
@@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.9.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.9.3...@ohif/extension-cornerstone@2.9.4) (2020-09-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.9.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.9.2...@ohif/extension-cornerstone@2.9.3) (2020-09-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-cornerstone
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.9.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@2.9.1...@ohif/extension-cornerstone@2.9.2) (2020-08-20)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-cornerstone",
|
||||
"version": "2.9.2",
|
||||
"version": "2.9.4",
|
||||
"description": "OHIF extension for Cornerstone",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -36,7 +36,7 @@
|
||||
"cornerstone-math": "^0.1.8",
|
||||
"cornerstone-tools": "^4.20.1",
|
||||
"cornerstone-wado-image-loader": "^3.1.0",
|
||||
"dcmjs": "0.16.0",
|
||||
"dcmjs": "0.16.3",
|
||||
"dicom-parser": "^1.8.3",
|
||||
"hammerjs": "^2.0.8",
|
||||
"prop-types": "^15.6.2",
|
||||
|
||||
@@ -3,6 +3,7 @@ import init from './init.js';
|
||||
import commandsModule from './commandsModule.js';
|
||||
import toolbarModule from './toolbarModule.js';
|
||||
import CornerstoneViewportDownloadForm from './CornerstoneViewportDownloadForm';
|
||||
import { version } from '../package.json';
|
||||
|
||||
const Component = React.lazy(() => {
|
||||
return import('./OHIFCornerstoneViewport');
|
||||
@@ -24,6 +25,7 @@ export default {
|
||||
* Only required property. Should be a unique value across all extensions.
|
||||
*/
|
||||
id: 'cornerstone',
|
||||
version,
|
||||
|
||||
/**
|
||||
*
|
||||
|
||||
File renamed without changes.
File renamed without changes.
@@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.1.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-debugging@0.1.3...@ohif/extension-debugging@0.1.4) (2020-09-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-debugging
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 0.1.3 (2020-09-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-debugging
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.1.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-p10-downloader@0.1.1...@ohif/extension-dicom-p10-downloader@0.1.2) (2020-06-18)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-p10-downloader
|
||||
File renamed without changes.
File renamed without changes.
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@ohif/extension-dicom-p10-downloader",
|
||||
"version": "0.1.2",
|
||||
"description": "OHIF extension for downloading DICOM P10 files",
|
||||
"name": "@ohif/extension-debugging",
|
||||
"version": "0.1.4",
|
||||
"description": "OHIF extension for debugging.",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
"repository": "OHIF/Viewers",
|
||||
@@ -33,6 +33,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"detect-browser": "5.1.1",
|
||||
"dicomweb-client": "^0.6.0",
|
||||
"file-saver": "^2.0.2",
|
||||
"jszip": "^3.2.2"
|
||||
@@ -0,0 +1,4 @@
|
||||
.debugReportModalHeader {
|
||||
padding: 10px 0 10px;
|
||||
color: var(--active-color);
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
import React from 'react';
|
||||
import { detect } from 'detect-browser';
|
||||
import './DebugReportModal.css';
|
||||
|
||||
const DubugReportModal = ({
|
||||
viewports,
|
||||
studies,
|
||||
servers,
|
||||
extensionManager,
|
||||
}) => {
|
||||
return (
|
||||
<div>
|
||||
<table>
|
||||
{getAppVersion()}
|
||||
{getExtensionVersions(extensionManager)}
|
||||
{getBrowserInfo()}
|
||||
{getCurrentStudyUrl()}
|
||||
{getLayout(viewports)}
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getAppVersion = () => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<tr>
|
||||
<th className="debugReportModalHeader">App</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Version</td>
|
||||
<td>{window.version}</td>
|
||||
</tr>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const getCurrentStudyUrl = () => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<tr>
|
||||
<th className="debugReportModalHeader">App</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>URL</td>
|
||||
<td>{window.location.href}</td>
|
||||
</tr>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const getExtensionVersions = extensionManager => {
|
||||
const { registeredExtensionVesions } = extensionManager;
|
||||
|
||||
const lineItems = Object.keys(registeredExtensionVesions).map(extensionId => {
|
||||
const version = registeredExtensionVesions[extensionId];
|
||||
|
||||
return (
|
||||
<tr>
|
||||
<td>{extensionId}</td>
|
||||
<td>{version}</td>
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<th className="debugReportModalHeader">Extensions</th>
|
||||
{lineItems}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const getLayout = viewports => {
|
||||
const { numRows, numColumns } = viewports;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<tr>
|
||||
<th className="debugReportModalHeader">Viewports</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Layout</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Rows</td>
|
||||
<td>{numRows}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Columns</td>
|
||||
<td>{numColumns}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>SeriesInstanceUIDs</th>
|
||||
</tr>
|
||||
{getSeriesInstanceUIDsPerRow(viewports)}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const getBrowserInfo = () => {
|
||||
const browser = detect();
|
||||
|
||||
const { name, os, type, version } = browser;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<tr>
|
||||
<th className="debugReportModalHeader">Platform</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>name</td>
|
||||
<td>{name}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>os</td>
|
||||
<td>{os}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>type</td>
|
||||
<td>{type}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>version</td>
|
||||
<td>{version}</td>
|
||||
</tr>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const getSeriesInstanceUIDsPerRow = viewports => {
|
||||
const { viewportSpecificData, numColumns } = viewports;
|
||||
|
||||
// NOTE viewportSpecificData is actually an object with numerical keys.
|
||||
return Object.keys(viewportSpecificData).map(viewportIndex => {
|
||||
const vsd = viewportSpecificData[viewportIndex];
|
||||
|
||||
const [row, column] = _viewportIndexToViewportPosition(
|
||||
viewportIndex,
|
||||
numColumns
|
||||
);
|
||||
|
||||
return (
|
||||
<tr>
|
||||
<td>{`[${row},${column}]`}</td>
|
||||
<td>{vsd.SeriesInstanceUID}</td>
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const _viewportIndexToViewportPosition = (viewportIndex, numColumns) => {
|
||||
const row = Math.floor(viewportIndex / numColumns);
|
||||
const column = viewportIndex % numColumns;
|
||||
|
||||
return [row, column];
|
||||
};
|
||||
|
||||
export default DubugReportModal;
|
||||
+26
-1
@@ -8,12 +8,14 @@ import {
|
||||
getSOPInstanceReferencesFromViewports,
|
||||
} from './utils';
|
||||
import _downloadAndZip, { downloadInstances } from './downloadAndZip';
|
||||
import DebugReportModal from './DebugReportModal';
|
||||
import React from 'react';
|
||||
|
||||
const {
|
||||
utils: { Queue },
|
||||
} = OHIF;
|
||||
|
||||
export function getCommands(context) {
|
||||
export function getCommands(context, servicesManager, extensionManager) {
|
||||
const queue = new Queue(1);
|
||||
const actions = {
|
||||
/**
|
||||
@@ -87,9 +89,32 @@ export function getCommands(context) {
|
||||
serverConfig
|
||||
);
|
||||
},
|
||||
openDebugInfoModal({ viewports, studies, servers }) {
|
||||
const { UIModalService } = servicesManager.services;
|
||||
|
||||
const WrappedDebugReportModal = function() {
|
||||
return (
|
||||
<DebugReportModal
|
||||
viewports={viewports}
|
||||
studies={studies}
|
||||
servers={servers}
|
||||
extensionManager={extensionManager}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
UIModalService.show({
|
||||
content: WrappedDebugReportModal,
|
||||
title: `Debugging Information`,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
const definitions = {
|
||||
openDebugInfoModal: {
|
||||
commandFn: actions.openDebugInfoModal,
|
||||
storeContexts: ['viewports', 'servers', 'studies'],
|
||||
},
|
||||
downloadAndZip: {
|
||||
commandFn: queue.bindSafe(actions.downloadAndZip, error),
|
||||
storeContexts: ['servers'],
|
||||
File renamed without changes.
@@ -1,5 +1,7 @@
|
||||
import { getDicomWebClientFromConfig } from './utils';
|
||||
import { getCommands } from './commandsModule';
|
||||
import { version } from '../package.json';
|
||||
import toolbarModule from './toolbarModule';
|
||||
|
||||
/**
|
||||
* Constants
|
||||
@@ -21,6 +23,7 @@ export default {
|
||||
* Only required property. Should be a unique value across all extensions.
|
||||
*/
|
||||
id: 'dicom-p10-downloader',
|
||||
version,
|
||||
|
||||
/**
|
||||
* LIFECYCLE HOOKS
|
||||
@@ -37,7 +40,11 @@ export default {
|
||||
* MODULE GETTERS
|
||||
*/
|
||||
|
||||
getCommandsModule() {
|
||||
return getCommands(sharedContext);
|
||||
getCommandsModule({ servicesManager, extensionManager }) {
|
||||
return getCommands(sharedContext, servicesManager, extensionManager);
|
||||
},
|
||||
|
||||
getToolbarModule() {
|
||||
return toolbarModule;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
const TOOLBAR_BUTTON_TYPES = {
|
||||
COMMAND: 'command',
|
||||
};
|
||||
|
||||
const definitions = [
|
||||
{
|
||||
id: 'Debug Info',
|
||||
label: 'Debug Info',
|
||||
icon: 'cog',
|
||||
//
|
||||
type: TOOLBAR_BUTTON_TYPES.COMMAND,
|
||||
commandName: 'openDebugInfoModal',
|
||||
context: 'VIEWER',
|
||||
},
|
||||
];
|
||||
|
||||
export default {
|
||||
definitions,
|
||||
defaultContext: 'VIEWER',
|
||||
};
|
||||
File renamed without changes.
@@ -3,6 +3,30 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.2.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.6...@ohif/extension-dicom-html@1.2.7) (2020-09-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-html
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.2.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.5...@ohif/extension-dicom-html@1.2.6) (2020-09-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-html
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.2.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.4...@ohif/extension-dicom-html@1.2.5) (2020-08-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-html
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.2.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@1.2.3...@ohif/extension-dicom-html@1.2.4) (2020-07-13)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-html
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-dicom-html",
|
||||
"version": "1.2.4",
|
||||
"version": "1.2.7",
|
||||
"description": "OHIF extension for rendering structured reports to HTML",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -29,7 +29,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@ohif/core": "^0.50.0",
|
||||
"dcmjs": "0.16.0",
|
||||
"dcmjs": "0.16.3",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6"
|
||||
|
||||
@@ -22,6 +22,12 @@ const OHIFDicomHtmlSopClassHandler = {
|
||||
getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) {
|
||||
const instance = series.getFirstInstance();
|
||||
|
||||
const {
|
||||
SeriesDate,
|
||||
SeriesTime,
|
||||
SeriesNumber,
|
||||
} = instance._instance.metadata;
|
||||
|
||||
return {
|
||||
plugin: 'html',
|
||||
Modality: 'SR',
|
||||
@@ -31,6 +37,9 @@ const OHIFDicomHtmlSopClassHandler = {
|
||||
SOPInstanceUID: instance.getSOPInstanceUID(),
|
||||
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||
StudyInstanceUID: study.getStudyInstanceUID(),
|
||||
SeriesDate,
|
||||
SeriesTime,
|
||||
SeriesNumber,
|
||||
authorizationHeaders,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import OHIFDicomHtmlSopClassHandler from './OHIFDicomHtmlSopClassHandler.js';
|
||||
import { version } from '../package.json';
|
||||
|
||||
const Component = React.lazy(() => {
|
||||
return import('./OHIFDicomHtmlViewport');
|
||||
@@ -18,6 +19,7 @@ export default {
|
||||
* Only required property. Should be a unique value across all extensions.
|
||||
*/
|
||||
id: 'html',
|
||||
version,
|
||||
|
||||
getViewportModule() {
|
||||
return OHIFDicomHtmlViewport;
|
||||
|
||||
@@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.51.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.51.1...@ohif/extension-dicom-microscopy@0.51.2) (2020-09-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.51.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.51.0...@ohif/extension-dicom-microscopy@0.51.1) (2020-08-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-microscopy
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [0.51.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.50.9...@ohif/extension-dicom-microscopy@0.51.0) (2020-06-04)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-dicom-microscopy",
|
||||
"version": "0.51.0",
|
||||
"version": "0.51.2",
|
||||
"description": "OHIF extension for Dicom Microscopy",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -12,6 +12,12 @@ const DicomMicroscopySopClassHandler = {
|
||||
getDisplaySetFromSeries(series, study, dicomWebClient) {
|
||||
const instance = series.getFirstInstance();
|
||||
|
||||
const {
|
||||
ContentDate,
|
||||
ContentTime,
|
||||
SeriesNumber,
|
||||
} = instance._instance.metadata;
|
||||
|
||||
// Note: We are passing the dicomweb client into each viewport!
|
||||
|
||||
return {
|
||||
@@ -22,6 +28,9 @@ const DicomMicroscopySopClassHandler = {
|
||||
SOPInstanceUID: instance.getSOPInstanceUID(),
|
||||
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||
StudyInstanceUID: study.getStudyInstanceUID(),
|
||||
SeriesDate: ContentDate, // Map ContentDate/Time to SeriesTime for series list sorting.
|
||||
SeriesTime: ContentTime,
|
||||
SeriesNumber,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import DicomMicroscopySopClassHandler from './DicomMicroscopySopClassHandler.js';
|
||||
import { version } from '../package.json';
|
||||
|
||||
const Component = React.lazy(() => {
|
||||
return import('./DicomMicroscopyViewport');
|
||||
@@ -18,6 +19,7 @@ export default {
|
||||
* Only required property. Should be a unique value across all extensions.
|
||||
*/
|
||||
id: 'microscopy',
|
||||
version,
|
||||
|
||||
getViewportModule() {
|
||||
return DicomMicroscopyViewport;
|
||||
|
||||
@@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.0.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-pdf@1.0.4...@ohif/extension-dicom-pdf@1.0.5) (2020-09-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-pdf
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.0.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-pdf@1.0.3...@ohif/extension-dicom-pdf@1.0.4) (2020-08-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-pdf
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.0.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-pdf@1.0.2...@ohif/extension-dicom-pdf@1.0.3) (2020-04-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-pdf
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-dicom-pdf",
|
||||
"version": "1.0.3",
|
||||
"version": "1.0.5",
|
||||
"description": "OHIF extension for Dicom PDF",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -12,6 +12,12 @@ const OHIFDicomPDFSopClassHandler = {
|
||||
getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) {
|
||||
const instance = series.getFirstInstance();
|
||||
|
||||
const {
|
||||
ContentDate,
|
||||
ContentTime,
|
||||
SeriesNumber,
|
||||
} = instance._instance.metadata;
|
||||
|
||||
return {
|
||||
plugin: 'pdf',
|
||||
Modality: 'DOC',
|
||||
@@ -21,6 +27,9 @@ const OHIFDicomPDFSopClassHandler = {
|
||||
SOPInstanceUID: instance.getSOPInstanceUID(),
|
||||
SeriesInstanceUID: series.getSeriesInstanceUID(),
|
||||
StudyInstanceUID: study.getStudyInstanceUID(),
|
||||
SeriesDate: ContentDate, // Map ContentDate/Time to SeriesTime for series list sorting.
|
||||
SeriesTime: ContentTime,
|
||||
SeriesNumber,
|
||||
authorizationHeaders: authorizationHeaders,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import OHIFDicomPDFSopClassHandler from './OHIFDicomPDFSopClassHandler.js';
|
||||
import { version } from '../package.json';
|
||||
|
||||
const Component = React.lazy(() => {
|
||||
return import('./ConnectedOHIFDicomPDFViewer');
|
||||
@@ -18,6 +19,7 @@ export default {
|
||||
* Only required property. Should be a unique value across all extensions.
|
||||
*/
|
||||
id: 'pdf',
|
||||
version,
|
||||
getViewportModule() {
|
||||
return ConnectedOHIFDicomPDFViewer;
|
||||
},
|
||||
|
||||
@@ -3,6 +3,38 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.4.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.3...@ohif/extension-dicom-rt@0.4.4) (2020-09-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.4.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.2...@ohif/extension-dicom-rt@0.4.3) (2020-09-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.4.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.1...@ohif/extension-dicom-rt@0.4.2) (2020-09-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.4.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.4.0...@ohif/extension-dicom-rt@0.4.1) (2020-08-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-rt
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [0.4.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-rt@0.3.0...@ohif/extension-dicom-rt@0.4.0) (2020-08-18)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-dicom-rt",
|
||||
"version": "0.4.0",
|
||||
"version": "0.4.4",
|
||||
"description": "OHIF extension for rendering DICOM RTSTRUCTs on top of cornerstone images.",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -31,7 +31,7 @@
|
||||
"@ohif/core": "^0.50.0",
|
||||
"cornerstone-core": "^2.2.8",
|
||||
"cornerstone-tools": "^4.20.1",
|
||||
"dcmjs": "0.16.0",
|
||||
"dcmjs": "0.16.3",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6"
|
||||
|
||||
@@ -27,6 +27,7 @@ const OHIFDicomRTStructSopClassHandler = {
|
||||
const {
|
||||
SeriesDate,
|
||||
SeriesTime,
|
||||
SeriesNumber,
|
||||
SeriesDescription,
|
||||
FrameOfReferenceUID,
|
||||
SOPInstanceUID,
|
||||
@@ -53,6 +54,7 @@ const OHIFDicomRTStructSopClassHandler = {
|
||||
isLoaded: false,
|
||||
SeriesDate,
|
||||
SeriesTime,
|
||||
SeriesNumber,
|
||||
SeriesDescription,
|
||||
};
|
||||
|
||||
|
||||
@@ -12,6 +12,9 @@ import StructureSetItem from '../StructureSetItem/StructureSetItem';
|
||||
import RTPanelSettings from '../RTSettings/RTSettings';
|
||||
import PanelSection from '../PanelSection/PanelSection';
|
||||
import LoadingIndicator from '../LoadingIndicator/LoadingIndicator';
|
||||
import TOOL_NAMES from '../../utils/toolNames';
|
||||
|
||||
const { RTSTRUCT_DISPLAY_TOOL } = TOOL_NAMES;
|
||||
|
||||
const { studyMetadataManager } = utils;
|
||||
|
||||
@@ -144,13 +147,16 @@ const RTPanel = ({
|
||||
if (isCornerstone()) {
|
||||
const enabledElements = cornerstone.getEnabledElements();
|
||||
const element = enabledElements[activeIndex].element;
|
||||
const toolState = cornerstoneTools.getToolState(element, 'stack');
|
||||
const stackToolState = cornerstoneTools.getToolState(
|
||||
element,
|
||||
'stack'
|
||||
);
|
||||
|
||||
if (!toolState) {
|
||||
if (!stackToolState) {
|
||||
return;
|
||||
}
|
||||
|
||||
const imageIds = toolState.data[0].imageIds;
|
||||
const imageIds = stackToolState.data[0].imageIds;
|
||||
|
||||
const module = cornerstoneTools.getModule('rtstruct');
|
||||
const imageId = module.getters.imageIdOfCenterFrameOfROIContour(
|
||||
@@ -159,6 +165,18 @@ const RTPanel = ({
|
||||
imageIds
|
||||
);
|
||||
|
||||
const toolState = cornerstoneTools.globalImageIdSpecificToolStateManager.saveToolState();
|
||||
const imageIdSpecificToolState = toolState[imageId];
|
||||
|
||||
const rtstructData =
|
||||
imageIdSpecificToolState[RTSTRUCT_DISPLAY_TOOL];
|
||||
|
||||
const specificData = rtstructData.data.find(
|
||||
rtData => rtData.ROINumber === ROINumber
|
||||
);
|
||||
|
||||
specificData.highlight = true;
|
||||
|
||||
const frameIndex = imageIds.indexOf(imageId);
|
||||
const SOPInstanceUID = cornerstone.metaData.get(
|
||||
'SOPInstanceUID',
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import React from 'react';
|
||||
|
||||
import init from './init.js';
|
||||
import sopClassHandlerModule from './OHIFDicomRTStructSopClassHandler';
|
||||
import id from './id.js';
|
||||
import RTPanel from './components/RTPanel/RTPanel';
|
||||
import { version } from '../package.json';
|
||||
|
||||
export default {
|
||||
/**
|
||||
* Only required property. Should be a unique value across all extensions.
|
||||
*/
|
||||
id,
|
||||
version,
|
||||
|
||||
/**
|
||||
*
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { importInternal, getToolState, toolColors } from 'cornerstone-tools';
|
||||
|
||||
import TOOL_NAMES from '../utils/toolNames';
|
||||
import drawCanvasCrosshairs from '../utils/drawCanvasCrosshairs';
|
||||
|
||||
// Cornerstone 3rd party dev kit imports
|
||||
const draw = importInternal('drawing/draw');
|
||||
@@ -37,11 +37,17 @@ export default class RTStructDisplayTool extends BaseTool {
|
||||
return;
|
||||
}
|
||||
|
||||
const { lineWidth, opacity } = rtstructModule.configuration;
|
||||
const {
|
||||
lineWidth,
|
||||
opacity,
|
||||
highlightOpacity,
|
||||
} = rtstructModule.configuration;
|
||||
|
||||
// We have tool data for this element - iterate over each one and draw it
|
||||
const context = getNewContext(eventData.canvasContext.canvas);
|
||||
|
||||
let crossHairCenter;
|
||||
|
||||
for (let i = 0; i < toolState.data.length; i++) {
|
||||
const data = toolState.data[i];
|
||||
|
||||
@@ -75,27 +81,51 @@ export default class RTStructDisplayTool extends BaseTool {
|
||||
colorArray[2]
|
||||
},${opacity})`;
|
||||
|
||||
let highlight = data.highlight;
|
||||
const options = { color, lineWidth };
|
||||
|
||||
if (highlight) {
|
||||
crossHairCenter = { x: 0, y: 0 };
|
||||
|
||||
points.forEach(point => {
|
||||
crossHairCenter.x += point.x;
|
||||
crossHairCenter.y += point.y;
|
||||
});
|
||||
|
||||
crossHairCenter.x /= points.length;
|
||||
crossHairCenter.y /= points.length;
|
||||
|
||||
// TODO: Disabling hightlight for now, it'd be good to bring it back
|
||||
// when we have a good way of doing this for SEG.
|
||||
|
||||
// options.fillStyle = color = `rgba(${colorArray[0]},${colorArray[1]},${
|
||||
// colorArray[2]
|
||||
// },${highlightOpacity})`;
|
||||
|
||||
// Draw highlight lines.
|
||||
|
||||
delete data.highlight; // Don't highlight on next render.
|
||||
}
|
||||
|
||||
switch (data.type) {
|
||||
case 'CLOSED_PLANAR':
|
||||
this._renderClosedPlanar(context, eventData.element, points, {
|
||||
color,
|
||||
lineWidth,
|
||||
});
|
||||
this._renderClosedPlanar(context, eventData.element, points, options);
|
||||
break;
|
||||
case 'POINT':
|
||||
this._renderPoint(context, eventData.element, points, {
|
||||
color,
|
||||
lineWidth,
|
||||
});
|
||||
this._renderPoint(context, eventData.element, points, options);
|
||||
break;
|
||||
case 'OPEN_PLANAR':
|
||||
this._renderOpenPlanar(context, eventData.element, points, {
|
||||
color,
|
||||
lineWidth,
|
||||
});
|
||||
this._renderOpenPlanar(context, eventData.element, points, options);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (crossHairCenter) {
|
||||
drawCanvasCrosshairs(eventData, crossHairCenter, {
|
||||
color: toolColors.getActiveColor(),
|
||||
lineWidth: 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_renderClosedPlanar(context, element, points, options) {
|
||||
|
||||
@@ -13,7 +13,8 @@ function refreshViewport() {
|
||||
|
||||
const configuration = {
|
||||
lineWidth: 3,
|
||||
opacity: 1.0,
|
||||
opacity: 0.75,
|
||||
highlightOpacity: 0.5,
|
||||
};
|
||||
|
||||
const state = {
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import cornerstone from 'cornerstone-core';
|
||||
import cornerstoneTools from 'cornerstone-tools';
|
||||
|
||||
const { importInternal } = cornerstoneTools;
|
||||
const draw = importInternal('drawing/draw');
|
||||
const drawLine = importInternal('drawing/drawLine');
|
||||
const getNewContext = importInternal('drawing/getNewContext');
|
||||
|
||||
export default function _drawCanvasCrosshairs(eventData, center, options) {
|
||||
const context = getNewContext(eventData.canvasContext.canvas);
|
||||
const { element } = eventData;
|
||||
|
||||
const centerCanvas = cornerstone.pixelToCanvas(element, center);
|
||||
|
||||
const { clientWidth: width, clientHeight: height } = element;
|
||||
|
||||
const offset = 10;
|
||||
|
||||
draw(context, context => {
|
||||
drawLine(
|
||||
context,
|
||||
element,
|
||||
{ x: centerCanvas.x + offset, y: centerCanvas.y },
|
||||
{ x: width, y: centerCanvas.y },
|
||||
options,
|
||||
'canvas'
|
||||
);
|
||||
|
||||
drawLine(
|
||||
context,
|
||||
element,
|
||||
{ x: centerCanvas.x - offset, y: centerCanvas.y },
|
||||
{ x: 0, y: centerCanvas.y },
|
||||
options,
|
||||
'canvas'
|
||||
);
|
||||
|
||||
drawLine(
|
||||
context,
|
||||
element,
|
||||
{ x: centerCanvas.x, y: centerCanvas.y + offset },
|
||||
{ x: centerCanvas.x, y: height },
|
||||
options,
|
||||
'canvas'
|
||||
);
|
||||
|
||||
drawLine(
|
||||
context,
|
||||
element,
|
||||
{ x: centerCanvas.x, y: centerCanvas.y - offset },
|
||||
{ x: centerCanvas.x, y: 0 },
|
||||
options,
|
||||
'canvas'
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -3,6 +3,38 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.4.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.4.3...@ohif/extension-dicom-segmentation@0.4.4) (2020-09-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.4.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.4.2...@ohif/extension-dicom-segmentation@0.4.3) (2020-09-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.4.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.4.1...@ohif/extension-dicom-segmentation@0.4.2) (2020-09-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.4.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.4.0...@ohif/extension-dicom-segmentation@0.4.1) (2020-08-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-dicom-segmentation
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [0.4.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-segmentation@0.3.4...@ohif/extension-dicom-segmentation@0.4.0) (2020-08-18)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-dicom-segmentation",
|
||||
"version": "0.4.0",
|
||||
"version": "0.4.4",
|
||||
"description": "OHIF extension for viewing segmentations in the 2D MPR view",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -31,7 +31,7 @@
|
||||
"@ohif/core": "^0.50.0",
|
||||
"cornerstone-core": "^2.2.8",
|
||||
"cornerstone-tools": "^4.20.1",
|
||||
"dcmjs": "0.16.0",
|
||||
"dcmjs": "0.16.3",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6"
|
||||
|
||||
@@ -5,6 +5,7 @@ import cornerstone from 'cornerstone-core';
|
||||
import moment from 'moment';
|
||||
import { utils, log } from '@ohif/core';
|
||||
import { ScrollableArea, TableList, Icon } from '@ohif/ui';
|
||||
import DICOMSegTempCrosshairsTool from '../../tools/DICOMSegTempCrosshairsTool';
|
||||
|
||||
import setActiveLabelmap from '../../utils/setActiveLabelMap';
|
||||
import refreshViewports from '../../utils/refreshViewports';
|
||||
@@ -334,6 +335,12 @@ const SegmentationPanel = ({
|
||||
imageId
|
||||
);
|
||||
|
||||
DICOMSegTempCrosshairsTool.addCrosshair(
|
||||
element,
|
||||
imageId,
|
||||
segmentNumber
|
||||
);
|
||||
|
||||
onSegmentItemClick({
|
||||
StudyInstanceUID,
|
||||
SOPInstanceUID,
|
||||
|
||||
@@ -28,6 +28,7 @@ export default function getSopClassHandlerModule({ servicesManager }) {
|
||||
const {
|
||||
SeriesDate,
|
||||
SeriesTime,
|
||||
SeriesNumber,
|
||||
SeriesDescription,
|
||||
FrameOfReferenceUID,
|
||||
SOPInstanceUID,
|
||||
@@ -52,6 +53,7 @@ export default function getSopClassHandlerModule({ servicesManager }) {
|
||||
isLoaded: false,
|
||||
SeriesDate,
|
||||
SeriesTime,
|
||||
SeriesNumber,
|
||||
SeriesDescription,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import setActiveLabelmap from './utils/setActiveLabelMap';
|
||||
import { classes } from '@ohif/core';
|
||||
|
||||
const { ImageSet } = classes;
|
||||
|
||||
export default function getSourceDisplaySet(studies, segDisplaySet) {
|
||||
const referencedDisplaySet = _getReferencedDisplaySet(segDisplaySet, studies);
|
||||
@@ -19,15 +22,43 @@ const _getReferencedDisplaySet = (segDisplaySet, studies) => {
|
||||
ds => ds.displaySetInstanceUID !== segDisplaySet.displaySetInstanceUID
|
||||
);
|
||||
|
||||
const ReferencedSeriesSequence = Array.isArray(
|
||||
segDisplaySet.metadata.ReferencedSeriesSequence
|
||||
)
|
||||
? segDisplaySet.metadata.ReferencedSeriesSequence
|
||||
: [segDisplaySet.metadata.ReferencedSeriesSequence];
|
||||
const { metadata } = segDisplaySet;
|
||||
|
||||
const referencedSeriesInstanceUIDs = ReferencedSeriesSequence.map(
|
||||
ReferencedSeries => ReferencedSeries.SeriesInstanceUID
|
||||
);
|
||||
let referencedSeriesInstanceUIDs;
|
||||
|
||||
if (metadata.ReferencedSeriesSequence) {
|
||||
const ReferencedSeriesSequence = _toArray(
|
||||
metadata.ReferencedSeriesSequence
|
||||
);
|
||||
|
||||
referencedSeriesInstanceUIDs = ReferencedSeriesSequence.map(
|
||||
ReferencedSeries => ReferencedSeries.SeriesInstanceUID
|
||||
);
|
||||
} else {
|
||||
const { PerFrameFunctionalGroupsSequence } = metadata;
|
||||
|
||||
let SourceImageSequence;
|
||||
|
||||
if (metadata.SourceImageSequence) {
|
||||
SourceImageSequence = metadata.SourceImageSequence;
|
||||
} else {
|
||||
const firstFunctionalGroups = _toArray(
|
||||
PerFrameFunctionalGroupsSequence
|
||||
)[0];
|
||||
const { DerivationImageSequence } = firstFunctionalGroups;
|
||||
|
||||
SourceImageSequence = DerivationImageSequence;
|
||||
}
|
||||
|
||||
const firstSourceImage = _toArray(SourceImageSequence)[0];
|
||||
|
||||
const { ReferencedSOPInstanceUID } = firstSourceImage;
|
||||
|
||||
referencedSeriesInstanceUIDs = _findReferencedSeriesInstanceUIDsFromSOPInstanceUID(
|
||||
otherDisplaySets,
|
||||
ReferencedSOPInstanceUID
|
||||
);
|
||||
}
|
||||
|
||||
const referencedDisplaySet = otherDisplaySets.find(ds =>
|
||||
referencedSeriesInstanceUIDs.includes(ds.SeriesInstanceUID)
|
||||
@@ -35,3 +66,23 @@ const _getReferencedDisplaySet = (segDisplaySet, studies) => {
|
||||
|
||||
return referencedDisplaySet;
|
||||
};
|
||||
|
||||
const _findReferencedSeriesInstanceUIDsFromSOPInstanceUID = (
|
||||
displaySets,
|
||||
SOPInstanceUID
|
||||
) => {
|
||||
const imageSets = displaySets.filter(ds => ds instanceof ImageSet);
|
||||
|
||||
for (let i = 0; i < imageSets.length; i++) {
|
||||
const { images } = imageSets[i];
|
||||
for (let j = 0; j < images.length; j++) {
|
||||
if (images[j].SOPInstanceUID === SOPInstanceUID) {
|
||||
return [images[j].getData().metadata.SeriesInstanceUID];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function _toArray(arrayOrObject) {
|
||||
return Array.isArray(arrayOrObject) ? arrayOrObject : [arrayOrObject];
|
||||
}
|
||||
@@ -1,15 +1,16 @@
|
||||
import React from 'react';
|
||||
|
||||
import init from './init.js';
|
||||
import toolbarModule from './toolbarModule.js';
|
||||
import getSopClassHandlerModule from './getOHIFDicomSegSopClassHandler.js';
|
||||
import SegmentationPanel from './components/SegmentationPanel/SegmentationPanel.js';
|
||||
import { version } from '../package.json';
|
||||
|
||||
export default {
|
||||
/**
|
||||
* Only required property. Should be a unique value across all extensions.
|
||||
*/
|
||||
id: 'com.ohif.dicom-segmentation',
|
||||
version,
|
||||
|
||||
/**
|
||||
*
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import csTools from 'cornerstone-tools';
|
||||
import DICOMSegTempCrosshairsTool from './tools/DICOMSegTempCrosshairsTool';
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -17,4 +18,6 @@ export default function init({ servicesManager, configuration = {} }) {
|
||||
alwaysEraseOnClick: true,
|
||||
},
|
||||
});
|
||||
|
||||
csTools.addTool(DICOMSegTempCrosshairsTool);
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
import cornerstoneTools, {
|
||||
importInternal,
|
||||
getToolState,
|
||||
toolColors,
|
||||
getModule,
|
||||
globalImageIdSpecificToolStateManager,
|
||||
} from 'cornerstone-tools';
|
||||
import cornerstone from 'cornerstone-core';
|
||||
import drawCanvasCrosshairs from '../utils/drawCanvasCrosshairs';
|
||||
import TOOL_NAMES from './TOOL_NAMES';
|
||||
|
||||
const { DICOM_SEG_TEMP_CROSSHAIRS_TOOL } = TOOL_NAMES;
|
||||
const { getters } = getModule('segmentation');
|
||||
|
||||
// Cornerstone 3rd party dev kit imports
|
||||
const BaseTool = importInternal('base/BaseTool');
|
||||
|
||||
/**
|
||||
* @class RTStructDisplayTool - Renders RTSTRUCT data in a read only manner (i.e. as an overlay).
|
||||
* @extends cornerstoneTools.BaseTool
|
||||
*/
|
||||
export default class DICOMSegTempCrosshairsTool extends BaseTool {
|
||||
constructor(props = {}) {
|
||||
const defaultProps = {
|
||||
mixins: ['enabledOrDisabledBinaryTool'],
|
||||
name: DICOM_SEG_TEMP_CROSSHAIRS_TOOL,
|
||||
};
|
||||
|
||||
const initialProps = Object.assign(defaultProps, props);
|
||||
|
||||
super(initialProps);
|
||||
|
||||
this._rtStructModule = cornerstoneTools.getModule('rtstruct');
|
||||
}
|
||||
|
||||
renderToolData(evt) {
|
||||
const eventData = evt.detail;
|
||||
const { element } = eventData;
|
||||
const toolState = getToolState(evt.currentTarget, this.name);
|
||||
|
||||
if (!toolState) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We have tool data for this element - iterate over each one and draw it
|
||||
|
||||
for (let i = 0; i < toolState.data.length; i++) {
|
||||
const data = toolState.data[i];
|
||||
const crossHairCenter = data.center;
|
||||
|
||||
drawCanvasCrosshairs(eventData, crossHairCenter, {
|
||||
color: toolColors.getActiveColor(),
|
||||
lineWidth: 1,
|
||||
});
|
||||
|
||||
// Remove the crosshairs, we only render them for one redraw.
|
||||
toolState.data.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DICOMSegTempCrosshairsTool.addCrosshair = (element, imageId, segmentNumber) => {
|
||||
const labelmap3D = getters.labelmap3D(element);
|
||||
const stackToolState = cornerstoneTools.getToolState(element, 'stack');
|
||||
const enabledElement = cornerstone.getEnabledElement(element);
|
||||
|
||||
const { rows, columns } = enabledElement.image;
|
||||
|
||||
if (!stackToolState) {
|
||||
return;
|
||||
}
|
||||
|
||||
const imageIds = stackToolState.data[0].imageIds;
|
||||
const imageIdIndex = imageIds.findIndex(imgId => imgId === imageId);
|
||||
|
||||
const labelmap2D = labelmap3D.labelmaps2D[imageIdIndex];
|
||||
const { pixelData } = labelmap2D;
|
||||
|
||||
let xCenter = 0;
|
||||
let yCenter = 0;
|
||||
|
||||
let count = 0;
|
||||
|
||||
for (let y = 0; y < rows; y++) {
|
||||
for (let x = 0; x < columns; x++) {
|
||||
if (pixelData[y * columns + x] === segmentNumber) {
|
||||
count++;
|
||||
xCenter += x + 0.5;
|
||||
yCenter += y + 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
xCenter /= count;
|
||||
yCenter /= count;
|
||||
|
||||
const globalToolState = globalImageIdSpecificToolStateManager.saveToolState();
|
||||
|
||||
if (!globalToolState[imageId]) {
|
||||
globalToolState[imageId] = {};
|
||||
}
|
||||
|
||||
const imageIdSpecificToolState = globalToolState[imageId];
|
||||
|
||||
if (!imageIdSpecificToolState[DICOM_SEG_TEMP_CROSSHAIRS_TOOL]) {
|
||||
imageIdSpecificToolState[DICOM_SEG_TEMP_CROSSHAIRS_TOOL] = { data: [] };
|
||||
} else if (!imageIdSpecificToolState[DICOM_SEG_TEMP_CROSSHAIRS_TOOL].data) {
|
||||
imageIdSpecificToolState[DICOM_SEG_TEMP_CROSSHAIRS_TOOL].data = [];
|
||||
}
|
||||
|
||||
const toolSpecificData =
|
||||
imageIdSpecificToolState[DICOM_SEG_TEMP_CROSSHAIRS_TOOL].data;
|
||||
|
||||
toolSpecificData.push({ center: { x: xCenter, y: yCenter }, segmentNumber });
|
||||
|
||||
// Enable the tool if not enabled for the element.
|
||||
|
||||
const tool = cornerstoneTools.getToolForElement(
|
||||
element,
|
||||
DICOM_SEG_TEMP_CROSSHAIRS_TOOL
|
||||
);
|
||||
|
||||
if (tool.mode !== 'enabled') {
|
||||
// If not already active or passive, set passive so contours render.
|
||||
cornerstoneTools.setToolEnabled(DICOM_SEG_TEMP_CROSSHAIRS_TOOL);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,5 @@
|
||||
const TOOL_NAMES = {
|
||||
DICOM_SEG_TEMP_CROSSHAIRS_TOOL: 'DICOMSegTempCrosshairsTool',
|
||||
};
|
||||
|
||||
export default TOOL_NAMES;
|
||||
@@ -0,0 +1,56 @@
|
||||
import cornerstone from 'cornerstone-core';
|
||||
import cornerstoneTools from 'cornerstone-tools';
|
||||
|
||||
const { importInternal } = cornerstoneTools;
|
||||
const draw = importInternal('drawing/draw');
|
||||
const drawLine = importInternal('drawing/drawLine');
|
||||
const getNewContext = importInternal('drawing/getNewContext');
|
||||
|
||||
export default function _drawCanvasCrosshairs(eventData, center, options) {
|
||||
const context = getNewContext(eventData.canvasContext.canvas);
|
||||
const { element } = eventData;
|
||||
|
||||
const centerCanvas = cornerstone.pixelToCanvas(element, center);
|
||||
|
||||
const { clientWidth: width, clientHeight: height } = element;
|
||||
|
||||
const offset = 10;
|
||||
|
||||
draw(context, context => {
|
||||
drawLine(
|
||||
context,
|
||||
element,
|
||||
{ x: centerCanvas.x + offset, y: centerCanvas.y },
|
||||
{ x: width, y: centerCanvas.y },
|
||||
options,
|
||||
'canvas'
|
||||
);
|
||||
|
||||
drawLine(
|
||||
context,
|
||||
element,
|
||||
{ x: centerCanvas.x - offset, y: centerCanvas.y },
|
||||
{ x: 0, y: centerCanvas.y },
|
||||
options,
|
||||
'canvas'
|
||||
);
|
||||
|
||||
drawLine(
|
||||
context,
|
||||
element,
|
||||
{ x: centerCanvas.x, y: centerCanvas.y + offset },
|
||||
{ x: centerCanvas.x, y: height },
|
||||
options,
|
||||
'canvas'
|
||||
);
|
||||
|
||||
drawLine(
|
||||
context,
|
||||
element,
|
||||
{ x: centerCanvas.x, y: centerCanvas.y - offset },
|
||||
{ x: centerCanvas.x, y: 0 },
|
||||
options,
|
||||
'canvas'
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -3,6 +3,14 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.2.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-lesion-tracker@0.2.0...@ohif/extension-lesion-tracker@0.2.1) (2020-09-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-lesion-tracker
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [0.2.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-lesion-tracker@0.1.0...@ohif/extension-lesion-tracker@0.2.0) (2020-02-10)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-lesion-tracker",
|
||||
"version": "0.2.0",
|
||||
"version": "0.2.1",
|
||||
"description": "OHIF extension for Lesion Tracker",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import MeasurementComparisonTable from './components/MeasurementComparisonTable';
|
||||
import { version } from '../package.json';
|
||||
|
||||
export default {
|
||||
/**
|
||||
* Only required property. Should be a unique value across all extensions.
|
||||
*/
|
||||
id: 'lesion-tracker',
|
||||
version,
|
||||
|
||||
/**
|
||||
* @param {object} params
|
||||
@@ -26,7 +28,7 @@ export default {
|
||||
component: MeasurementComparisonTable,
|
||||
},
|
||||
],
|
||||
defaultContext: ['VIEWER']
|
||||
defaultContext: ['VIEWER'],
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
@@ -3,6 +3,38 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.7.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.4...@ohif/extension-vtk@1.7.5) (2020-09-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.7.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.3...@ohif/extension-vtk@1.7.4) (2020-09-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.7.3](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.2...@ohif/extension-vtk@1.7.3) (2020-09-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.7.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.1...@ohif/extension-vtk@1.7.2) (2020-08-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/extension-vtk
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [1.7.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@1.7.0...@ohif/extension-vtk@1.7.1) (2020-08-24)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/extension-vtk",
|
||||
"version": "1.7.1",
|
||||
"version": "1.7.5",
|
||||
"description": "OHIF extension for VTK.js",
|
||||
"author": "OHIF",
|
||||
"license": "MIT",
|
||||
@@ -35,7 +35,7 @@
|
||||
"cornerstone-core": "^2.2.8",
|
||||
"cornerstone-tools": "^4.20.1",
|
||||
"cornerstone-wado-image-loader": "^3.1.0",
|
||||
"dcmjs": "0.16.0",
|
||||
"dcmjs": "0.16.3",
|
||||
"dicom-parser": "^1.8.3",
|
||||
"i18next": "^17.0.3",
|
||||
"i18next-browser-languagedetector": "^3.0.1",
|
||||
@@ -53,7 +53,7 @@
|
||||
"react-vtkjs-viewport": "^0.10.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ohif/core": "^2.10.0",
|
||||
"@ohif/core": "^2.10.4",
|
||||
"@ohif/ui": "^1.5.3",
|
||||
"cornerstone-tools": "^4.20.1",
|
||||
"cornerstone-wado-image-loader": "^3.1.0",
|
||||
|
||||
@@ -3,6 +3,7 @@ import asyncComponent from './asyncComponent.js';
|
||||
import commandsModule from './commandsModule.js';
|
||||
import toolbarModule from './toolbarModule.js';
|
||||
import withCommandsManager from './withCommandsManager.js';
|
||||
import { version } from '../package.json';
|
||||
// This feels weird
|
||||
// import loadLocales from './loadLocales';
|
||||
|
||||
@@ -15,6 +16,7 @@ const vtkExtension = {
|
||||
* Only required property. Should be a unique value across all extensions.
|
||||
*/
|
||||
id: 'vtk',
|
||||
version,
|
||||
|
||||
getViewportModule({ commandsManager, servicesManager }) {
|
||||
const ExtendedVTKViewport = props => (
|
||||
|
||||
@@ -3,6 +3,38 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.10.4](https://github.com/OHIF/Viewers/compare/@ohif/core@2.10.3...@ohif/core@2.10.4) (2020-09-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.10.3](https://github.com/OHIF/Viewers/compare/@ohif/core@2.10.2...@ohif/core@2.10.3) (2020-09-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.10.2](https://github.com/OHIF/Viewers/compare/@ohif/core@2.10.1...@ohif/core@2.10.2) (2020-09-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.10.1](https://github.com/OHIF/Viewers/compare/@ohif/core@2.10.0...@ohif/core@2.10.1) (2020-08-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.10.0](https://github.com/OHIF/Viewers/compare/@ohif/core@2.9.14...@ohif/core@2.10.0) (2020-08-18)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/core",
|
||||
"version": "2.10.0",
|
||||
"version": "2.10.4",
|
||||
"description": "Generic business logic for web-based medical imaging applications",
|
||||
"author": "OHIF Core Team",
|
||||
"license": "MIT",
|
||||
@@ -39,7 +39,7 @@
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"ajv": "^6.10.0",
|
||||
"dcmjs": "0.16.0",
|
||||
"dcmjs": "0.16.3",
|
||||
"dicomweb-client": "^0.6.0",
|
||||
"immer": "6.0.2",
|
||||
"isomorphic-base64": "^1.0.2",
|
||||
|
||||
@@ -54,9 +54,6 @@ export class OHIFStudyMetadataSource extends StudyMetadataSource {
|
||||
// Get Study display sets
|
||||
const displaySets = studyMetadata.createDisplaySets();
|
||||
|
||||
// Set studyMetadata display sets
|
||||
studyMetadata.setDisplaySets(displaySets);
|
||||
|
||||
OHIFStudyMetadataSource._updateStudyCollections(studyMetadata);
|
||||
resolve(studyMetadata);
|
||||
})
|
||||
|
||||
@@ -10,8 +10,8 @@ import { api } from 'dicomweb-client';
|
||||
// - createStacks
|
||||
import { isImage } from '../../utils/isImage';
|
||||
import isDisplaySetReconstructable from '../../utils/isDisplaySetReconstructable';
|
||||
import isLowPriorityModality from '../../utils/isLowPriorityModality';
|
||||
import errorHandler from '../../errorHandler';
|
||||
import isLowPriorityModality from '../../utils/isLowPriorityModality';
|
||||
|
||||
export class StudyMetadata extends Metadata {
|
||||
constructor(data, uid) {
|
||||
@@ -308,14 +308,10 @@ export class StudyMetadata extends Metadata {
|
||||
series
|
||||
);
|
||||
|
||||
displaySets.push(...displaySetsForSeries);
|
||||
displaySetsForSeries.forEach(ds => this._insertDisplaySet(ds));
|
||||
});
|
||||
|
||||
return sortDisplaySetList(displaySets);
|
||||
}
|
||||
|
||||
sortDisplaySets() {
|
||||
sortDisplaySetList(this._displaySets);
|
||||
return this._displaySets;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -346,25 +342,9 @@ export class StudyMetadata extends Metadata {
|
||||
this.addDisplaySet(displaySet);
|
||||
});
|
||||
|
||||
this.sortDisplaySets();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set display sets
|
||||
* @param {Array} displaySets Array of display sets (ImageSet[])
|
||||
*/
|
||||
setDisplaySets(displaySets) {
|
||||
if (Array.isArray(displaySets) && displaySets.length > 0) {
|
||||
// TODO: This is weird, can we just switch it to writable: true?
|
||||
this._displaySets.splice(0);
|
||||
|
||||
displaySets.forEach(displaySet => this.addDisplaySet(displaySet));
|
||||
this.sortDisplaySets();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a single display set to the list
|
||||
* @param {Object} displaySet Display set object
|
||||
@@ -372,7 +352,7 @@ export class StudyMetadata extends Metadata {
|
||||
*/
|
||||
addDisplaySet(displaySet) {
|
||||
if (displaySet instanceof ImageSet || displaySet.sopClassModule) {
|
||||
this._displaySets.push(displaySet);
|
||||
this._insertDisplaySet(displaySet);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -393,6 +373,96 @@ export class StudyMetadata extends Metadata {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the displaySet so that the list has an increasing SeriesNumber,
|
||||
* with the most recent series first for displaySets with the same SeriesNumber.
|
||||
*
|
||||
* If the displaySet is low priority, the same logic is applied, but is sorted within a sub list
|
||||
* At the end of the list, where all low priority data is found.
|
||||
*/
|
||||
_insertDisplaySet(displaySet) {
|
||||
const { SeriesNumber } = displaySet;
|
||||
const displaySets = this._displaySets;
|
||||
let insertIndex = displaySets.length;
|
||||
let firstIndexWithSameSeriesNumber;
|
||||
|
||||
// If low priority, start search from next low priority.
|
||||
if (isLowPriorityModality(displaySet.Modality)) {
|
||||
let startingIndex;
|
||||
|
||||
// Find where the first low priority displaySet is.
|
||||
for (let i = 0; i < displaySets.length; i++) {
|
||||
if (isLowPriorityModality(displaySets[i].Modality)) {
|
||||
startingIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!startingIndex) {
|
||||
startingIndex = displaySets.length;
|
||||
}
|
||||
|
||||
// Find the correct SeriesNumber location to insert within the low priority
|
||||
// Modality displaySets
|
||||
for (let i = startingIndex; i < displaySets.length; i++) {
|
||||
if (
|
||||
displaySets[i].SeriesNumber === SeriesNumber &&
|
||||
!firstIndexWithSameSeriesNumber
|
||||
) {
|
||||
firstIndexWithSameSeriesNumber = i;
|
||||
}
|
||||
|
||||
if (displaySets[i].SeriesNumber > SeriesNumber) {
|
||||
insertIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Find correct SeriesNumber to insert or where the low priority modalities start.
|
||||
for (let i = 0; i < displaySets.length; i++) {
|
||||
if (
|
||||
displaySets[i].SeriesNumber === SeriesNumber &&
|
||||
!firstIndexWithSameSeriesNumber
|
||||
) {
|
||||
firstIndexWithSameSeriesNumber = i;
|
||||
}
|
||||
|
||||
if (
|
||||
displaySets[i].SeriesNumber > SeriesNumber ||
|
||||
isLowPriorityModality(displaySets[i].Modality)
|
||||
) {
|
||||
insertIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we have multiple displaySets with the same series number, find the insert position based on
|
||||
// SeriesDate and SeriesTime.
|
||||
if (firstIndexWithSameSeriesNumber !== undefined) {
|
||||
// If no SeriesDate, is just a placeholder displaySet, just insert anywhere, it will be re-added later.
|
||||
if (displaySet.SeriesDate) {
|
||||
const seriesDateTime = `${displaySet.SeriesDate}${displaySet.SeriesTime}`;
|
||||
|
||||
for (let i = firstIndexWithSameSeriesNumber; i < insertIndex; i++) {
|
||||
const displaySetI = displaySets[i];
|
||||
|
||||
if (
|
||||
displaySetI.SeriesDate &&
|
||||
`${displaySetI.SeriesDate}${displaySetI.SeriesTime}` <
|
||||
seriesDateTime
|
||||
) {
|
||||
insertIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._displaySets.splice(insertIndex, 0, displaySet);
|
||||
this.displaySets = this._displaySets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search the associated display sets using the supplied callback as criteria. The callback is passed
|
||||
* two arguments: display set (an ImageSet instance) and index (the integer
|
||||
@@ -548,49 +618,6 @@ export class StudyMetadata extends Metadata {
|
||||
return this._series.indexOf(series);
|
||||
}
|
||||
|
||||
/**
|
||||
* It sorts the series based on display sets order. Each series must be an instance
|
||||
* of SeriesMetadata and each display sets must be an instance of ImageSet.
|
||||
* Useful example of usage:
|
||||
* Study data provided by backend does not sort series at all and client-side
|
||||
* needs series sorted by the same criteria used for sorting display sets.
|
||||
*/
|
||||
sortSeriesByDisplaySets() {
|
||||
// Object for mapping display sets' index by SeriesInstanceUID
|
||||
const displaySetsMapping = {};
|
||||
|
||||
// Loop through each display set to create the mapping
|
||||
this.forEachDisplaySet((displaySet, index) => {
|
||||
if (!(displaySet instanceof ImageSet)) {
|
||||
throw new OHIFError(
|
||||
`StudyMetadata::sortSeriesByDisplaySets display set at index ${index} is not an instance of ImageSet`
|
||||
);
|
||||
}
|
||||
|
||||
// In case of multiframe studies, just get the first index occurence
|
||||
if (displaySetsMapping[displaySet.SeriesInstanceUID] === void 0) {
|
||||
displaySetsMapping[displaySet.SeriesInstanceUID] = index;
|
||||
}
|
||||
});
|
||||
|
||||
// Clone of actual series
|
||||
const actualSeries = this.getSeries();
|
||||
|
||||
actualSeries.forEach((series, index) => {
|
||||
if (!(series instanceof SeriesMetadata)) {
|
||||
throw new OHIFError(
|
||||
`StudyMetadata::sortSeriesByDisplaySets series at index ${index} is not an instance of SeriesMetadata`
|
||||
);
|
||||
}
|
||||
|
||||
// Get the new series index
|
||||
const seriesIndex = displaySetsMapping[series.getSeriesInstanceUID()];
|
||||
|
||||
// Update the series object with the new series position
|
||||
this._series[seriesIndex] = series;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the current study instance with another one.
|
||||
* @param {StudyMetadata} study An instance of the StudyMetadata class.
|
||||
@@ -865,53 +892,3 @@ function _getDisplaySetFromSopClassModule(
|
||||
}
|
||||
return displaySet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort series primarily by Modality (i.e., series with references to other
|
||||
* series like SEG, KO or PR are grouped in the end of the list) and then by
|
||||
* series number:
|
||||
*
|
||||
* --------
|
||||
* | CT #3 |
|
||||
* | CT #4 |
|
||||
* | CT #5 |
|
||||
* --------
|
||||
* | SEG #1 |
|
||||
* | SEG #2 |
|
||||
* --------
|
||||
*
|
||||
* @param {*} a - DisplaySet
|
||||
* @param {*} b - DisplaySet
|
||||
*/
|
||||
|
||||
function seriesSortingCriteria(a, b) {
|
||||
const isLowPriorityA = isLowPriorityModality(a.Modality);
|
||||
const isLowPriorityB = isLowPriorityModality(b.Modality);
|
||||
if (!isLowPriorityA && isLowPriorityB) {
|
||||
return -1;
|
||||
}
|
||||
if (isLowPriorityA && !isLowPriorityB) {
|
||||
return 1;
|
||||
}
|
||||
return sortBySeriesNumber(a, b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort series by series number. Series with low
|
||||
* @param {*} a - DisplaySet
|
||||
* @param {*} b - DisplaySet
|
||||
*/
|
||||
function sortBySeriesNumber(a, b) {
|
||||
const seriesNumberAIsGreaterOrUndefined =
|
||||
a.SeriesNumber > b.SeriesNumber || (!a.SeriesNumber && b.SeriesNumber);
|
||||
|
||||
return seriesNumberAIsGreaterOrUndefined ? 1 : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts a list of display set objects
|
||||
* @param {Array} list A list of display sets to be sorted
|
||||
*/
|
||||
function sortDisplaySetList(list) {
|
||||
return list.sort(seriesSortingCriteria);
|
||||
}
|
||||
@@ -5,6 +5,7 @@ export default class ExtensionManager {
|
||||
constructor({ commandsManager, servicesManager, api, appConfig = {} }) {
|
||||
this.modules = {};
|
||||
this.registeredExtensionIds = [];
|
||||
this.registeredExtensionVesions = {};
|
||||
this.moduleTypeNames = Object.values(MODULE_TYPES);
|
||||
//
|
||||
this._commandsManager = commandsManager;
|
||||
@@ -51,6 +52,7 @@ export default class ExtensionManager {
|
||||
}
|
||||
|
||||
let extensionId = extension.id;
|
||||
const version = extension.version;
|
||||
|
||||
if (!extensionId) {
|
||||
extensionId = Math.random()
|
||||
@@ -98,6 +100,8 @@ export default class ExtensionManager {
|
||||
|
||||
// Track extension registration
|
||||
this.registeredExtensionIds.push(extensionId);
|
||||
|
||||
this.registeredExtensionVesions[extensionId] = version;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -120,7 +124,8 @@ export default class ExtensionManager {
|
||||
commandsManager: this._commandsManager,
|
||||
appConfig: this._appConfig,
|
||||
configuration,
|
||||
api: this._api
|
||||
api: this._api,
|
||||
extensionManager: this,
|
||||
});
|
||||
|
||||
if (!extensionModule) {
|
||||
|
||||
@@ -184,6 +184,8 @@ describe('ExtensionManager.js', () => {
|
||||
commandsManager,
|
||||
appConfig,
|
||||
configuration: extensionConfiguration,
|
||||
api: undefined,
|
||||
extensionManager,
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -226,7 +228,7 @@ describe('ExtensionManager.js', () => {
|
||||
return {
|
||||
definitions: {
|
||||
exampleDefinition: {
|
||||
commandFn: () => { },
|
||||
commandFn: () => {},
|
||||
storeContexts: [],
|
||||
options: {},
|
||||
},
|
||||
|
||||
@@ -11,12 +11,15 @@ import { retrieveStudyMetadata } from './retrieveStudyMetadata';
|
||||
* @param {Array} studyInstanceUIDs The UIDs of the Studies to be retrieved
|
||||
* @param {Object} [filters] - Object containing filters to be applied on retrieve metadata process
|
||||
* @param {string} [filter.seriesInstanceUID] - series instance uid to filter results against
|
||||
* @param {boolean} [separateSeriesInstanceUIDFilters = false] - If true, split filtered metadata calls into multiple calls,
|
||||
* as some DICOMWeb implementations only support single filters.
|
||||
* @returns {Promise} that will be resolved with the metadata or rejected with the error
|
||||
*/
|
||||
export default function retrieveStudiesMetadata(
|
||||
server,
|
||||
studyInstanceUIDs,
|
||||
filters
|
||||
filters,
|
||||
separateSeriesInstanceUIDFilters = false
|
||||
) {
|
||||
// Create an empty array to store the Promises for each metaData retrieval call
|
||||
const promises = [];
|
||||
@@ -24,7 +27,12 @@ export default function retrieveStudiesMetadata(
|
||||
// Loop through the array of studyInstanceUIDs
|
||||
studyInstanceUIDs.forEach(function(StudyInstanceUID) {
|
||||
// Send the call and resolve or reject the related promise based on its outcome
|
||||
const promise = retrieveStudyMetadata(server, StudyInstanceUID, filters);
|
||||
const promise = retrieveStudyMetadata(
|
||||
server,
|
||||
StudyInstanceUID,
|
||||
filters,
|
||||
separateSeriesInstanceUIDFilters
|
||||
);
|
||||
|
||||
// Add the current promise to the array of promises
|
||||
promises.push(promise);
|
||||
|
||||
@@ -11,9 +11,16 @@ const StudyMetaDataPromises = new Map();
|
||||
* @param {string} StudyInstanceUID The UID of the Study to be retrieved
|
||||
* @param {Object} [filters] - Object containing filters to be applied on retrieve metadata process
|
||||
* @param {string} [filter.seriesInstanceUID] - series instance uid to filter results against
|
||||
* @param {boolean} [separateSeriesInstanceUIDFilters = false] - If true, split filtered metadata calls into multiple calls,
|
||||
* as some DICOMWeb implementations only support single filters.
|
||||
* @returns {Promise} that will be resolved with the metadata or rejected with the error
|
||||
*/
|
||||
export function retrieveStudyMetadata(server, StudyInstanceUID, filters) {
|
||||
export function retrieveStudyMetadata(
|
||||
server,
|
||||
StudyInstanceUID,
|
||||
filters,
|
||||
separateSeriesInstanceUIDFilters = false
|
||||
) {
|
||||
// @TODO: Whenever a study metadata request has failed, its related promise will be rejected once and for all
|
||||
// and further requests for that metadata will always fail. On failure, we probably need to remove the
|
||||
// corresponding promise from the "StudyMetaDataPromises" map...
|
||||
@@ -33,11 +40,29 @@ export function retrieveStudyMetadata(server, StudyInstanceUID, filters) {
|
||||
}
|
||||
|
||||
// Create a promise to handle the data retrieval
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
RetrieveMetadata(server, StudyInstanceUID, filters).then(function(data) {
|
||||
resolve(data);
|
||||
}, reject);
|
||||
});
|
||||
let promise;
|
||||
|
||||
if (
|
||||
filters &&
|
||||
filters.seriesInstanceUID &&
|
||||
separateSeriesInstanceUIDFilters
|
||||
) {
|
||||
promise = __separateSeriesRequestToAggregatePromiseateSeriesRequestToAggregatePromise(
|
||||
server,
|
||||
StudyInstanceUID,
|
||||
filters
|
||||
);
|
||||
} else {
|
||||
promise = RetrieveMetadata(server, StudyInstanceUID, filters);
|
||||
|
||||
/*
|
||||
promise = new Promise((resolve, reject) => {
|
||||
RetrieveMetadata(server, StudyInstanceUID, filters).then(function(data) {
|
||||
resolve(data);
|
||||
}, reject);
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
// Store the promise in cache
|
||||
StudyMetaDataPromises.set(StudyInstanceUID, promise);
|
||||
@@ -45,6 +70,49 @@ export function retrieveStudyMetadata(server, StudyInstanceUID, filters) {
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits up seriesInstanceUID filters to multiple calls for platforms
|
||||
* @param {Object} server Object with server configuration parameters
|
||||
* @param {string} StudyInstanceUID The UID of the Study to be retrieved
|
||||
* @param {Object} filters - Object containing filters to be applied on retrieve metadata process
|
||||
*/
|
||||
function __separateSeriesRequestToAggregatePromiseateSeriesRequestToAggregatePromise(
|
||||
server,
|
||||
StudyInstanceUID,
|
||||
filters
|
||||
) {
|
||||
const { seriesInstanceUID } = filters;
|
||||
const seriesInstanceUIDs = seriesInstanceUID.split(',');
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const promises = [];
|
||||
|
||||
seriesInstanceUIDs.forEach(uid => {
|
||||
const seriesSpecificFilters = Object.assign({}, filters, {
|
||||
seriesInstanceUID: uid,
|
||||
});
|
||||
|
||||
promises.push(
|
||||
RetrieveMetadata(server, StudyInstanceUID, seriesSpecificFilters)
|
||||
);
|
||||
});
|
||||
|
||||
Promise.all(promises).then(results => {
|
||||
const data = results[0];
|
||||
|
||||
let series = [];
|
||||
|
||||
results.forEach(result => {
|
||||
series = [...series, ...result.series];
|
||||
});
|
||||
|
||||
data.series = series;
|
||||
|
||||
resolve(data);
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the cached study metadata retrieval promise to ensure that the browser will
|
||||
* re-retrieve the study metadata when it is next requested
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
const LOW_PRIORITY_MODALITIES = Object.freeze(['SEG', 'KO', 'PR']);
|
||||
const LOW_PRIORITY_MODALITIES = Object.freeze([
|
||||
'SEG',
|
||||
'DOC',
|
||||
'RTSTRUCT',
|
||||
'SR',
|
||||
'KO',
|
||||
'PR',
|
||||
]);
|
||||
|
||||
export default function isLowPriorityModality(Modality) {
|
||||
return LOW_PRIORITY_MODALITIES.includes(Modality);
|
||||
|
||||
@@ -9,7 +9,7 @@ services:
|
||||
max-size: '10m'
|
||||
ports:
|
||||
- '389:389'
|
||||
env_file: ./dcm4che/docker-compose-dcm4che.env
|
||||
env_file: ./docker-compose-dcm4che.env
|
||||
volumes:
|
||||
- ./dcm4che/etc/localtime:/etc/localtime:ro
|
||||
- ./dcm4che/etc/timezone:/etc/timezone:ro
|
||||
@@ -25,7 +25,7 @@ services:
|
||||
max-size: '10m'
|
||||
ports:
|
||||
- '5432:5432'
|
||||
env_file: ./dcm4che/docker-compose-dcm4che.env
|
||||
env_file: ./docker-compose-dcm4che.env
|
||||
volumes:
|
||||
- ./dcm4che/etc/localtime:/etc/localtime:ro
|
||||
- ./dcm4che/etc/timezone:/etc/timezone:ro
|
||||
@@ -44,7 +44,7 @@ services:
|
||||
- '9990:9990'
|
||||
- '11112:11112'
|
||||
- '2575:2575'
|
||||
env_file: ./dcm4che/docker-compose-dcm4che.env
|
||||
env_file: ./docker-compose-dcm4che.env
|
||||
environment:
|
||||
WILDFLY_CHOWN: /opt/wildfly/standalone /storage
|
||||
WILDFLY_WAIT_FOR: ldap:389 db:5432
|
||||
@@ -73,5 +73,4 @@ services:
|
||||
restart: always
|
||||
networks:
|
||||
- dcm4che_default
|
||||
|
||||
networks: dcm4che_default:
|
||||
networks: dcm4che_default:
|
||||
@@ -3,6 +3,54 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [4.5.8](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.5.7...@ohif/viewer@4.5.8) (2020-09-10)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.5.7](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.5.6...@ohif/viewer@4.5.7) (2020-09-09)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.5.6](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.5.5...@ohif/viewer@4.5.6) (2020-09-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.5.5](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.5.4...@ohif/viewer@4.5.5) (2020-09-03)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.5.4](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.5.3...@ohif/viewer@4.5.4) (2020-09-02)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.5.3](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.5.2...@ohif/viewer@4.5.3) (2020-08-28)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.5.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@4.5.1...@ohif/viewer@4.5.2) (2020-08-24)
|
||||
|
||||
**Note:** Version bump only for package @ohif/viewer
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ohif/viewer",
|
||||
"version": "4.5.2",
|
||||
"version": "4.5.8",
|
||||
"description": "OHIF Viewer",
|
||||
"author": "OHIF Contributors",
|
||||
"license": "MIT",
|
||||
@@ -48,16 +48,16 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"@ohif/core": "^2.10.0",
|
||||
"@ohif/extension-cornerstone": "^2.9.2",
|
||||
"@ohif/extension-dicom-html": "^1.2.4",
|
||||
"@ohif/extension-dicom-microscopy": "^0.51.0",
|
||||
"@ohif/extension-dicom-p10-downloader": "^0.1.2",
|
||||
"@ohif/extension-dicom-pdf": "^1.0.3",
|
||||
"@ohif/extension-dicom-rt": "^0.4.0",
|
||||
"@ohif/extension-dicom-segmentation": "^0.4.0",
|
||||
"@ohif/extension-lesion-tracker": "^0.2.0",
|
||||
"@ohif/extension-vtk": "^1.7.1",
|
||||
"@ohif/core": "^2.10.4",
|
||||
"@ohif/extension-cornerstone": "^2.9.4",
|
||||
"@ohif/extension-debugging": "^0.1.4",
|
||||
"@ohif/extension-dicom-html": "^1.2.7",
|
||||
"@ohif/extension-dicom-microscopy": "^0.51.2",
|
||||
"@ohif/extension-dicom-pdf": "^1.0.5",
|
||||
"@ohif/extension-dicom-rt": "^0.4.4",
|
||||
"@ohif/extension-dicom-segmentation": "^0.4.4",
|
||||
"@ohif/extension-lesion-tracker": "^0.2.1",
|
||||
"@ohif/extension-vtk": "^1.7.5",
|
||||
"@ohif/i18n": "^0.52.8",
|
||||
"@ohif/ui": "^1.5.3",
|
||||
"@tanem/react-nprogress": "^1.1.25",
|
||||
@@ -67,7 +67,7 @@
|
||||
"cornerstone-math": "^0.1.8",
|
||||
"cornerstone-tools": "^4.20.1",
|
||||
"cornerstone-wado-image-loader": "^3.1.0",
|
||||
"dcmjs": "0.16.0",
|
||||
"dcmjs": "0.16.3",
|
||||
"dicom-parser": "^1.8.3",
|
||||
"dicomweb-client": "^0.4.4",
|
||||
"hammerjs": "^2.0.8",
|
||||
|
||||
@@ -5,11 +5,14 @@ window.config = function(props) {
|
||||
disableMeasurementPanel: true,
|
||||
routerBasename: '/',
|
||||
enableGoogleCloudAdapter: true,
|
||||
splitQueryParameterCalls: true, // Allows the user to split QIDO SeriesInstanceUID filters into multiple calls, if the server does not support multi-valued query parameters.
|
||||
enableGoogleCloudAdapterUI: false,
|
||||
showStudyList: true,
|
||||
filterQueryParam: true,
|
||||
httpErrorHandler: error => {
|
||||
// This is 429 when rejected from the public idc sandbox too often.
|
||||
console.warn(error.status);
|
||||
|
||||
// Could use services manager here to bring up a dialog/modal if needed.
|
||||
console.warn('test, navigate to https://ohif.org/');
|
||||
window.location = 'https://ohif.org/';
|
||||
|
||||
@@ -2,6 +2,9 @@ import commandsModule from './commandsModule.js';
|
||||
|
||||
export default {
|
||||
id: 'generic-viewer-commands',
|
||||
get version() {
|
||||
return window.version;
|
||||
},
|
||||
getCommandsModule({ commandsManager }) {
|
||||
return commandsModule({ commandsManager });
|
||||
},
|
||||
|
||||
@@ -9,6 +9,9 @@ export default {
|
||||
* Only required property. Should be a unique value across all extensions.
|
||||
*/
|
||||
id: 'measurements-table',
|
||||
get version() {
|
||||
return window.version;
|
||||
},
|
||||
|
||||
preRegistration({ servicesManager, commandsManager, configuration = {} }) {
|
||||
init({ servicesManager, commandsManager, configuration });
|
||||
|
||||
@@ -19,6 +19,7 @@ import UserManagerContext from '../context/UserManagerContext';
|
||||
import AppContext from '../context/AppContext';
|
||||
|
||||
import './Viewer.css';
|
||||
import { finished } from 'stream';
|
||||
|
||||
class Viewer extends Component {
|
||||
static propTypes = {
|
||||
@@ -189,6 +190,7 @@ class Viewer extends Component {
|
||||
currentTimepointId,
|
||||
]);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
thumbnails: _mapStudiesToThumbnails(studies),
|
||||
});
|
||||
@@ -197,6 +199,7 @@ class Viewer extends Component {
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { studies, isStudyLoaded } = this.props;
|
||||
|
||||
if (studies !== prevProps.studies) {
|
||||
this.setState({
|
||||
thumbnails: _mapStudiesToThumbnails(studies),
|
||||
@@ -355,7 +358,6 @@ export default withDialog(Viewer);
|
||||
* a mapping layer?
|
||||
*
|
||||
* TODO[react]:
|
||||
* - Add sorting of display sets
|
||||
* - Add showStackLoadingProgressBar option
|
||||
*
|
||||
* @param {Study[]} studies
|
||||
@@ -369,9 +371,9 @@ const _mapStudiesToThumbnails = function(studies) {
|
||||
const {
|
||||
displaySetInstanceUID,
|
||||
SeriesDescription,
|
||||
SeriesNumber,
|
||||
InstanceNumber,
|
||||
numImageFrames,
|
||||
SeriesNumber,
|
||||
} = displaySet;
|
||||
|
||||
let imageId;
|
||||
@@ -395,9 +397,9 @@ const _mapStudiesToThumbnails = function(studies) {
|
||||
altImageText,
|
||||
displaySetInstanceUID,
|
||||
SeriesDescription,
|
||||
SeriesNumber,
|
||||
InstanceNumber,
|
||||
numImageFrames,
|
||||
SeriesNumber,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -77,7 +77,6 @@ class ViewerLocalFileData extends Component {
|
||||
study.displaySets =
|
||||
study.displaySets ||
|
||||
studyMetadata.createDisplaySets(sopClassHandlerModules);
|
||||
studyMetadata.setDisplaySets(study.displaySets);
|
||||
|
||||
studyMetadata.forEachDisplaySet(displayset => {
|
||||
displayset.localFile = true;
|
||||
|
||||
@@ -47,7 +47,6 @@ const _promoteList = (study, studyMetadata, filters, isFilterStrategy) => {
|
||||
let promoted = false;
|
||||
// Promote only if no filter should be applied
|
||||
if (!isFilterStrategy) {
|
||||
_sortStudyDisplaySet(study, studyMetadata);
|
||||
promoted = _promoteStudyDisplaySet(study, studyMetadata, filters);
|
||||
}
|
||||
|
||||
@@ -89,6 +88,7 @@ const _isQueryParamApplied = (study, filters = {}, isFilterStrategy) => {
|
||||
const { seriesInstanceUID } = filters;
|
||||
let applied = true;
|
||||
// skip in case no filter or no toast manager
|
||||
|
||||
if (!seriesInstanceUID) {
|
||||
return applied;
|
||||
}
|
||||
@@ -193,12 +193,6 @@ const _updateStudyDisplaySets = (study, studyMetadata) => {
|
||||
if (study.derivedDisplaySets) {
|
||||
studyMetadata._addDerivedDisplaySets(study.derivedDisplaySets);
|
||||
}
|
||||
|
||||
studyMetadata.setDisplaySets(study.displaySets);
|
||||
};
|
||||
|
||||
const _sortStudyDisplaySet = (study, studyMetadata) => {
|
||||
studyMetadata.sortDisplaySets(study.displaySets);
|
||||
};
|
||||
|
||||
const _thinStudyData = study => {
|
||||
@@ -347,6 +341,13 @@ function ViewerRetrieveStudyData({
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
appConfig.splitQueryParameterCalls ||
|
||||
appConfig.enableGoogleCloudAdapter
|
||||
) {
|
||||
retrieveParams.push(true); // Seperate SeriesInstanceUID filter calls.
|
||||
}
|
||||
|
||||
cancelableStudiesPromises[studyInstanceUIDs] = makeCancelable(
|
||||
retrieveStudiesMetadata(...retrieveParams)
|
||||
)
|
||||
|
||||
@@ -29,7 +29,9 @@ import OHIFDicomSegmentationExtension from '@ohif/extension-dicom-segmentation';
|
||||
import OHIFDicomRtExtension from '@ohif/extension-dicom-rt';
|
||||
import OHIFDicomMicroscopyExtension from '@ohif/extension-dicom-microscopy';
|
||||
import OHIFDicomPDFExtension from '@ohif/extension-dicom-pdf';
|
||||
import OHIFDicomP10DownloaderExtension from '@ohif/extension-dicom-p10-downloader';
|
||||
// Add this for Debugging purposes:
|
||||
//import OHIFDebuggingExtension from '@ohif/extension-debugging';
|
||||
import { version } from '../package.json';
|
||||
|
||||
/*
|
||||
* Default Settings
|
||||
@@ -38,6 +40,8 @@ let config = {};
|
||||
|
||||
if (window) {
|
||||
config = window.config || {};
|
||||
|
||||
window.version = version;
|
||||
}
|
||||
|
||||
const appProps = {
|
||||
@@ -49,7 +53,7 @@ const appProps = {
|
||||
OHIFDicomPDFExtension,
|
||||
OHIFDicomSegmentationExtension,
|
||||
OHIFDicomRtExtension,
|
||||
OHIFDicomP10DownloaderExtension,
|
||||
//OHIFDebuggingExtension,
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
@@ -194,7 +194,6 @@ const _mapStudiesToNewFormat = studies => {
|
||||
study.displaySets =
|
||||
study.displaySets ||
|
||||
studyMetadata.createDisplaySets(sopClassHandlerModules);
|
||||
studyMetadata.setDisplaySets(study.displaySets);
|
||||
|
||||
studyMetadataManager.add(studyMetadata);
|
||||
uniqueStudyUIDs.add(study.StudyInstanceUID);
|
||||
|
||||
@@ -6539,10 +6539,10 @@ dateformat@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae"
|
||||
integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==
|
||||
|
||||
dcmjs@0.16.0:
|
||||
version "0.16.0"
|
||||
resolved "https://registry.yarnpkg.com/dcmjs/-/dcmjs-0.16.0.tgz#714755d006c3a49cf65ef13e627ff7a23151d35a"
|
||||
integrity sha512-41nY3Isg5i5FoSd1s67aUgUHi146r+fb8JuiRvDsxvm7MEkkdjIzMYMtxjsrVwKRW1xRH+x34nxbV9+8cc7WQw==
|
||||
dcmjs@0.16.3:
|
||||
version "0.16.3"
|
||||
resolved "https://registry.yarnpkg.com/dcmjs/-/dcmjs-0.16.3.tgz#ef44ccc4988a473fd40e1601122c57f2babded11"
|
||||
integrity sha512-pMQGifMpMW9i9ufCdUoEPHrLWnm6ilnpfLqUc+PPoWr7OnuBhiRVRunbv39oTtFDZOLhQ2Ampm7UrkidWAbqiA==
|
||||
dependencies:
|
||||
"@babel/polyfill" "^7.8.3"
|
||||
"@babel/runtime" "^7.8.4"
|
||||
@@ -6782,6 +6782,11 @@ detab@2.0.2, detab@^2.0.0:
|
||||
dependencies:
|
||||
repeat-string "^1.5.4"
|
||||
|
||||
detect-browser@5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/detect-browser/-/detect-browser-5.1.1.tgz#a800db91d3fd60d0861669f5984f1be9ffbe009c"
|
||||
integrity sha512-5n2aWI57qC3kZaK4j2zYsG6L1LrxgLptGCNhMQgdKhVn6cSdcq43pp6xHPfTHG3TYM6myF4tIPWiZtfdVDgb9w==
|
||||
|
||||
detect-file@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7"
|
||||
|
||||
Reference in new issue
Block a user