Compare commits

...
Author SHA1 Message Date
ohif-bot 3b50bd8cc4 chore(version): Update package versions [skip ci] 2023-07-03 14:41:24 +00:00
ohif-bot cd553e29b2 chore(version): version.json [skip ci] 2023-07-03 14:41:21 +00:00
mccle 12dd5a2619 fix(typeError): Address SliceThickness.toFixed TypeError (#3510) 2023-07-03 10:31:46 -04:00
ohif-bot f7a435c2f3 chore(version): Update package versions [skip ci] 2023-06-29 19:56:51 +00:00
ohif-bot 19f1da702f chore(version): version.json [skip ci] 2023-06-29 19:56:47 +00:00
Sofien-Sellami 14974b6973 fix(tmtv): Fusion_Viewport to allow displaySets to be changed (#3502)
Allows drag and drop of new sets of viewports to change them one at a time.
Co-authored-by: Bill Wallace <wayfarer3130@gmail.com>
2023-06-29 15:48:52 -04:00
ohif-bot b4dbe5656b chore(version): Update package versions [skip ci] 2023-06-29 15:24:35 +00:00
ohif-bot c6f7349fd7 chore(version): version.json [skip ci] 2023-06-29 15:24:32 +00:00
Sofien-Sellami abe939bd20 fix(hp): Remove filtered displaySets from parameter from study protocolEngine.findMatch() (#3343)
The changes look good and are running now successfully.
2023-06-29 11:17:08 -04:00
ohif-bot 4d4dd45c23 chore(version): Update package versions [skip ci] 2023-06-29 15:04:16 +00:00
ohif-bot 7671b9faf4 chore(version): version.json [skip ci] 2023-06-29 15:04:12 +00:00
Sofien-SellamiandBill Wallace e653961858 Add support for array input for all constraints in HangingProtocolService validator (#3322)
Co-authored-by: Bill Wallace <wayfarer3130@gmail.com>
2023-06-29 10:56:40 -04:00
ohif-bot aeb61f40da chore(version): Update package versions [skip ci] 2023-06-29 14:51:44 +00:00
ohif-bot b5fb2461dd chore(version): version.json [skip ci] 2023-06-29 14:51:41 +00:00
Joe Boccanfuso 15ad8f29cd fix(date formatting): Show study date as a fallback for an empty series date and show 'No Study Date' as a back up for both (#3483) 2023-06-29 10:43:56 -04:00
ohif-bot fa37593696 chore(version): Update package versions [skip ci] 2023-06-29 14:38:20 +00:00
ohif-bot 08eed4cdf9 chore(version): version.json [skip ci] 2023-06-29 14:38:15 +00:00
Sofien-Sellami b5b7d323c8 fix: allow volume3d viewport for SUV rendering (#3494)
Add checks to include volume3d viewports.
2023-06-29 10:30:41 -04:00
ohif-bot 8a8ae237d2 chore(version): Update package versions [skip ci] 2023-06-27 15:52:28 +00:00
ohif-bot cf1e00fee0 chore(version): version.json [skip ci] 2023-06-27 15:52:24 +00:00
Joe Boccanfuso ef4f28690a fix(DICOM Upload): use correct Accept header for upload (#3498) 2023-06-27 11:44:46 -04:00
ohif-bot c98211ccb5 chore(version): Update package versions [skip ci] 2023-06-23 00:55:55 +00:00
ohif-bot 71342fac8f chore(version): version.json [skip ci] 2023-06-23 00:55:52 +00:00
Alireza 52f419d188 feat(transferSyntax): Add customizable Transfer syntax through datasource config (#3406) 2023-06-22 20:47:50 -04:00
Salim Kanoun ca3a0b56f7 update old configs 2023-06-22 21:26:26 +02:00
Salim Kanoun 1a91f210ab rename 2023-06-20 22:18:14 +02:00
ohif-bot 3503e6e9f4 chore(version): Update package versions [skip ci] 2023-06-20 14:33:52 +00:00
ohif-bot 0c51444066 chore(version): version.json [skip ci] 2023-06-20 14:33:48 +00:00
Alireza 83c453d1f3 chore(version): update cs3d version and add iframe documentation (#3474) 2023-06-20 10:26:05 -04:00
Salim Kanoun fa73a458bd WIP (not tested yet) 2023-06-20 09:34:20 +00:00
ohif-bot 5670a1699d chore(version): Update package versions [skip ci] 2023-06-19 14:44:15 +00:00
ohif-bot c529c0da9c chore(version): version.json [skip ci] 2023-06-19 14:44:10 +00:00
dxlin d51211f6b9 fix(SR export): prevent empty sr description when export (#3173) 2023-06-19 10:35:53 -04:00
ohif-bot 2d8400f9d8 chore(version): Update package versions [skip ci] 2023-06-19 14:10:15 +00:00
ohif-bot e12242a15c chore(version): version.json [skip ci] 2023-06-19 14:10:11 +00:00
Davy 8baf9deaf0 fix(i18n): add more fields i18n (#3262) 2023-06-19 10:02:48 -04:00
ohif-bot a34a0b1a3a chore(version): Update package versions [skip ci] 2023-06-19 13:47:55 +00:00
ohif-bot e1545b67da chore(version): version.json [skip ci] 2023-06-19 13:47:52 +00:00
wangxuan d75305e70d fi(proptype): Invalid prop WhiteLabeling of type object supplied... (#3152) 2023-06-19 09:40:31 -04:00
ohif-bot 242beb7b46 chore(version): Update package versions [skip ci] 2023-06-19 13:39:11 +00:00
ohif-bot c5d4e97115 chore(version): version.json [skip ci] 2023-06-19 13:39:07 +00:00
Igor OctavianoandIgor Octaviano 59cdc4ba9a fix(SEG): Missing SpacingBetweenSlices prevents SEG rendering (#3344)
Co-authored-by: Igor Octaviano <igor.octaviano@radicalimaging.com>
2023-06-19 09:31:04 -04:00
ohif-bot 971c594791 chore(version): Update package versions [skip ci] 2023-06-19 13:22:44 +00:00
ohif-bot c55136f55e chore(version): version.json [skip ci] 2023-06-19 13:22:41 +00:00
Jemshith T K 63e12dd651 docs(ohif-cli): update ohif-cli.md (#3461) 2023-06-19 09:15:30 -04:00
ohif-bot c75c6af22f chore(version): Update package versions [skip ci] 2023-06-19 13:09:00 +00:00
ohif-bot 3c777ba58b chore(version): version.json [skip ci] 2023-06-19 13:08:56 +00:00
dxlin e6f73a2025 fix(RT) : Make ContourImageSequence check under ROIContourSequence->ContourImageSequence optional (#3476) 2023-06-19 09:01:23 -04:00
ohif-bot 652e61a417 chore(version): Update package versions [skip ci] 2023-06-15 17:08:06 +00:00
ohif-bot 48bcdd6b50 chore(version): version.json [skip ci] 2023-06-15 17:08:01 +00:00
Joe Boccanfuso cc73f15c8c fix(Button): redesign of existing Button component for consistency (#3473) 2023-06-15 12:58:21 -04:00
ohif-bot 3dce70e624 chore(version): Update package versions [skip ci] 2023-06-13 12:59:35 +00:00
ohif-bot 1d00893c49 chore(version): version.json [skip ci] 2023-06-13 12:59:31 +00:00
Sofien-Sellami 2890635675 fix(overlay): Fix undefined volumes in customizable viewport overlay (#3435) 2023-06-13 08:51:01 -04:00
ohif-bot a6d967a37e chore(version): Update package versions [skip ci] 2023-06-13 02:41:20 +00:00
ohif-bot f23a4500f5 chore(version): version.json [skip ci] 2023-06-13 02:41:17 +00:00
Alireza cadb559619 docs(readme): correct the docs images (#3470) 2023-06-12 22:33:54 -04:00
Salim Kanoun ad3ef3e8ef Merge branch 'master' into customizable-TS 2023-06-09 21:45:03 +02:00
Salim Kanoun 18c6a47dca Merge branch 'master' into customizable-TS 2023-06-07 13:08:49 +02:00
Salim Kanoun 93f30f06c2 Fix tests 2023-06-06 16:57:19 +02:00
Salim Kanoun 50d2640876 accept both accept header and transfer syntax uid 2023-06-05 22:32:29 +02:00
Salim Kanoun 10e399d7b8 Merge branch 'master' into customizable-TS 2023-06-04 12:43:16 +02:00
Salim Kanoun e4937e7473 update comments 2023-06-01 23:35:13 +02:00
Salim Kanoun 62dd2e78bd null safe 2023-06-01 23:32:16 +02:00
Salim Kanoun c3654b7ddf rework TS 2023-06-01 23:18:54 +02:00
Salim Kanoun de60586b71 fixes 2023-05-29 23:39:06 +02:00
Salim Kanoun f670678cf0 WIP accept header 2023-05-29 23:28:17 +02:00
Salim Kanoun 56633a67d9 Merge branch 'v3-stable' into customizable-TS 2023-05-29 23:26:36 +02:00
Salim Kanoun 8e2535a308 temp disabled of requested ts 2023-05-29 10:38:27 +02:00
Salim Kanoun 6da1734b6e Moving requested ts in createDicomWebApi (WIP) 2023-05-29 10:38:04 +02:00
Salim Kanoun 5ae6e9caf4 restaure config 2023-05-18 20:05:28 +02:00
Salim Kanoun 5e4043d99c add source of type correspondancies 2023-05-18 19:21:46 +02:00
Salim Kanoun 0e991d6bde config documentation 2023-05-18 19:13:36 +02:00
Salim Kanoun 2f63a9db80 init commit making TS customizable in OHIF settings 2023-05-18 19:06:26 +02:00
Salim Kanoun bedc379e97 Merge pull request #4 from OHIF/v3-stable
V3 stable
2023-05-18 17:35:16 +02:00
Salim Kanoun 5b7dc9e9c3 Merge pull request #3 from OHIF/v3-stable
V3 stable
2023-04-05 14:02:23 +02:00
Salim Kanoun 00099c0136 Merge remote-tracking branch 'upstream/v3-stable' into v3-stable 2023-03-12 08:01:06 +01:00
Salim Kanoun 799d648a90 Merge pull request #2 from OHIF/v3-stable
V3 stable
2022-12-28 21:25:05 +01:00
Salim Kanoun d5c347cecd Merge pull request #1 from OHIF/v3-stable
V3 stable
2022-11-24 17:01:48 +01:00
539 changed files with 2385 additions and 10507 deletions

No files matched your search

-9
View File
@@ -73,15 +73,6 @@ module.exports = (env, argv, { SRC_DIR, ENTRY }) => {
children: false,
warnings: true,
},
devServer: {
open: true,
port: 3000,
historyApiFallback: true,
headers: {
'Cross-Origin-Embedder-Policy': 'require-corp',
'Cross-Origin-Opener-Policy': 'same-origin',
},
},
cache: {
type: 'filesystem',
},
+9 -7
View File
@@ -34,13 +34,15 @@ provided by the <a href="https://ohif.org/">Open Health Imaging Foundation (OHIF
<!-- [![All Contributors](https://img.shields.io/badge/all_contributors-10-orange.svg?style=flat-square)](#contributors) -->
<!-- prettier-ignore-end -->
| | |
| :-: | :--- |
| <img src="platform/docs/docs/assets/img/demo-measurements.jpg" alt="Measurement tracking" width="350"/> | Measurement Tracking |
| <img src="platform/docs/docs/assets/img/demo-segmentation.png" alt="Segmentations" width="350"/> | Labelmap Segmentations |
| <img src="platform/docs/docs/assets/img/demo-ptct.png" alt="Hanging Protocols" width="350"/> | Fusion and Custom Hanging protocols |
| <img src="platform/docs/docs/assets/img/demo-microscopy.png" alt="Microscopy" width="350"/> | Slide Microscopy |
| <img src="platform/docs/docs/assets/img/demo-volumeRendering.png" alt="Volume Rendering" width="350"/> | Volume Rendering |
| | | |
| :-: | :--- | :--- |
| <img src="platform/docs/docs/assets/img/demo-measurements.jpg" alt="Measurement tracking" width="350"/> | Measurement Tracking | [Demo](https://viewer.ohif.org/viewer?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125095438.5) |
| <img src="platform/docs/docs/assets/img/demo-segmentation.png" alt="Segmentations" width="350"/> | Labelmap Segmentations | [Demo](https://viewer.ohif.org/viewer?StudyInstanceUIDs=1.3.12.2.1107.5.2.32.35162.30000015050317233592200000046) |
| <img src="platform/docs/docs/assets/img/demo-ptct.png" alt="Hanging Protocols" width="350"/> | Fusion and Custom Hanging protocols | [Demo](https://viewer.ohif.org/tmtv?StudyInstanceUIDs=1.3.6.1.4.1.14519.5.2.1.7009.2403.334240657131972136850343327463) |
| <img src="platform/docs/docs/assets/img/demo-microscopy.png" alt="Microscopy" width="350"/> | Slide Microscopy | [Demo](https://viewer.ohif.org/microscopy?StudyInstanceUIDs=2.25.275741864483510678566144889372061815320) |
| <img src="platform/docs/docs/assets/img/demo-volumeRendering.png" alt="Volume Rendering" width="350"/> | Volume Rendering | [Demo](https://viewer.ohif.org/viewer?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125095438.5&hangingprotocolId=mprAnd3DVolumeViewport) |
## About
+1 -1
View File
@@ -1 +1 @@
14369414cf0193f00243ae5c9f8415cf7079a753
12dd5a26198cf44d32f70f211c471e2d3733ea41
+5 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-cornerstone-dicom-rt",
"version": "3.7.0-beta.2",
"version": "3.7.0-beta.20",
"description": "DICOM RT read workflow",
"author": "OHIF",
"license": "MIT",
@@ -31,10 +31,10 @@
"start": "yarn run dev"
},
"peerDependencies": {
"@ohif/core": "3.7.0-beta.2",
"@ohif/extension-cornerstone": "3.7.0-beta.2",
"@ohif/extension-default": "3.7.0-beta.2",
"@ohif/i18n": "3.7.0-beta.2",
"@ohif/core": "3.7.0-beta.20",
"@ohif/extension-cornerstone": "3.7.0-beta.20",
"@ohif/extension-default": "3.7.0-beta.20",
"@ohif/i18n": "3.7.0-beta.20",
"prop-types": "^15.6.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
@@ -181,12 +181,6 @@ export default async function loadRTStruct(
ContourGeometricType,
} = ContourSequenceArray[c];
const sopInstanceUID = ContourImageSequence.ReferencedSOPInstanceUID;
const imageId = _getImageId(imageIdSopInstanceUidPairs, sopInstanceUID);
if (!imageId) {
continue;
}
let isSupported = false;
const points = [];
@@ -1,3 +1,4 @@
import { ButtonEnums } from '@ohif/ui';
import hydrateRTDisplaySet from './_hydrateRT';
const RESPONSE = {
@@ -38,12 +39,12 @@ function _askHydrate(uiViewportDialogService, viewportIndex) {
const message = 'Do you want to open this Segmentation?';
const actions = [
{
type: 'secondary',
type: ButtonEnums.type.secondary,
text: 'No',
value: RESPONSE.CANCEL,
},
{
type: 'primary',
type: ButtonEnums.type.primary,
text: 'Yes',
value: RESPONSE.HYDRATE_SEG,
},
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-cornerstone-dicom-seg",
"version": "3.7.0-beta.2",
"version": "3.7.0-beta.20",
"description": "DICOM SEG read workflow",
"author": "OHIF",
"license": "MIT",
@@ -31,10 +31,10 @@
"start": "yarn run dev"
},
"peerDependencies": {
"@ohif/core": "3.7.0-beta.2",
"@ohif/extension-cornerstone": "3.7.0-beta.2",
"@ohif/extension-default": "3.7.0-beta.2",
"@ohif/i18n": "3.7.0-beta.2",
"@ohif/core": "3.7.0-beta.20",
"@ohif/extension-cornerstone": "3.7.0-beta.20",
"@ohif/extension-default": "3.7.0-beta.20",
"@ohif/i18n": "3.7.0-beta.20",
"prop-types": "^15.6.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
@@ -1,5 +1,5 @@
import React from 'react';
import { Input, Dialog } from '@ohif/ui';
import { Input, Dialog, ButtonEnums } from '@ohif/ui';
function callInputDialog(uiDialogService, label, callback) {
const dialogId = 'enter-segment-label';
@@ -29,8 +29,8 @@ function callInputDialog(uiDialogService, label, callback) {
noCloseButton: true,
onClose: () => uiDialogService.dismiss({ id: dialogId }),
actions: [
{ id: 'cancel', text: 'Cancel', type: 'primary' },
{ id: 'save', text: 'Confirm', type: 'secondary' },
{ id: 'cancel', text: 'Cancel', type: ButtonEnums.type.secondary },
{ id: 'save', text: 'Confirm', type: ButtonEnums.type.primary },
],
onSubmit: onSubmitHandler,
body: ({ value, setValue }) => {
@@ -1,3 +1,4 @@
import { ButtonEnums } from '@ohif/ui';
import hydrateSEGDisplaySet from './_hydrateSEG';
const RESPONSE = {
@@ -6,11 +7,7 @@ const RESPONSE = {
HYDRATE_SEG: 5,
};
function promptHydrateSEG({
servicesManager,
segDisplaySet,
viewportIndex,
}) {
function promptHydrateSEG({ servicesManager, segDisplaySet, viewportIndex }) {
const { uiViewportDialogService } = servicesManager.services;
return new Promise(async function(resolve, reject) {
@@ -36,12 +33,12 @@ function _askHydrate(uiViewportDialogService, viewportIndex) {
const message = 'Do you want to open this Segmentation?';
const actions = [
{
type: 'secondary',
type: ButtonEnums.type.secondary,
text: 'No',
value: RESPONSE.CANCEL,
},
{
type: 'primary',
type: ButtonEnums.type.primary,
text: 'Yes',
value: RESPONSE.HYDRATE_SEG,
},
@@ -71,7 +71,8 @@ function OHIFCornerstoneSEGViewport(props) {
const referencedDisplaySet = segDisplaySet.getReferenceDisplaySet();
const referencedDisplaySetMetadata = _getReferencedDisplaySetMetadata(
referencedDisplaySet
referencedDisplaySet,
segDisplaySet
);
referencedDisplaySetRef.current = {
@@ -343,10 +344,10 @@ function OHIFCornerstoneSEGViewport(props) {
patientSex: PatientSex || '',
patientAge: PatientAge || '',
MRN: PatientID || '',
thickness: SliceThickness ? `${SliceThickness.toFixed(2)}mm` : '',
thickness: SliceThickness ? `${parseFloat(SliceThickness).toFixed(2)}mm` : '',
spacing:
SpacingBetweenSlices !== undefined
? `${SpacingBetweenSlices.toFixed(2)}mm`
? `${parseFloat(SpacingBetweenSlices).toFixed(2)}mm`
: '',
scanner: ManufacturerModelName || '',
},
@@ -381,20 +382,28 @@ OHIFCornerstoneSEGViewport.defaultProps = {
customProps: {},
};
function _getReferencedDisplaySetMetadata(referencedDisplaySet) {
function _getReferencedDisplaySetMetadata(referencedDisplaySet, segDisplaySet) {
const {
SharedFunctionalGroupsSequence: [SharedFunctionalGroup],
} = segDisplaySet.instance;
const {
PixelMeasuresSequence: [PixelMeasures],
} = SharedFunctionalGroup;
const { SpacingBetweenSlices, SliceThickness } = PixelMeasures;
const image0 = referencedDisplaySet.images[0];
const referencedDisplaySetMetadata = {
PatientID: image0.PatientID,
PatientName: image0.PatientName,
PatientSex: image0.PatientSex,
PatientAge: image0.PatientAge,
SliceThickness: image0.SliceThickness,
SliceThickness: image0.SliceThickness || SliceThickness,
StudyDate: image0.StudyDate,
SeriesDescription: image0.SeriesDescription,
SeriesInstanceUID: image0.SeriesInstanceUID,
SeriesNumber: image0.SeriesNumber,
ManufacturerModelName: image0.ManufacturerModelName,
SpacingBetweenSlices: image0.SpacingBetweenSlices,
SpacingBetweenSlices: image0.SpacingBetweenSlices || SpacingBetweenSlices,
};
return referencedDisplaySetMetadata;
+8 -8
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-cornerstone-dicom-sr",
"version": "3.7.0-beta.2",
"version": "3.7.0-beta.20",
"description": "OHIF extension for an SR Cornerstone Viewport",
"author": "OHIF",
"license": "MIT",
@@ -32,10 +32,10 @@
"test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests"
},
"peerDependencies": {
"@ohif/core": "3.7.0-beta.2",
"@ohif/extension-cornerstone": "3.7.0-beta.2",
"@ohif/extension-measurement-tracking": "3.7.0-beta.2",
"@ohif/ui": "3.7.0-beta.2",
"@ohif/core": "3.7.0-beta.20",
"@ohif/extension-cornerstone": "3.7.0-beta.20",
"@ohif/extension-measurement-tracking": "3.7.0-beta.20",
"@ohif/ui": "3.7.0-beta.20",
"dcmjs": "^0.29.5",
"dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8",
@@ -44,9 +44,9 @@
},
"dependencies": {
"@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^1.1.8",
"@cornerstonejs/core": "^1.1.8",
"@cornerstonejs/tools": "^1.1.8",
"@cornerstonejs/adapters": "^1.2.4",
"@cornerstonejs/core": "^1.2.4",
"@cornerstonejs/tools": "^1.2.4",
"classnames": "^2.3.2"
}
}
+8 -8
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-cornerstone",
"version": "3.7.0-beta.2",
"version": "3.7.0-beta.20",
"description": "OHIF extension for Cornerstone",
"author": "OHIF",
"license": "MIT",
@@ -36,9 +36,9 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
"@cornerstonejs/codec-openjpeg": "^1.2.2",
"@cornerstonejs/codec-openjph": "^2.4.2",
"@cornerstonejs/dicom-image-loader": "^1.1.8",
"@ohif/core": "3.7.0-beta.2",
"@ohif/ui": "3.7.0-beta.2",
"@cornerstonejs/dicom-image-loader": "^1.2.4",
"@ohif/core": "3.7.0-beta.20",
"@ohif/ui": "3.7.0-beta.20",
"dcmjs": "^0.29.6",
"dicom-parser": "^1.8.21",
"hammerjs": "^2.0.8",
@@ -52,10 +52,10 @@
},
"dependencies": {
"@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^1.1.8",
"@cornerstonejs/core": "^1.1.8",
"@cornerstonejs/streaming-image-volume-loader": "^1.1.8",
"@cornerstonejs/tools": "^1.1.8",
"@cornerstonejs/adapters": "^1.2.4",
"@cornerstonejs/core": "^1.2.4",
"@cornerstonejs/streaming-image-volume-loader": "^1.2.4",
"@cornerstonejs/tools": "^1.2.4",
"@kitware/vtk.js": "27.3.1",
"html2canvas": "^1.4.1",
"lodash.debounce": "4.0.8",
@@ -379,7 +379,7 @@ function _getViewportInstance(viewportData, imageIndex) {
if (viewportData.viewportType === Enums.ViewportType.STACK) {
imageId = viewportData.data.imageIds[imageIndex];
} else if (viewportData.viewportType === Enums.ViewportType.ORTHOGRAPHIC) {
const volumes = viewportData.volumes;
const volumes = viewportData.data;
if (volumes && volumes.length == 1) {
const volume = volumes[0];
imageId = volume.imageIds[imageIndex];
@@ -5,7 +5,7 @@ import PropTypes from 'prop-types';
import classNames from 'classnames';
import DicomFileUploader from '../../utils/DicomFileUploader';
import DicomUploadProgress from './DicomUploadProgress';
import { Button } from '@ohif/ui';
import { Button, ButtonEnums } from '@ohif/ui';
import './DicomUpload.css';
type DicomUploadProps = {
@@ -46,12 +46,7 @@ function DicomUpload({
<Dropzone onDrop={onDrop} noDrag>
{({ getRootProps, getInputProps }) => (
<div {...getRootProps()}>
<Button
variant="contained"
color="primary"
disabled={false}
onClick={() => {}}
>
<Button disabled={false} onClick={() => {}}>
{'Add files'}
<input {...getInputProps()} />
</Button>
@@ -62,9 +57,7 @@ function DicomUpload({
{({ getRootProps, getInputProps }) => (
<div {...getRootProps()}>
<Button
variant="contained"
color="primaryDark"
border="primaryActive"
type={ButtonEnums.type.secondary}
disabled={false}
onClick={() => {}}
>
@@ -308,13 +308,7 @@ function DicomUploadProgress({
} ${
dicomFileUploaderArr.length > 1 ? 'files' : 'file'
} completed.`}</span>
<Button
variant="contained"
color="primary"
disabled={false}
className="ml-auto"
onClick={onComplete}
>
<Button disabled={false} className="ml-auto" onClick={onComplete}>
{'Close'}
</Button>
</>
@@ -27,7 +27,6 @@ const DicomUploadProgressItem = memo(
const [failedReason, setFailedReason] = useState('');
const [status, setStatus] = useState(dicomFileUploader.getStatus());
console.info(`${dicomFileUploader.getFileId()}`);
const isComplete = useCallback(() => {
return (
status === UploadStatus.Failed ||
+1 -1
View File
@@ -166,7 +166,7 @@ export default async function init({
prefetch: appConfig?.maxNumRequests?.prefetch || 10,
};
initWADOImageLoader(userAuthenticationService, appConfig);
initWADOImageLoader(userAuthenticationService, appConfig, extensionManager);
/* Measurement Service */
this.measurementServiceSource = connectToolsToMeasurementService(
@@ -5,7 +5,7 @@ import dicomImageLoader, {
webWorkerManager,
} from '@cornerstonejs/dicom-image-loader';
import dicomParser from 'dicom-parser';
import { errorHandler } from '@ohif/core';
import { errorHandler, utils } from '@ohif/core';
const { registerVolumeLoader } = volumeLoader;
@@ -35,7 +35,8 @@ function initWebWorkers(appConfig) {
export default function initWADOImageLoader(
userAuthenticationService,
appConfig
appConfig,
extensionManager
) {
dicomImageLoader.external.cornerstone = cornerstone;
dicomImageLoader.external.dicomParser = dicomParser;
@@ -55,18 +56,18 @@ export default function initWADOImageLoader(
convertFloatPixelDataToInt: false,
},
beforeSend: function(xhr) {
//TODO should be removed in the future and request emitted by DicomWebDataSource
const sourceConfig =
extensionManager.getActiveDataSource()?.[0].getConfig() ?? {};
const headers = userAuthenticationService.getAuthorizationHeader();
const acceptHeader = utils.generateAcceptHeader(
sourceConfig.acceptHeader,
sourceConfig.requestTransferSyntaxUID,
sourceConfig.omitQuotationForMultipartRequest
);
// Request:
// JPEG-LS Lossless (1.2.840.10008.1.2.4.80) if available, otherwise accept
// whatever transfer-syntax the origin server provides.
// For now we use image/jls and image/x-jls because some servers still use the old type
// http://dicom.nema.org/medical/dicom/current/output/html/part18.html
const xhrRequestHeaders = {
Accept: appConfig.omitQuotationForMultipartRequest
? 'multipart/related; type=application/octet-stream'
: 'multipart/related; type="application/octet-stream"',
// 'multipart/related; type="image/x-jls", multipart/related; type="image/jls"; transfer-syntax="1.2.840.10008.1.2.4.80", multipart/related; type="image/x-jls", multipart/related; type="application/octet-stream"; transfer-syntax=*',
Accept: acceptHeader,
};
if (headers) {
@@ -816,45 +816,54 @@ class SegmentationService extends PubSubService {
const segmentsCachedStats = {};
const initializeContour = async rtStructData => {
const { data, id, color, segmentIndex, geometryId } = rtStructData;
const geometry = await geometryLoader.createAndCacheGeometry(geometryId, {
geometryData: {
data,
id,
color,
frameOfReferenceUID: structureSet.frameOfReferenceUID,
// catch error instead of failing to allow loading to continue
try {
const geometry = await geometryLoader.createAndCacheGeometry(
geometryId,
{
geometryData: {
data,
id,
color,
frameOfReferenceUID: structureSet.frameOfReferenceUID,
segmentIndex,
},
type: csEnums.GeometryType.CONTOUR,
}
);
const contourSet = geometry.data;
const centroid = contourSet.getCentroid();
segmentsCachedStats[segmentIndex] = {
center: { world: centroid },
modifiedTime: rtDisplaySet.SeriesDate, // we use the SeriesDate as the modifiedTime since this is the first time we are creating the segmentation
};
segmentation.segments[segmentIndex] = {
label: id,
segmentIndex,
},
type: csEnums.GeometryType.CONTOUR,
});
color,
...SEGMENT_CONSTANT,
};
const contourSet = geometry.data;
const centroid = contourSet.getCentroid();
const numInitialized = Object.keys(segmentsCachedStats).length;
segmentsCachedStats[segmentIndex] = {
center: { world: centroid },
modifiedTime: rtDisplaySet.SeriesDate, // we use the SeriesDate as the modifiedTime since this is the first time we are creating the segmentation
};
// Calculate percentage completed
const percentComplete = Math.round(
(numInitialized / allRTStructData.length) * 100
);
segmentation.segments[segmentIndex] = {
label: id,
segmentIndex,
color,
...SEGMENT_CONSTANT,
};
const numInitialized = Object.keys(segmentsCachedStats).length;
// Calculate percentage completed
const percentComplete = Math.round(
(numInitialized / allRTStructData.length) * 100
);
this._broadcastEvent(EVENTS.SEGMENT_LOADING_COMPLETE, {
percentComplete,
// Note: this is not the geometryIds length since there might be
// some missing ROINumbers
numSegments: allRTStructData.length,
});
this._broadcastEvent(EVENTS.SEGMENT_LOADING_COMPLETE, {
percentComplete,
// Note: this is not the geometryIds length since there might be
// some missing ROINumbers
numSegments: allRTStructData.length,
});
} catch (e) {
console.warn(e);
}
};
const promiseArray = [];
@@ -745,7 +745,7 @@ class CornerstoneViewportService extends PubSubService
const viewport = this.getCornerstoneViewport(viewportId);
const viewportCamera = viewport.getCamera();
if (viewport instanceof VolumeViewport) {
if (viewport instanceof VolumeViewport || viewport instanceof VolumeViewport3D) {
this._setVolumeViewport(viewport, viewportData, viewportInfo).then(() => {
if (keepCamera) {
viewport.setCamera(viewportCamera);
@@ -198,7 +198,9 @@ class ViewportInfo {
// via cornerstoneViewportService
let viewportData = this.getViewportData();
if (viewportData.viewportType === Enums.ViewportType.ORTHOGRAPHIC) {
if (viewportData.viewportType === Enums.ViewportType.ORTHOGRAPHIC ||
viewportData.viewportType === Enums.ViewportType.VOLUME_3D
) {
viewportData = viewportData as VolumeViewportData;
return viewportData.data.some(
({ displaySetInstanceUID: dsUID }) => dsUID === displaySetInstanceUID
@@ -1,5 +1,5 @@
import React from 'react';
import { Input, Dialog } from '@ohif/ui';
import { Input, Dialog, ButtonEnums } from '@ohif/ui';
/**
*
@@ -60,8 +60,8 @@ function callInputDialog(
noCloseButton: true,
onClose: () => uiDialogService.dismiss({ id: dialogId }),
actions: [
{ id: 'cancel', text: 'Cancel', type: 'primary' },
{ id: 'save', text: 'Save', type: 'secondary' },
{ id: 'cancel', text: 'Cancel', type: ButtonEnums.type.secondary },
{ id: 'save', text: 'Save', type: ButtonEnums.type.primary },
],
onSubmit: onSubmitHandler,
body: ({ value, setValue }) => {
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-default",
"version": "3.7.0-beta.2",
"version": "3.7.0-beta.20",
"description": "Common/default features and functionality for basic image viewing",
"author": "OHIF Core Team",
"license": "MIT",
@@ -30,8 +30,8 @@
"start": "yarn run dev"
},
"peerDependencies": {
"@ohif/core": "3.7.0-beta.2",
"@ohif/i18n": "3.7.0-beta.2",
"@ohif/core": "3.7.0-beta.20",
"@ohif/i18n": "3.7.0-beta.20",
"dcmjs": "^0.29.5",
"dicomweb-client": "^0.10.2",
"prop-types": "^15.6.2",
@@ -60,15 +60,42 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) {
supportsReject,
staticWado,
singlepart,
omitQuotationForMultipartRequest,
acceptHeader,
requestTransferSyntaxUID,
} = dicomWebConfig;
const dicomWebConfigCopy = JSON.parse(JSON.stringify(dicomWebConfig));
const getAuthrorizationHeader = () => {
const xhrRequestHeaders = {};
const authHeaders = userAuthenticationService.getAuthorizationHeader();
if (authHeaders && authHeaders.Authorization) {
xhrRequestHeaders.Authorization = authHeaders.Authorization;
}
return xhrRequestHeaders;
};
const generateWadoHeader = () => {
let authorizationHeader = getAuthrorizationHeader();
//Generate accept header depending on config params
let formattedAcceptHeader = utils.generateAcceptHeader(
acceptHeader,
requestTransferSyntaxUID,
omitQuotationForMultipartRequest
);
return {
...authorizationHeader,
Accept: formattedAcceptHeader,
};
};
const qidoConfig = {
url: qidoRoot,
staticWado,
singlepart,
headers: userAuthenticationService.getAuthorizationHeader(),
headers: getAuthrorizationHeader(),
errorInterceptor: errorHandler.getHTTPErrorHandler(),
};
@@ -76,7 +103,7 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) {
url: wadoRoot,
staticWado,
singlepart,
headers: userAuthenticationService.getAuthorizationHeader(),
headers: generateWadoHeader(),
errorInterceptor: errorHandler.getHTTPErrorHandler(),
};
@@ -110,11 +137,7 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) {
studies: {
mapParams: mapParams.bind(),
search: async function(origParams) {
const headers = userAuthenticationService.getAuthorizationHeader();
if (headers) {
qidoDicomWebClient.headers = headers;
}
qidoDicomWebClient.headers = getAuthrorizationHeader();
const { studyInstanceUid, seriesInstanceUid, ...mappedParams } =
mapParams(origParams, {
supportsFuzzyMatching,
@@ -135,11 +158,7 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) {
series: {
// mapParams: mapParams.bind(),
search: async function(studyInstanceUid) {
const headers = userAuthenticationService.getAuthorizationHeader();
if (headers) {
qidoDicomWebClient.headers = headers;
}
qidoDicomWebClient.headers = getAuthrorizationHeader();
const results = await seriesInStudy(
qidoDicomWebClient,
studyInstanceUid
@@ -151,11 +170,7 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) {
},
instances: {
search: (studyInstanceUid, queryParameters) => {
const headers = userAuthenticationService.getAuthorizationHeader();
if (headers) {
qidoDicomWebClient.headers = headers;
}
qidoDicomWebClient.headers = getAuthrorizationHeader();
qidoSearch.call(
undefined,
qidoDicomWebClient,
@@ -182,6 +197,7 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) {
return getDirectURL({ wadoRoot, singlepart }, params);
},
bulkDataURI: async ({ StudyInstanceUID, BulkDataURI }) => {
qidoDicomWebClient.headers = getAuthrorizationHeader();
const options = {
multipart: false,
BulkDataURI,
@@ -200,11 +216,6 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) {
sortFunction,
madeInClient = false,
} = {}) => {
const headers = userAuthenticationService.getAuthorizationHeader();
if (headers) {
wadoDicomWebClient.headers = headers;
}
if (!StudyInstanceUID) {
throw new Error(
'Unable to query for SeriesMetadata without StudyInstanceUID'
@@ -234,17 +245,12 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) {
store: {
dicom: async (dataset, request) => {
const headers = userAuthenticationService.getAuthorizationHeader();
if (headers) {
wadoDicomWebClient.headers = headers;
}
wadoDicomWebClient.headers = getAuthrorizationHeader();
if (dataset instanceof ArrayBuffer) {
const options = {
datasets: [dataset],
request,
};
await wadoDicomWebClient.storeInstances(options);
} else {
const meta = {
@@ -282,7 +288,7 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) {
madeInClient
) => {
const enableStudyLazyLoad = false;
wadoDicomWebClient.headers = generateWadoHeader();
// data is all SOPInstanceUIDs
const data = await retrieveStudyMetadata(
wadoDicomWebClient,
@@ -353,6 +359,7 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) {
madeInClient = false
) => {
const enableStudyLazyLoad = true;
wadoDicomWebClient.headers = generateWadoHeader();
// Get Series
const {
preLoadData: seriesSummaryMetadata,
@@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { Button, ButtonGroup } from '@ohif/ui';
import { LegacyButton, ButtonGroup } from '@ohif/ui';
function ActionButtons({ onExportClick, onCreateReportClick }) {
const { t } = useTranslation('MeasurementTable');
@@ -10,12 +10,16 @@ function ActionButtons({ onExportClick, onCreateReportClick }) {
return (
<React.Fragment>
<ButtonGroup color="black" size="inherit">
<Button className="px-2 py-2 text-base" onClick={onExportClick}>
{/* TODO Revisit design of ButtonGroup later - for now use LegacyButton for its children.*/}
<LegacyButton className="px-2 py-2 text-base" onClick={onExportClick}>
{t('Export CSV')}
</Button>
<Button className="px-2 py-2 text-base" onClick={onCreateReportClick}>
</LegacyButton>
<LegacyButton
className="px-2 py-2 text-base"
onClick={onCreateReportClick}
>
{t('Create Report')}
</Button>
</LegacyButton>
</ButtonGroup>
</React.Fragment>
);
@@ -3,7 +3,7 @@ import classnames from 'classnames';
import { useNavigate } from 'react-router-dom';
import { useAppConfig } from '@state';
import { Button } from '@ohif/ui';
import { Button, ButtonEnums } from '@ohif/ui';
function DataSourceSelector() {
const [appConfig] = useAppConfig();
@@ -33,7 +33,8 @@ function DataSourceSelector() {
<div key={ds.sourceName}>
<h1 className="text-white">{ds.friendlyName}</h1>
<Button
className={classnames('font-bold', 'ml-2')}
type={ButtonEnums.type.primary}
className={classnames('ml-2')}
onClick={() => {
navigate({
pathname: '/',
@@ -1,7 +1,13 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { utils, ServicesManager } from '@ohif/core';
import { MeasurementTable, Dialog, Input, useViewportGrid } from '@ohif/ui';
import {
MeasurementTable,
Dialog,
Input,
useViewportGrid,
ButtonEnums,
} from '@ohif/ui';
import ActionButtons from './ActionButtons';
import debounce from 'lodash.debounce';
@@ -189,9 +195,8 @@ export default function PanelMeasurementTable({
);
},
actions: [
// temp: swap button types until colors are updated
{ id: 'cancel', text: 'Cancel', type: 'primary' },
{ id: 'save', text: 'Save', type: 'secondary' },
{ id: 'cancel', text: 'Cancel', type: ButtonEnums.type.secondary },
{ id: 'save', text: 'Save', type: ButtonEnums.type.primary },
],
onSubmit: onSubmitHandler,
},
@@ -1,6 +1,6 @@
/* eslint-disable react/display-name */
import React from 'react';
import { Dialog, Input, Select } from '@ohif/ui';
import { ButtonEnums, Dialog, Input, Select } from '@ohif/ui';
export const CREATE_REPORT_DIALOG_RESPONSE = {
CANCEL: 0,
@@ -81,8 +81,8 @@ export default function createReportDialogPrompt(
noCloseButton: true,
onClose: _handleClose,
actions: [
{ id: 'cancel', text: 'Cancel', type: 'primary' },
{ id: 'save', text: 'Save', type: 'secondary' },
{ id: 'cancel', text: 'Cancel', type: ButtonEnums.type.secondary },
{ id: 'save', text: 'Save', type: ButtonEnums.type.primary },
],
// TODO: Should be on button press...
onSubmit: _handleFormSubmit,
+5 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-dicom-microscopy",
"version": "3.7.0-beta.2",
"version": "3.7.0-beta.20",
"description": "OHIF extension for DICOM microscopy",
"author": "Bill Wallace, md-prog",
"license": "MIT",
@@ -28,10 +28,10 @@
"start": "yarn run dev"
},
"peerDependencies": {
"@ohif/core": "3.7.0-beta.2",
"@ohif/extension-default": "3.7.0-beta.2",
"@ohif/i18n": "3.7.0-beta.2",
"@ohif/ui": "3.7.0-beta.2",
"@ohif/core": "3.7.0-beta.20",
"@ohif/extension-default": "3.7.0-beta.20",
"@ohif/i18n": "3.7.0-beta.20",
"@ohif/ui": "3.7.0-beta.20",
"prop-types": "^15.6.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
@@ -1,5 +1,5 @@
import React from 'react';
import { Input, Dialog } from '@ohif/ui';
import { Input, Dialog, ButtonEnums } from '@ohif/ui';
/**
*
@@ -14,7 +14,7 @@ export default function callInputDialog({
uiDialogService,
title = 'Annotation',
defaultValue = '',
callback = (value: string, action: string) => {}
callback = (value: string, action: string) => {},
}) {
const dialogId = 'microscopy-input-dialog';
@@ -43,8 +43,8 @@ export default function callInputDialog({
noCloseButton: true,
onClose: () => uiDialogService.dismiss({ id: dialogId }),
actions: [
{ id: 'cancel', text: 'Cancel', type: 'primary' },
{ id: 'save', text: 'Save', type: 'secondary' },
{ id: 'cancel', text: 'Cancel', type: ButtonEnums.type.secondary },
{ id: 'save', text: 'Save', type: ButtonEnums.type.primary },
],
onSubmit: onSubmitHandler,
body: ({ value, setValue }) => {
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-dicom-pdf",
"version": "3.7.0-beta.2",
"version": "3.7.0-beta.20",
"description": "OHIF extension for PDF display",
"author": "OHIF",
"license": "MIT",
@@ -28,8 +28,8 @@
"test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests"
},
"peerDependencies": {
"@ohif/core": "3.7.0-beta.2",
"@ohif/ui": "3.7.0-beta.2",
"@ohif/core": "3.7.0-beta.20",
"@ohif/ui": "3.7.0-beta.20",
"dcmjs": "^0.29.5",
"dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8",
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-dicom-video",
"version": "3.7.0-beta.2",
"version": "3.7.0-beta.20",
"description": "OHIF extension for video display",
"author": "OHIF",
"license": "MIT",
@@ -28,8 +28,8 @@
"test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests"
},
"peerDependencies": {
"@ohif/core": "3.7.0-beta.2",
"@ohif/ui": "3.7.0-beta.2",
"@ohif/core": "3.7.0-beta.20",
"@ohif/ui": "3.7.0-beta.20",
"dcmjs": "^0.29.5",
"dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8",
+7 -7
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-measurement-tracking",
"version": "3.7.0-beta.2",
"version": "3.7.0-beta.20",
"description": "Tracking features and functionality for basic image viewing",
"author": "OHIF Core Team",
"license": "MIT",
@@ -30,11 +30,11 @@
"start": "yarn run dev"
},
"peerDependencies": {
"@cornerstonejs/core": "^1.1.8",
"@cornerstonejs/tools": "^1.1.8",
"@ohif/core": "3.7.0-beta.2",
"@ohif/extension-cornerstone-dicom-sr": "3.7.0-beta.2",
"@ohif/ui": "3.7.0-beta.2",
"@cornerstonejs/core": "^1.2.4",
"@cornerstonejs/tools": "^1.2.4",
"@ohif/core": "3.7.0-beta.20",
"@ohif/extension-cornerstone-dicom-sr": "3.7.0-beta.20",
"@ohif/ui": "3.7.0-beta.20",
"classnames": "^2.3.2",
"dcmjs": "^0.29.5",
"lodash.debounce": "^4.17.21",
@@ -46,7 +46,7 @@
},
"dependencies": {
"@babel/runtime": "^7.20.13",
"@ohif/ui": "3.7.0-beta.2",
"@ohif/ui": "3.7.0-beta.20",
"@xstate/react": "^0.8.1",
"xstate": "^4.10.0"
}
@@ -1,6 +1,6 @@
/* eslint-disable react/display-name */
import React from 'react';
import { Dialog, Input } from '@ohif/ui';
import { ButtonEnums, Dialog, Input } from '@ohif/ui';
import RESPONSE from './PROMPT_RESPONSES';
export default function createReportDialogPrompt(uiDialogService) {
@@ -20,12 +20,19 @@ export default function createReportDialogPrompt(uiDialogService) {
* @param {string} param0.value - value from input field
*/
const _handleFormSubmit = ({ action, value }) => {
uiDialogService.dismiss({ id: dialogId });
switch (action.id) {
case 'save':
resolve({ action: RESPONSE.CREATE_REPORT, value: value.label });
// Only save if description is not blank otherwise ignore
if (value.label && value.label.trim() !== '') {
resolve({
action: RESPONSE.CREATE_REPORT,
value: value.label.trim(),
});
uiDialogService.dismiss({ id: dialogId });
}
break;
case 'cancel':
uiDialogService.dismiss({ id: dialogId });
resolve({ action: RESPONSE.CANCEL, value: undefined });
break;
}
@@ -43,8 +50,8 @@ export default function createReportDialogPrompt(uiDialogService) {
noCloseButton: true,
onClose: _handleClose,
actions: [
{ id: 'cancel', text: 'Cancel', type: 'primary' },
{ id: 'save', text: 'Save', type: 'secondary' },
{ id: 'cancel', text: 'Cancel', type: ButtonEnums.type.secondary },
{ id: 'save', text: 'Save', type: ButtonEnums.type.primary },
],
// TODO: Should be on button press...
onSubmit: _handleFormSubmit,
@@ -55,8 +62,8 @@ export default function createReportDialogPrompt(uiDialogService) {
};
const onKeyPressHandler = event => {
if (event.key === 'Enter') {
uiDialogService.dismiss({ id: dialogId });
resolve({ action: RESPONSE.CREATE_REPORT, value: value.label });
// Trigger form submit
_handleFormSubmit({ action: { id: 'save' }, value });
}
};
return (
@@ -1,3 +1,5 @@
import { ButtonEnums } from '@ohif/ui';
const RESPONSE = {
NO_NEVER: -1,
CANCEL: 0,
@@ -31,19 +33,19 @@ function _askTrackMeasurements(uiViewportDialogService, viewportIndex) {
const actions = [
{
id: 'prompt-begin-tracking-cancel',
type: 'cancel',
type: ButtonEnums.type.secondary,
text: 'No',
value: RESPONSE.CANCEL,
},
{
id: 'prompt-begin-tracking-no-do-not-ask-again',
type: 'secondary',
type: ButtonEnums.type.secondary,
text: 'No, do not ask again',
value: RESPONSE.NO_NEVER,
},
{
id: 'prompt-begin-tracking-yes',
type: 'primary',
type: ButtonEnums.type.primary,
text: 'Yes',
value: RESPONSE.SET_STUDY_AND_SERIES,
},
@@ -1,4 +1,5 @@
import { hydrateStructuredReport } from '@ohif/extension-cornerstone-dicom-sr';
import { ButtonEnums } from '@ohif/ui';
const RESPONSE = {
NO_NEVER: -1,
@@ -61,12 +62,12 @@ function _askTrackMeasurements(uiViewportDialogService, viewportIndex) {
'Do you want to continue tracking measurements for this study?';
const actions = [
{
type: 'secondary',
type: ButtonEnums.type.secondary,
text: 'No',
value: RESPONSE.CANCEL,
},
{
type: 'primary',
type: ButtonEnums.type.primary,
text: 'Yes',
value: RESPONSE.HYDRATE_REPORT,
},
@@ -1,3 +1,5 @@
import { ButtonEnums } from '@ohif/ui';
const RESPONSE = {
NO_NEVER: -1,
CANCEL: 0,
@@ -38,14 +40,18 @@ function _askShouldAddMeasurements(uiViewportDialogService, viewportIndex) {
const message =
'Do you want to add this measurement to the existing report?';
const actions = [
{ type: 'cancel', text: 'Cancel', value: RESPONSE.CANCEL },
{
type: 'secondary',
type: ButtonEnums.type.secondary,
text: 'Cancel',
value: RESPONSE.CANCEL,
},
{
type: ButtonEnums.type.primary,
text: 'Create new report',
value: RESPONSE.CREATE_REPORT,
},
{
type: 'primary',
type: ButtonEnums.type.primary,
text: 'Add to existing report',
value: RESPONSE.ADD_SERIES,
},
@@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { Button, ButtonGroup } from '@ohif/ui';
import { Button, ButtonEnums } from '@ohif/ui';
function ActionButtons({ onExportClick, onCreateReportClick, disabled }) {
const { t } = useTranslation('MeasurementTable');
@@ -10,23 +10,18 @@ function ActionButtons({ onExportClick, onCreateReportClick, disabled }) {
return (
<React.Fragment>
<Button
className="text-base px-2 py-2"
size="initial"
variant={disabled ? 'disabled' : 'outlined'}
color="black"
border="primaryActive"
onClick={onExportClick}
disabled={disabled}
type={ButtonEnums.type.secondary}
size={ButtonEnums.size.small}
>
{t('Export')}
</Button>
<Button
className="ml-2 px-2 text-base"
variant={disabled ? 'disabled' : 'outlined'}
size="initial"
color="black"
border="primaryActive"
className="ml-2"
onClick={onCreateReportClick}
type={ButtonEnums.type.secondary}
size={ButtonEnums.size.small}
disabled={disabled}
>
{t('Create Report')}
@@ -6,6 +6,7 @@ import {
Dialog,
Input,
useViewportGrid,
ButtonEnums,
} from '@ohif/ui';
import { DicomMetadataStore, utils } from '@ohif/core';
import { useDebounce } from '@hooks';
@@ -217,9 +218,8 @@ function PanelMeasurementTableTracking({ servicesManager, extensionManager }) {
);
},
actions: [
// temp: swap button types until colors are updated
{ id: 'cancel', text: 'Cancel', type: 'primary' },
{ id: 'save', text: 'Save', type: 'secondary' },
{ id: 'cancel', text: 'Cancel', type: ButtonEnums.type.secondary },
{ id: 'save', text: 'Save', type: ButtonEnums.type.primary },
],
onSubmit: onSubmitHandler,
},
@@ -1,4 +1,5 @@
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import PropTypes from 'prop-types';
import { utils } from '@ohif/core';
import {
@@ -6,6 +7,7 @@ import {
useImageViewer,
useViewportGrid,
Dialog,
ButtonEnums,
} from '@ohif/ui';
import { useTrackedMeasurements } from '../../getContextModule';
@@ -30,6 +32,8 @@ function PanelStudyBrowserTracking({
uiNotificationService,
} = servicesManager.services;
const { t } = useTranslation('Common');
// Normally you nest the components so the tree isn't so deep, and the data
// doesn't have to have such an intense shape. This works well enough for now.
// Tabs --> Studies --> DisplaySets --> Thumbnails
@@ -133,7 +137,7 @@ function PanelStudyBrowserTracking({
const actuallyMappedStudies = mappedStudies.map(qidoStudy => {
return {
studyInstanceUid: qidoStudy.StudyInstanceUID,
date: formatDate(qidoStudy.StudyDate),
date: formatDate(qidoStudy.StudyDate) || t('NoStudyDate'),
description: qidoStudy.StudyDescription,
modalities: qidoStudy.ModalitiesInStudy,
numInstances: qidoStudy.NumInstances,
@@ -507,11 +511,15 @@ function _mapDisplaySets(
</div>
),
actions: [
{ id: 'cancel', text: 'Cancel', type: 'secondary' },
{
id: 'cancel',
text: 'Cancel',
type: ButtonEnums.type.secondary,
},
{
id: 'yes',
text: 'Yes',
type: 'primary',
type: ButtonEnums.type.primary,
classes: ['reject-yes-button'],
},
],
@@ -22,7 +22,7 @@ function TrackedCornerstoneViewport(props) {
viewportOptions,
} = props;
const { t } = useTranslation('TrackedViewport');
const { t } = useTranslation('Common');
const {
measurementService,
@@ -54,6 +54,7 @@ function TrackedCornerstoneViewport(props) {
PatientAge,
SliceThickness,
SpacingBetweenSlices,
StudyDate,
ManufacturerModelName,
} = displaySet.images[0];
@@ -194,7 +195,8 @@ function TrackedCornerstoneViewport(props) {
getStatusComponent={() => _getStatusComponent(isTracked)}
studyData={{
label: viewportLabel,
studyDate: formatDate(SeriesDate), // TODO: This is series date. Is that ok?
studyDate:
formatDate(SeriesDate) || formatDate(StudyDate) || t('NoStudyDate'),
currentSeries: SeriesNumber, // TODO - switch entire currentSeries to be UID based or actual position based
seriesDescription: SeriesDescription,
patientInformation: {
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-test",
"version": "3.7.0-beta.2",
"version": "3.7.0-beta.20",
"description": "OHIF extension used inside e2e testing",
"author": "OHIF",
"license": "MIT",
@@ -28,8 +28,8 @@
"test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests"
},
"peerDependencies": {
"@ohif/core": "3.7.0-beta.2",
"@ohif/ui": "3.7.0-beta.2",
"@ohif/core": "3.7.0-beta.20",
"@ohif/ui": "3.7.0-beta.20",
"dcmjs": "0.29.4",
"dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8",
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-tmtv",
"version": "3.7.0-beta.2",
"version": "3.7.0-beta.20",
"description": "OHIF extension for Total Metabolic Tumor Volume",
"author": "OHIF",
"license": "MIT",
@@ -28,8 +28,8 @@
"test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests"
},
"peerDependencies": {
"@ohif/core": "3.7.0-beta.2",
"@ohif/ui": "3.7.0-beta.2",
"@ohif/core": "3.7.0-beta.20",
"@ohif/ui": "3.7.0-beta.20",
"dcmjs": "^0.29.5",
"dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8",
+1 -3
View File
@@ -225,9 +225,7 @@ export default function PanelPetSUV({ servicesManager, commandsManager }) {
value={metadata.SeriesTime || ''}
onChange={() => {}}
/>
<Button color="primary" onClick={updateMetadata}>
Reload Data
</Button>
<Button onClick={updateMetadata}>Reload Data</Button>
</div>
</div>
}
@@ -1,5 +1,5 @@
import React from 'react';
import { Button, ButtonGroup } from '@ohif/ui';
import { LegacyButton, ButtonGroup } from '@ohif/ui';
import { useTranslation } from 'react-i18next';
function ExportReports({ segmentations, tmtvValue, config, commandsManager }) {
@@ -9,8 +9,9 @@ function ExportReports({ segmentations, tmtvValue, config, commandsManager }) {
<>
{segmentations?.length ? (
<div className="flex justify-center mt-4 space-x-2">
{/* TODO Revisit design of ButtonGroup later - for now use LegacyButton for its children.*/}
<ButtonGroup color="black" size="inherit">
<Button
<LegacyButton
className="px-2 py-2 text-base"
disabled={tmtvValue === null}
onClick={() => {
@@ -22,10 +23,10 @@ function ExportReports({ segmentations, tmtvValue, config, commandsManager }) {
}}
>
{t('Export CSV')}
</Button>
</LegacyButton>
</ButtonGroup>
<ButtonGroup color="black" size="inherit">
<Button
<LegacyButton
className="px-2 py-2 text-base"
onClick={() => {
commandsManager.runCommand('createTMTVRTReport');
@@ -33,7 +34,7 @@ function ExportReports({ segmentations, tmtvValue, config, commandsManager }) {
disabled={tmtvValue === null}
>
{t('Create RT Report')}
</Button>
</LegacyButton>
</ButtonGroup>
</div>
) : null}
@@ -181,7 +181,6 @@ export default function PanelRoiThresholdSegmentation({
<div className="overflow-x-hidden overflow-y-auto invisible-scrollbar">
<div className="flex mx-4 my-4 mb-4 space-x-4">
<Button
color="primary"
onClick={() => {
setLabelmapLoading(true);
setTimeout(() => {
@@ -194,9 +193,7 @@ export default function PanelRoiThresholdSegmentation({
>
{labelmapLoading ? 'loading ...' : 'New Label'}
</Button>
<Button color="primary" onClick={handleROIThresholding}>
Run
</Button>
<Button onClick={handleROIThresholding}>Run</Button>
</div>
<div
className="flex items-center justify-around h-8 mb-2 border-t outline-none cursor-pointer select-none bg-secondary-dark first:border-0 border-secondary-light"
@@ -1,5 +1,5 @@
import React from 'react';
import { Input, Label, Select, Button, ButtonGroup } from '@ohif/ui';
import { Input, Label, Select, LegacyButton, ButtonGroup } from '@ohif/ui';
import { useTranslation } from 'react-i18next';
export const ROI_STAT = 'roi_stat';
@@ -38,8 +38,9 @@ function ROIThresholdConfiguration({ config, dispatch, runCommand }) {
/>
</div>
<div className="w-1/2">
{/* TODO Revisit design of ButtonGroup later - for now use LegacyButton for its children.*/}
<ButtonGroup>
<Button
<LegacyButton
size="initial"
className="px-2 py-2 text-base text-white"
color="primaryLight"
@@ -47,8 +48,8 @@ function ROIThresholdConfiguration({ config, dispatch, runCommand }) {
onClick={() => runCommand('setStartSliceForROIThresholdTool')}
>
{t('Start')}
</Button>
<Button
</LegacyButton>
<LegacyButton
size="initial"
color="primaryLight"
variant="outlined"
@@ -56,7 +57,7 @@ function ROIThresholdConfiguration({ config, dispatch, runCommand }) {
onClick={() => runCommand('setEndSliceForROIThresholdTool')}
>
{t('End')}
</Button>
</LegacyButton>
</ButtonGroup>
</div>
</div>
@@ -1,5 +1,5 @@
import React from 'react';
import { Input, Dialog } from '@ohif/ui';
import { Input, Dialog, ButtonEnums } from '@ohif/ui';
function segmentationItemEditHandler({ id, servicesManager }) {
const { segmentationService, uiDialogService } = servicesManager.services;
@@ -44,23 +44,20 @@ function segmentationItemEditHandler({ id, servicesManager }) {
}
};
return (
<div className="p-4 bg-primary-dark">
<Input
autoFocus
className="mt-2 bg-black border-primary-main"
type="text"
containerClassName="mr-2"
value={value.label}
onChange={onChangeHandler}
onKeyPress={onKeyPressHandler}
/>
</div>
<Input
autoFocus
className="bg-black border-primary-main"
type="text"
containerClassName="mr-2"
value={value.label}
onChange={onChangeHandler}
onKeyPress={onKeyPressHandler}
/>
);
},
actions: [
// temp: swap button types until colors are updated
{ id: 'cancel', text: 'Cancel', type: 'primary' },
{ id: 'save', text: 'Save', type: 'secondary' },
{ id: 'cancel', text: 'Cancel', type: ButtonEnums.type.secondary },
{ id: 'save', text: 'Save', type: ButtonEnums.type.primary },
],
onSubmit: onSubmitHandler,
},
+1 -1
View File
@@ -1,5 +1,5 @@
{
"version": "3.7.0-beta.2",
"version": "3.7.0-beta.20",
"packages": ["extensions/*", "platform/*", "modes/*"],
"npmClient": "yarn",
"useWorkspaces": true
+7 -7
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/mode-basic-dev-mode",
"version": "3.7.0-beta.2",
"version": "3.7.0-beta.20",
"description": "Basic OHIF Viewer Using Cornerstone",
"author": "OHIF",
"license": "MIT",
@@ -29,12 +29,12 @@
"test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests"
},
"peerDependencies": {
"@ohif/core": "3.7.0-beta.2",
"@ohif/extension-cornerstone": "3.7.0-beta.2",
"@ohif/extension-cornerstone-dicom-sr": "3.7.0-beta.2",
"@ohif/extension-default": "3.7.0-beta.2",
"@ohif/extension-dicom-pdf": "3.7.0-beta.2",
"@ohif/extension-dicom-video": "3.7.0-beta.2"
"@ohif/core": "3.7.0-beta.20",
"@ohif/extension-cornerstone": "3.7.0-beta.20",
"@ohif/extension-cornerstone-dicom-sr": "3.7.0-beta.20",
"@ohif/extension-default": "3.7.0-beta.20",
"@ohif/extension-dicom-pdf": "3.7.0-beta.20",
"@ohif/extension-dicom-video": "3.7.0-beta.20"
},
"dependencies": {
"@babel/runtime": "^7.20.13"
+9 -9
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/mode-test",
"version": "3.7.0-beta.2",
"version": "3.7.0-beta.20",
"description": "Basic mode for testing",
"author": "OHIF",
"license": "MIT",
@@ -32,14 +32,14 @@
"test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests"
},
"peerDependencies": {
"@ohif/core": "3.7.0-beta.2",
"@ohif/extension-cornerstone": "3.7.0-beta.2",
"@ohif/extension-cornerstone-dicom-sr": "3.7.0-beta.2",
"@ohif/extension-default": "3.7.0-beta.2",
"@ohif/extension-dicom-pdf": "3.7.0-beta.2",
"@ohif/extension-dicom-video": "3.7.0-beta.2",
"@ohif/extension-measurement-tracking": "3.7.0-beta.2",
"@ohif/extension-test": "3.7.0-beta.2"
"@ohif/core": "3.7.0-beta.20",
"@ohif/extension-cornerstone": "3.7.0-beta.20",
"@ohif/extension-cornerstone-dicom-sr": "3.7.0-beta.20",
"@ohif/extension-default": "3.7.0-beta.20",
"@ohif/extension-dicom-pdf": "3.7.0-beta.20",
"@ohif/extension-dicom-video": "3.7.0-beta.20",
"@ohif/extension-measurement-tracking": "3.7.0-beta.20",
"@ohif/extension-test": "3.7.0-beta.20"
},
"dependencies": {
"@babel/runtime": "^7.20.13"
+10 -10
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/mode-longitudinal",
"version": "3.7.0-beta.2",
"version": "3.7.0-beta.20",
"description": "Longitudinal Workflow",
"author": "OHIF",
"license": "MIT",
@@ -32,15 +32,15 @@
"test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests"
},
"peerDependencies": {
"@ohif/core": "3.7.0-beta.2",
"@ohif/extension-cornerstone": "3.7.0-beta.2",
"@ohif/extension-cornerstone-dicom-rt": "3.7.0-beta.2",
"@ohif/extension-cornerstone-dicom-seg": "3.7.0-beta.2",
"@ohif/extension-cornerstone-dicom-sr": "3.7.0-beta.2",
"@ohif/extension-default": "3.7.0-beta.2",
"@ohif/extension-dicom-pdf": "3.7.0-beta.2",
"@ohif/extension-dicom-video": "3.7.0-beta.2",
"@ohif/extension-measurement-tracking": "3.7.0-beta.2"
"@ohif/core": "3.7.0-beta.20",
"@ohif/extension-cornerstone": "3.7.0-beta.20",
"@ohif/extension-cornerstone-dicom-rt": "3.7.0-beta.20",
"@ohif/extension-cornerstone-dicom-seg": "3.7.0-beta.20",
"@ohif/extension-cornerstone-dicom-sr": "3.7.0-beta.20",
"@ohif/extension-default": "3.7.0-beta.20",
"@ohif/extension-dicom-pdf": "3.7.0-beta.20",
"@ohif/extension-dicom-video": "3.7.0-beta.20",
"@ohif/extension-measurement-tracking": "3.7.0-beta.20"
},
"dependencies": {
"@babel/runtime": "^7.20.13"
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/mode-microscopy",
"version": "3.7.0-beta.2",
"version": "3.7.0-beta.20",
"description": "OHIF mode for DICOM microscopy",
"author": "OHIF",
"license": "MIT",
@@ -33,8 +33,8 @@
"test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests"
},
"peerDependencies": {
"@ohif/core": "3.7.0-beta.2",
"@ohif/extension-dicom-microscopy": "3.7.0-beta.2"
"@ohif/core": "3.7.0-beta.20",
"@ohif/extension-dicom-microscopy": "3.7.0-beta.20"
},
"dependencies": {
"@babel/runtime": "^7.20.13"
+8 -8
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/mode-tmtv",
"version": "3.7.0-beta.2",
"version": "3.7.0-beta.20",
"description": "Total Metabolic Tumor Volume Workflow",
"author": "OHIF",
"license": "MIT",
@@ -32,13 +32,13 @@
"test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests"
},
"peerDependencies": {
"@ohif/core": "3.7.0-beta.2",
"@ohif/extension-cornerstone": "3.7.0-beta.2",
"@ohif/extension-cornerstone-dicom-sr": "3.7.0-beta.2",
"@ohif/extension-default": "3.7.0-beta.2",
"@ohif/extension-dicom-pdf": "3.7.0-beta.2",
"@ohif/extension-dicom-video": "3.7.0-beta.2",
"@ohif/extension-measurement-tracking": "3.7.0-beta.2"
"@ohif/core": "3.7.0-beta.20",
"@ohif/extension-cornerstone": "3.7.0-beta.20",
"@ohif/extension-cornerstone-dicom-sr": "3.7.0-beta.20",
"@ohif/extension-default": "3.7.0-beta.20",
"@ohif/extension-dicom-pdf": "3.7.0-beta.20",
"@ohif/extension-dicom-video": "3.7.0-beta.20",
"@ohif/extension-measurement-tracking": "3.7.0-beta.20"
},
"dependencies": {
"@babel/runtime": "^7.20.13"
+2 -1
View File
@@ -220,7 +220,8 @@ function modeFactory({ modeConfiguration }) {
return {
id: ohif.layout,
props: {
// leftPanels: [ohif.thumbnailList],
leftPanels: [ohif.thumbnailList],
leftPanelDefaultClosed: true,
rightPanels: [tmtv.ROIThresholdPanel, tmtv.petSUV],
viewports: [
{
+1 -1
View File
@@ -149,7 +149,7 @@
]
},
"resolutions": {
"@cornerstonejs/core": "^1.1.8",
"@cornerstonejs/core": "^1.2.4",
"**/@babel/runtime": "^7.20.13",
"commander": "8.3.0",
"nth-check": "^2.1.1",
@@ -1,5 +1,5 @@
describe('OHIF Context Menu', function () {
beforeEach(function () {
describe('OHIF Context Menu', function() {
beforeEach(function() {
cy.checkStudyRouteInViewer(
'1.2.840.113619.2.5.1762583153.215519.978957063.78'
);
@@ -10,10 +10,10 @@ describe('OHIF Context Menu', function () {
cy.resetViewport().wait(50);
});
it('checks context menu customization', function () {
it('checks context menu customization', function() {
// Add length measurement
cy.addLengthMeasurement();
cy.get('[data-cy="prompt-begin-tracking-yes"]').click();
cy.get('[data-cy="prompt-begin-tracking-yes-btn"]').click();
cy.get('[data-cy="measurement-item"]').click();
const [x1, y1] = [150, 100];
@@ -113,7 +113,7 @@ describe('OHIF Cornerstone Toolbar', () => {
cy.addLengthMeasurement();
cy.get('[data-cy="viewport-notification"]').should('exist');
cy.get('[data-cy="viewport-notification"]').should('be.visible');
cy.get('[data-cy="prompt-begin-tracking-yes"]').click();
cy.get('[data-cy="prompt-begin-tracking-yes-btn"]').click();
//Verify the measurement exists in the table
cy.get('@measurementsPanel').should('be.visible');
@@ -27,7 +27,7 @@ describe('OHIF Measurement Panel', function() {
cy.addLengthMeasurement();
cy.get('[data-cy="viewport-notification"]').should('exist');
cy.get('[data-cy="viewport-notification"]').should('be.visible');
cy.get('[data-cy="prompt-begin-tracking-yes"]').click();
cy.get('[data-cy="prompt-begin-tracking-yes-btn"]').click();
cy.get('[data-cy="measurement-item"]').click();
cy.get('[data-cy="measurement-item"]')
@@ -46,7 +46,7 @@ describe('OHIF Measurement Panel', function() {
it('checks if image would jump when clicked on a measurement item', function() {
// Add length measurement
cy.addLengthMeasurement();
cy.get('[data-cy="prompt-begin-tracking-yes"]').click();
cy.get('[data-cy="prompt-begin-tracking-yes-btn"]').click();
cy.scrollToIndex(13);
+2
View File
@@ -43,3 +43,5 @@
# COMMENT: For sharedArrayBuffer, see https://developer.chrome.com/blog/enabling-shared-array-buffer/
Cross-Origin-Embedder-Policy = "require-corp"
Cross-Origin-Opener-Policy = "same-origin"
# set CORP to cross-origin for anyone who wants to use the viewer in an iframe
Cross-Origin-Resource-Policy = "cross-origin"
+18 -18
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/app",
"version": "3.7.0-beta.2",
"version": "3.7.0-beta.20",
"productVersion": "3.4.0",
"description": "OHIF Viewer",
"author": "OHIF Contributors",
@@ -51,23 +51,23 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
"@cornerstonejs/codec-openjpeg": "^1.2.2",
"@cornerstonejs/codec-openjph": "^2.4.2",
"@cornerstonejs/dicom-image-loader": "^1.1.8",
"@ohif/core": "3.7.0-beta.2",
"@ohif/extension-cornerstone": "3.7.0-beta.2",
"@ohif/extension-cornerstone-dicom-rt": "3.7.0-beta.2",
"@ohif/extension-cornerstone-dicom-seg": "3.7.0-beta.2",
"@ohif/extension-cornerstone-dicom-sr": "3.7.0-beta.2",
"@ohif/extension-default": "3.7.0-beta.2",
"@ohif/extension-dicom-microscopy": "3.7.0-beta.2",
"@ohif/extension-dicom-pdf": "3.7.0-beta.2",
"@ohif/extension-dicom-video": "3.7.0-beta.2",
"@ohif/extension-test": "3.7.0-beta.2",
"@ohif/i18n": "3.7.0-beta.2",
"@ohif/mode-basic-dev-mode": "3.7.0-beta.2",
"@ohif/mode-longitudinal": "3.7.0-beta.2",
"@ohif/mode-microscopy": "3.7.0-beta.2",
"@ohif/mode-test": "3.7.0-beta.2",
"@ohif/ui": "3.7.0-beta.2",
"@cornerstonejs/dicom-image-loader": "^1.2.4",
"@ohif/core": "3.7.0-beta.20",
"@ohif/extension-cornerstone": "3.7.0-beta.20",
"@ohif/extension-cornerstone-dicom-rt": "3.7.0-beta.20",
"@ohif/extension-cornerstone-dicom-seg": "3.7.0-beta.20",
"@ohif/extension-cornerstone-dicom-sr": "3.7.0-beta.20",
"@ohif/extension-default": "3.7.0-beta.20",
"@ohif/extension-dicom-microscopy": "3.7.0-beta.20",
"@ohif/extension-dicom-pdf": "3.7.0-beta.20",
"@ohif/extension-dicom-video": "3.7.0-beta.20",
"@ohif/extension-test": "3.7.0-beta.20",
"@ohif/i18n": "3.7.0-beta.20",
"@ohif/mode-basic-dev-mode": "3.7.0-beta.20",
"@ohif/mode-longitudinal": "3.7.0-beta.20",
"@ohif/mode-microscopy": "3.7.0-beta.20",
"@ohif/mode-test": "3.7.0-beta.20",
"@ohif/ui": "3.7.0-beta.20",
"@types/react": "^17.0.38",
"classnames": "^2.3.2",
"core-js": "^3.16.1",
+2 -1
View File
@@ -4,7 +4,7 @@ window.config = {
modes: [],
showStudyList: true,
// below flag is for performance reasons, but it might not work for all servers
omitQuotationForMultipartRequest: true,
showWarningMessageForCrossOrigin: true,
showCPUFallbackMessage: true,
showLoadingIndicator: true,
@@ -30,6 +30,7 @@ window.config = {
supportsFuzzyMatching: false,
supportsWildcard: false,
staticWado: true,
omitQuotationForMultipartRequest: true,
},
},
{
+1 -2
View File
@@ -11,7 +11,6 @@ window.config = {
// some windows systems have issues with more than 3 web workers
maxNumberOfWebWorkers: 3,
// below flag is for performance reasons, but it might not work for all servers
omitQuotationForMultipartRequest: true,
showWarningMessageForCrossOrigin: true,
showCPUFallbackMessage: true,
showLoadingIndicator: true,
@@ -51,7 +50,6 @@ window.config = {
wadoUriRoot: 'https://d33do7qe4w26qo.cloudfront.net/dicomweb',
qidoRoot: 'https://d33do7qe4w26qo.cloudfront.net/dicomweb',
wadoRoot: 'https://d33do7qe4w26qo.cloudfront.net/dicomweb',
qidoSupportsIncludeField: false,
supportsReject: false,
imageRendering: 'wadors',
@@ -68,6 +66,7 @@ window.config = {
enabled: true,
relativeResolution: 'studies',
},
omitQuotationForMultipartRequest: true,
},
},
{
+1 -1
View File
@@ -4,7 +4,6 @@ window.config = {
extensions: [],
showStudyList: true,
// below flag is for performance reasons, but it might not work for all servers
omitQuotationForMultipartRequest: true,
showWarningMessageForCrossOrigin: true,
strictZSpacingForVolumeViewport: true,
showCPUFallbackMessage: true,
@@ -25,6 +24,7 @@ window.config = {
bulkDataURI: {
enabled: false,
},
omitQuotationForMultipartRequest: true,
},
},
],
@@ -4,7 +4,6 @@ window.config = {
modes: [],
showStudyList: true,
// below flag is for performance reasons, but it might not work for all servers
omitQuotationForMultipartRequest: true,
showWarningMessageForCrossOrigin: true,
showCPUFallbackMessage: true,
showLoadingIndicator: true,
@@ -28,6 +27,7 @@ window.config = {
enableStudyLazyLoad: true,
supportsFuzzyMatching: false,
supportsWildcard: false,
omitQuotationForMultipartRequest: true,
},
},
{
@@ -5,7 +5,6 @@ window.config = {
showStudyList: true,
maxNumberOfWebWorkers: 3,
// below flag is for performance reasons, but it might not work for all servers
omitQuotationForMultipartRequest: true,
showWarningMessageForCrossOrigin: true,
showCPUFallbackMessage: true,
showLoadingIndicator: true,
@@ -31,6 +30,7 @@ window.config = {
supportsWildcard: true,
staticWado: true,
singlepart: 'bulkdata,video,pdf',
omitQuotationForMultipartRequest: true,
},
},
{
@@ -4,7 +4,6 @@ window.config = {
extensions: [],
modes: [],
// below flag is for performance reasons, but it might not work for all servers
omitQuotationForMultipartRequest: true,
showWarningMessageForCrossOrigin: true,
showCPUFallbackMessage: true,
showLoadingIndicator: true,
@@ -23,6 +22,7 @@ window.config = {
qidoSupportsIncludeField: false,
imageRendering: 'wadors',
thumbnailRendering: 'wadors',
omitQuotationForMultipartRequest: true,
},
},
{
@@ -2,7 +2,6 @@ window.config = {
routerBasename: '/',
showStudyList: true,
// below flag is for performance reasons, but it might not work for all servers
omitQuotationForMultipartRequest: true,
showWarningMessageForCrossOrigin: true,
showCPUFallbackMessage: true,
showLoadingIndicator: true,
@@ -18,6 +17,7 @@ window.config = {
qidoSupportsIncludeField: false,
imageRendering: 'wadors',
thumbnailRendering: 'wadors',
omitQuotationForMultipartRequest: true,
// REQUIRED TAG:
// TODO: Remove tag after https://github.com/OHIF/ohif-core/pull/19 is merged and we bump version
// requestOptions: {
@@ -4,7 +4,7 @@ window.config = {
extensions: [],
modes: [],
// below flag is for performance reasons, but it might not work for all servers
omitQuotationForMultipartRequest: true,
showWarningMessageForCrossOrigin: true,
showCPUFallbackMessage: true,
showLoadingIndicator: true,
@@ -23,6 +23,7 @@ window.config = {
qidoSupportsIncludeField: false,
imageRendering: 'wadors',
thumbnailRendering: 'wadors',
omitQuotationForMultipartRequest: true,
},
},
{
+1 -1
View File
@@ -4,7 +4,6 @@ window.config = {
modes: ['@ohif/mode-test'],
showStudyList: true,
// below flag is for performance reasons, but it might not work for all servers
omitQuotationForMultipartRequest: true,
maxNumberOfWebWorkers: 3,
showWarningMessageForCrossOrigin: false,
showCPUFallbackMessage: false,
@@ -31,6 +30,7 @@ window.config = {
supportsFuzzyMatching: false,
supportsWildcard: true,
singlepart: 'video,thumbnail,pdf',
omitQuotationForMultipartRequest: true,
},
},
{
+1 -1
View File
@@ -6,7 +6,6 @@ window.config = {
},
enableGoogleCloudAdapter: false,
// below flag is for performance reasons, but it might not work for all servers
omitQuotationForMultipartRequest: true,
showWarningMessageForCrossOrigin: true,
showCPUFallbackMessage: true,
showLoadingIndicator: true,
@@ -55,6 +54,7 @@ window.config = {
supportsFuzzyMatching: true,
supportsWildcard: false,
dicomUploadEnabled: true,
omitQuotationForMultipartRequest: true,
},
},
{
-1
View File
@@ -2,7 +2,6 @@ window.config = {
routerBasename: '/',
enableGoogleCloudAdapter: true,
// below flag is for performance reasons, but it might not work for all servers
omitQuotationForMultipartRequest: true,
showWarningMessageForCrossOrigin: true,
showCPUFallbackMessage: true,
showLoadingIndicator: true,
+1 -1
View File
@@ -8,7 +8,6 @@ window.config = {
extensions: [],
modes: [],
// below flag is for performance reasons, but it might not work for all servers
omitQuotationForMultipartRequest: true,
showWarningMessageForCrossOrigin: true,
showCPUFallbackMessage: true,
showLoadingIndicator: true,
@@ -39,6 +38,7 @@ window.config = {
bulkDataURI: {
enabled: true,
},
omitQuotationForMultipartRequest: true,
},
},
{
+1 -1
View File
@@ -8,7 +8,6 @@ window.config = {
showStudyList: true,
maxNumberOfWebWorkers: 4,
// below flag is for performance reasons, but it might not work for all servers
omitQuotationForMultipartRequest: true,
showWarningMessageForCrossOrigin: true,
showCPUFallbackMessage: true,
showLoadingIndicator: true,
@@ -33,6 +32,7 @@ window.config = {
supportsWildcard: true,
staticWado: true,
singlepart: 'bulkdata,video,pdf',
omitQuotationForMultipartRequest: true,
},
},
{
+1 -1
View File
@@ -16,7 +16,6 @@ window.config = {
showStudyList: true,
maxNumberOfWebWorkers: 4,
// below flag is for performance reasons, but it might not work for all servers
omitQuotationForMultipartRequest: true,
showWarningMessageForCrossOrigin: true,
showCPUFallbackMessage: true,
showLoadingIndicator: true,
@@ -42,6 +41,7 @@ window.config = {
supportsWildcard: true,
staticWado: true,
singlepart: 'bulkdata,video,pdf',
omitQuotationForMultipartRequest: true,
},
},
{
+1 -1
View File
@@ -4,7 +4,6 @@ window.config = {
modes: [],
showStudyList: true,
// below flag is for performance reasons, but it might not work for all servers
omitQuotationForMultipartRequest: true,
showWarningMessageForCrossOrigin: true,
showCPUFallbackMessage: true,
showLoadingIndicator: true,
@@ -38,6 +37,7 @@ window.config = {
enabled: true,
relativeResolution: 'studies',
},
omitQuotationForMultipartRequest: true,
},
},
{
@@ -2,7 +2,6 @@ window.config = {
routerBasename: '/',
showStudyList: true,
// below flag is for performance reasons, but it might not work for all servers
omitQuotationForMultipartRequest: true,
showWarningMessageForCrossOrigin: true,
showCPUFallbackMessage: true,
showLoadingIndicator: true,
@@ -18,6 +17,7 @@ window.config = {
imageRendering: 'wadors',
thumbnailRendering: 'wadors',
supportsFuzzyMatching: true,
omitQuotationForMultipartRequest: true,
},
],
},
@@ -15,7 +15,7 @@ import { utils, hotkeys, ServicesManager } from '@ohif/core';
import {
Icon,
StudyListExpandedRow,
Button,
LegacyButton,
EmptyStudies,
StudyListTable,
StudyListPagination,
@@ -363,16 +363,17 @@ function WorkList({
''}?${query.toString()}`}
// to={`${mode.routeName}/dicomweb?StudyInstanceUIDs=${studyInstanceUid}`}
>
<Button
{/* TODO revisit the completely rounded style of buttons used for launching a mode from the worklist later - for now use LegacyButton*/}
<LegacyButton
rounded="full"
variant={isValidMode ? 'contained' : 'disabled'}
disabled={!isValidMode}
endIcon={<Icon name="launch-arrow" />} // launch-arrow | launch-info
className={classnames('font-medium ', { 'ml-2': !isFirst })}
className={classnames({ 'ml-2': !isFirst })}
onClick={() => {}}
>
{t(`Modes:${mode.displayName}`)}
</Button>
</LegacyButton>
</Link>
);
})}
+7
View File
@@ -74,9 +74,16 @@ module.exports = {
},
customblue: {
10: '#0A163F',
20: '#0B1F54',
30: '#09286e',
40: '#0E307F',
50: '#0F3A94',
80: '#1454D4',
100: '#c4fdff',
200: '#38daff',
300: '#1D204D',
400: '#90A0C1',
},
customgray: {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/cli",
"version": "3.7.0-beta.2",
"version": "3.7.0-beta.20",
"description": "A CLI to bootstrap new OHIF extension or mode",
"type": "module",
"main": "src/index.js",
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/core",
"version": "3.7.0-beta.2",
"version": "3.7.0-beta.20",
"description": "Generic business logic for web-based medical imaging applications",
"author": "OHIF Core Team",
"license": "MIT",
@@ -35,8 +35,8 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
"@cornerstonejs/codec-openjpeg": "^1.2.2",
"@cornerstonejs/codec-openjph": "^2.4.2",
"@cornerstonejs/dicom-image-loader": "^1.1.8",
"@ohif/ui": "3.7.0-beta.2",
"@cornerstonejs/dicom-image-loader": "^1.2.4",
"@ohif/ui": "3.7.0-beta.20",
"cornerstone-math": "0.1.9",
"dicom-parser": "^1.8.21"
},
@@ -695,15 +695,18 @@ export default class HangingProtocolService extends PubSubService {
) {
viewport.displaySets.forEach(displaySet => {
const { id } = displaySet;
const {
displaySetInstanceUID: oldDisplaySetInstanceUID,
} = displaySetMatchDetails.get(id);
const displaySetMatchDetail = displaySetMatchDetails.get(id);
displaySetInstanceUIDs.push(
const { displaySetInstanceUID: oldDisplaySetInstanceUID } = displaySetMatchDetail;
const displaySetInstanceUID =
displaySet.id === displaySetSelectorId
? newDisplaySetInstanceUID
: oldDisplaySetInstanceUID
);
: oldDisplaySetInstanceUID;
displaySetMatchDetail.displaySetInstanceUID = displaySetInstanceUID;
displaySetInstanceUIDs.push(displaySetInstanceUID);
displaySetOptions.push(displaySet);
});
}
@@ -1389,17 +1392,20 @@ export default class HangingProtocolService extends PubSubService {
this.studies.forEach(study => {
// Skip non-active if active only
if (matchActiveOnly && this.activeStudy !== study) return;
const studyDisplaySets = this.displaySets.filter(
it => it.StudyInstanceUID === study.StudyInstanceUID
it => it.StudyInstanceUID === study.StudyInstanceUID
);
const studyMatchDetails = this.protocolEngine.findMatch(
study,
studyMatchingRules,
{
studies: this.studies,
displaySets: studyDisplaySets,
displaySetMatchDetails: this.displaySetMatchDetails,
}
study,
studyMatchingRules,
{
studies: this.studies,
displaySets: studyDisplaySets,
allDisplaySets: this.displaySets,
displaySetMatchDetails: this.displaySetMatchDetails,
}
);
// Prevent bestMatch from being updated if the matchDetails' required attribute check has failed
@@ -1408,10 +1414,10 @@ export default class HangingProtocolService extends PubSubService {
}
this.debug(
'study',
study.StudyInstanceUID,
'display sets #',
studyDisplaySets.length
'study',
study.StudyInstanceUID,
'display sets #',
studyDisplaySets.length
);
studyDisplaySets.forEach(displaySet => {
const {
@@ -1420,15 +1426,15 @@ export default class HangingProtocolService extends PubSubService {
displaySetInstanceUID,
} = displaySet;
const seriesMatchDetails = this.protocolEngine.findMatch(
displaySet,
seriesMatchingRules,
// Todo: why we have images here since the matching type does not have it
{
studies: this.studies,
instance: displaySet.images?.[0],
displaySetMatchDetails: this.displaySetMatchDetails,
displaySets: studyDisplaySets,
}
displaySet,
seriesMatchingRules,
// Todo: why we have images here since the matching type does not have it
{
studies: this.studies,
instance: displaySet.images?.[0],
displaySetMatchDetails: this.displaySetMatchDetails,
displaySets: studyDisplaySets,
}
);
// Prevent bestMatch from being updated if the matchDetails' required attribute check has failed
@@ -1,69 +1,240 @@
import validate from 'validate.js';
/**
* check if the value is strictly equal to options
*
* @example
* value = ['abc', 'def', 'GHI']
* testValue = 'abc' (Fail)
* = ['abc'] (Fail)
* = ['abc', 'def', 'GHI'] (Valid)
* = ['abc', 'GHI', 'def'] (Fail)
* = ['abc', 'def'] (Fail)
*
* value = 'Attenuation Corrected'
* testValue = 'Attenuation Corrected' (Valid)
* testValue = 'Attenuation' (Fail)
*
* value = ['Attenuation Corrected']
* testValue = ['Attenuation Corrected'] (Valid)
* = 'Attenuation Corrected' (Valid)
* = 'Attenuation' (Fail)
*
* */
validate.validators.equals = function(value, options, key) {
const testValue = getTestValue(options);
const dicomArrayValue = dicomTagToArray(value);
validate.validators.equals = function(value, options, key, attributes) {
const testValue = options?.value ?? options;
if (value !== testValue) {
return key + 'must equal ' + testValue;
// If options is an array, then we need to validate each element in the array
if (Array.isArray(testValue)) {
// If the array has only one element, then we need to compare the value to that element
if (testValue.length !== dicomArrayValue.length) {
return `${key} must be an array of length ${testValue.length}`;
} else {
for (let i = 0; i < testValue.length; i++) {
if (testValue[i] !== dicomArrayValue[i]) {
return `${key} ${testValue[i]} must equal ${dicomArrayValue[i]}`;
}
}
}
} else if (testValue !== dicomArrayValue[0]) {
return `${key} must equal ${testValue}`;
}
};
/**
* check if the value is not equal to options
*
* @example
* value = ['abc', 'def', 'GHI']
* testValue = 'abc' (Valid)
* = ['abc'] (Valid)
* = ['abc', 'def', 'GHI'] (Fail)
* = ['abc', 'GHI', 'def'] (Valid)
* = ['abc', 'def'] (Valid)
*
* value = 'Attenuation Corrected'
* = 'Attenuation Corrected' (Fail)
* = 'Attenuation' (Valid)
*
* value = ['Attenuation Corrected']
* testValue = ['Attenuation Corrected'] (Fail)
* = 'Attenuation Corrected' (Fail)
* = 'Attenuation' (Fail)
* */
validate.validators.doesNotEqual = function(value, options, key) {
const testValue = options?.value ?? options;
if (value === testValue) {
return key + 'cannot equal ' + testValue;
const testValue = getTestValue(options);
const dicomArrayValue = dicomTagToArray(value);
if (Array.isArray(testValue)) {
if (testValue.length === dicomArrayValue.length) {
let score = 0;
testValue.forEach((x, i) => {
if (x === dicomArrayValue[i]) {
score++;
}
});
if (score === testValue.length) {
return `${key} must not equal to ${testValue}`;
}
}
} else if (testValue === dicomArrayValue[0]) {
console.debug(dicomArrayValue, testValue);
return `${key} must not equal to ${testValue}`;
}
};
/**
* Check if a value includes one or more specified options.
*
* @example
* value = ['abc', 'def', 'GHI']
* testValue = ‘abc’ (Fail)
* = ‘dog’ (Fail)
* = [‘abc’] (Valid)
* = [‘att’, ‘abc’] (Valid)
* = ['abc', 'def', 'dog'] (Valid)
* = ['cat', 'dog'] (Fail)
*
* value = ['Attenuation Corrected']
* testValue = 'Attenuation Corrected' (Fail)
* = ['Attenuation Corrected', 'Corrected'] (Valid)
* = ['Attenuation', 'Corrected'] (Fail)
*
* value = 'Attenuation Corrected'
* testValue = ['Attenuation Corrected', 'Corrected'] (Valid)
* = ['Attenuation', 'Corrected'] (Fail)
* */
validate.validators.includes = function(value, options, key) {
const testValue = getTestValue(options);
const dicomArrayValue = dicomTagToArray(value);
if (Array.isArray(testValue)) {
const includedValues = testValue.filter(el => dicomArrayValue.includes(el));
if (includedValues.length === 0) {
return `${key} must include at least one of the following values: ${testValue.join(
', '
)}`;
}
} else return `${key} ${testValue} must be an array`;
// else if (!value.includes(testValue)) {
// return `${key} ${value} must include ${testValue}`;
// }
};
/**
* Check if a value does not include one or more specified options.
*
* @example
* value = ['abc', 'def', 'GHI']
* testValue = ['Corr'] (Valid)
* = 'abc' (Fail)
* = ['abc'] (Fail)
* = [‘att’, ‘cor’] (Valid)
* = ['abc', 'def', 'dog'] (Fail)
*
* value = ['Attenuation Corrected']
* testValue = 'Attenuation Corrected' (Fail)
* = ['Attenuation Corrected', 'Corrected'] (Fail)
* = ['Attenuation', 'Corrected'] (Valid)
*
* value = 'Attenuation Corrected'
* testValue = ['Attenuation Corrected', 'Corrected'] (Fail)
* = ['Attenuation', 'Corrected'] (Valid)
* */
validate.validators.doesNotInclude = function(value, options, key) {
const testValue = getTestValue(options);
const dicomArrayValue = dicomTagToArray(value);
// if (!Array.isArray(value) || value.length === 1) {
// return `${key} is not allowed as a single value`;
// }
if (Array.isArray(testValue)) {
const includedValues = testValue.filter(el => dicomArrayValue.includes(el));
if (includedValues.length > 0) {
return `${key} must not include the following value: ${includedValues}`;
}
} else return `${key} ${testValue} must be an array`;
};
// Ignore case contains.
// options testValue MUST be in lower case already, otherwise it won't match
validate.validators.containsI = function (value, options, key) {
const testValue = options?.value ?? options;
/**
* @example
* value = 'Attenuation Corrected'
* testValue = ‘Corr’ (Valid)
* = ‘corr’ (Valid)
* = [‘att’, ‘cor’] (Valid)
* = [‘Att’, ‘Wall’] (Valid)
* = [‘cat’, ‘dog’] (Fail)
*
* value = ['abc', 'def', 'GHI']
* testValue = 'def' (Valid)
* = 'dog' (Fail)
* = ['gh', 'de'] (Valid)
* = ['cat', 'dog'] (Fail)
*
* */
validate.validators.containsI = function(value, options, key) {
const testValue = getTestValue(options);
if (Array.isArray(value)) {
if (
value.some(
item => !validate.validators.containsI(item.toLowerCase(), options, key)
)
value.some(
item => !validate.validators.containsI(item.toLowerCase(), options, key)
)
) {
return undefined;
}
return `No item of ${value.join(',')} contains ${JSON.stringify(
testValue
testValue
)}`;
}
if (Array.isArray(testValue)) {
if (
testValue.some(
subTest => !validate.validators.containsI(value, subTest, key)
)
testValue.some(
subTest =>
!validate.validators.containsI(value, subTest.toLowerCase(), key)
)
) {
return;
}
return `${key} must contain at least one of ${testValue.join(',')}`;
}
if (
testValue &&
value.indexOf &&
value.toLowerCase().indexOf(testValue) === -1
testValue &&
value.indexOf &&
value.toLowerCase().indexOf(testValue.toLowerCase()) === -1
) {
return key + 'must contain any case of' + testValue;
}
};
/**
* @example
* value = 'Attenuation Corrected'
* testValue = ‘Corr’ (Valid)
* = ‘corr’ (Fail)
* = [‘att’, ‘cor’] (Fail)
* = [‘Att’, ‘Wall’] (Valid)
* = [‘cat’, ‘dog’] (Fail)
*
* value = ['abc', 'def', 'GHI']
* testValue = 'def' (Valid)
* = 'dog' (Fail)
* = ['cat', 'de'] (Valid)
* = ['cat', 'dog'] (Fail)
*
* */
validate.validators.contains = function(value, options, key) {
const testValue = options?.value ?? options;
const testValue = getTestValue(options);
if (Array.isArray(value)) {
if (value.some(item => !validate.validators.contains(item, options, key))) {
return undefined;
}
return `No item of ${value.join(',')} contains ${JSON.stringify(
testValue
testValue
)}`;
}
if (Array.isArray(testValue)) {
if (
testValue.some(
subTest => !validate.validators.contains(value, subTest, key)
)
testValue.some(
subTest => !validate.validators.contains(value, subTest, key)
)
) {
return;
}
@@ -73,50 +244,235 @@ validate.validators.contains = function(value, options, key) {
return key + 'must contain ' + testValue;
}
};
/**
* @example
* value = 'Attenuation Corrected'
* testValue = ‘Corr’ (Fail)
* = ‘corr’ (Valid)
* = [‘att’, ‘cor’] (Valid)
* = [‘Att’, ‘Wall’] (Fail)
* = [‘cat’, ‘dog’] (Valid)
*
* value = ['abc', 'def', 'GHI']
* testValue = 'def' (Fail)
* = 'dog' (Valid)
* = ['cat', 'de'] (Fail)
* = ['cat', 'dog'] (Valid)
*
* */
validate.validators.doesNotContain = function(value, options, key) {
if (options && value.indexOf && value.indexOf(options.value) !== -1) {
return key + 'cannot contain ' + options.value;
const containsResult = validate.validators.contains(value, options, key);
if (!containsResult) {
return `No item of ${value} should contain ${getTestValue(options)}`;
}
};
/**
* @example
* value = 'Attenuation Corrected'
* testValue = ‘Corr’ (Fail)
* = ‘corr’ (Fail)
* = [‘att’, ‘cor’] (Fail)
* = [‘Att’, ‘Wall’] (Fail)
* = [‘cat’, ‘dog’] (Valid)
*
* value = ['abc', 'def', 'GHI']
* testValue = 'DEF' (Fail)
* = 'dog' (Valid)
* = ['cat', 'gh'] (Fail)
* = ['cat', 'dog'] (Valid)
*
* */
validate.validators.doesNotContainI = function(value, options, key) {
const containsResult = validate.validators.containsI(value, options, key);
if (!containsResult) {
return `No item of ${value} should not contain ${getTestValue(options)}`;
}
};
/**
* @example
* value = 'Attenuation Corrected'
* testValue = ‘Corr’ (Fail)
* = ‘Att’ (Fail)
* = ['cat', 'dog', 'Att'] (Valid)
* = [‘cat’, ‘dog’] (Fail)
*
* value = ['abc', 'def', 'GHI']
* testValue = 'deg' (Valid)
* = ['cat', 'GH'] (Valid)
* = ['cat', 'gh'] (Fail)
* = ['cat', 'dog'] (Fail)
*
* */
validate.validators.startsWith = function(value, options, key) {
if (options && value.startsWith && !value.startsWith(options.value)) {
return key + 'must start with ' + options.value;
let testValues = getTestValue(options);
if (typeof testValues === 'string') {
testValues = [testValues];
}
if (typeof value === 'string') {
if (!testValues.some(testValue => value.startsWith(testValue))) {
return key + ' must start with any of these values: ' + testValues;
}
} else if (Array.isArray(value)) {
let valid = false;
for (let i = 0; i < value.length; i++) {
for (let j = 0; j < testValues.length; j++) {
if (value[i].startsWith(testValues[j])) {
valid = true; // set valid flag to true if a match is found
break;
}
}
if (valid) {
return undefined; // break out of loop if a match is found
}
}
if (!valid) {
return key + ' must start with any of these values: ' + testValues; // return undefined if no match is found
}
} else {
return 'Value must be a string or an array';
}
};
/**
* @example
* value = 'Attenuation Corrected'
* testValue = ‘TED’ (Fail)
* = ‘ted’ (Valid)
* = ['cat', 'dog', 'ted'] (Valid)
* = [‘cat’, ‘dog’] (Fail)
*
* value = ['abc', 'def', 'GHI']
* testValue = 'deg' (Valid)
* = ['cat', 'HI'] (Valid)
* = ['cat', 'hi'] (Fail)
* = ['cat', 'dog'] (Fail)
*
* */
validate.validators.endsWith = function(value, options, key) {
if (options && value.endsWith && !value.endsWith(options.value)) {
return key + 'must end with ' + options.value;
let testValues = getTestValue(options);
if (typeof testValues === 'string') {
testValues = [testValues];
}
if (typeof value === 'string') {
if (!testValues.some(testValue => value.endsWith(testValue))) {
return key + ' must end with any of these values: ' + testValues;
}
} else if (Array.isArray(value)) {
let valid = false;
for (let i = 0; i < value.length; i++) {
for (let j = 0; j < testValues.length; j++) {
if (value[i].endsWith(testValues[j])) {
valid = true; // set valid flag to true if a match is found
break;
}
}
if (valid) {
return undefined; // break out of loop if a match is found
}
}
if (!valid) {
return key + ' must end with any of these values: ' + testValues; // return undefined if no match is found
}
} else {
return key + ' must be a string or an array';
}
};
const getTestValue = options => options?.value ?? options;
/**
* @example
* value = 30
* testValue = 20 (Valid)
* = 40 (Fail)
*
* */
validate.validators.greaterThan = function(value, options, key) {
const testValue = getTestValue(options);
if (value === undefined || value === null || value <= testValue) {
return key + 'with value ' + value + ' must be greater than ' + testValue;
if (Array.isArray(value) || typeof value === 'string') {
return `${key} is not allowed as an array or string`;
}
if (Array.isArray(testValue)) {
if (testValue.length === 1) {
if (!(value >= testValue[0])) {
return `${key} must be greater than or equal to ${testValue[0]}, but was ${value}`;
}
} else if (testValue.length > 1) {
return key + ' must be an array of length 1';
}
} else {
if (!(value >= testValue)) {
return key + ' must be greater than ' + testValue;
}
}
};
/**
* @example
* value = 30
* testValue = 40 (Valid)
* = 20 (Fail)
*
* */
validate.validators.lessThan = function(value, options, key) {
const testValue = getTestValue(options);
if (Array.isArray(testValue)) {
if (testValue.length === 1) {
if (!(value <= testValue[0])) {
return `${key} must be less than or equal to ${testValue[0]}, but was ${value}`;
}
} else if (testValue.length > 1) {
return key + ' must be an array of length 1';
}
} else {
if (!(value <= testValue)) {
return key + ' must be less than ' + testValue;
}
}
};
/**
* @example
*
* value = 50
* testValue = [10,60] (Valid)
* = [60, 10] (Valid)
* = [0, 10] (Fail)
* = [70, 80] (Fail)
* = 45 (Fail)
* = [45] (Fail)
*
* */
validate.validators.range = function(value, options, key) {
const testValue = getTestValue(options);
if (value === undefined || value < testValue[0] || value > testValue[1]) {
return (
key +
'with value ' +
value +
' must be between ' +
testValue[0] +
' and ' +
testValue[1]
);
}
if (Array.isArray(testValue) && testValue.length === 2) {
const min = Math.min(testValue[0], testValue[1]);
const max = Math.max(testValue[0], testValue[1]);
if (value === undefined || value < min || value > max) {
return `${key} with value ${value} must be between ${min} and ${max}`;
}
} else return `${key} must be an array of length 2`;
};
validate.validators.notNull = value =>
value === null || value === undefined ? 'Value is null' : undefined;
value === null || value === undefined ? 'Value is null' : undefined;
const getTestValue = options => {
if (Array.isArray(options)) {
return options.map(option => option?.value ?? option);
} else {
return options?.value ?? options;
}
};
const dicomTagToArray = value => {
let dicomArrayValue;
if (!Array.isArray(value)) {
dicomArrayValue = [value];
} else {
dicomArrayValue = [...value];
}
return dicomArrayValue;
};
export default validate;
@@ -1,139 +1,570 @@
import validate from './validator.js';
describe('validator', () => {
const attributeMap = {
str: 'string',
upper: 'UPPER',
num: 3,
nullValue: null,
list: ['abc', 'def', 'GHI'],
};
const attributeMap = {
str: 'Attenuation Corrected',
upper: 'UPPER',
num: 3,
nullValue: null,
list: ['abc', 'def', 'GHI'],
listStr: ['Attenuation Corrected'],
};
const options = {
format: 'grouped',
};
const options = {
format: 'grouped',
};
describe('contains', () => {
it('returns match any list contains', () => {
expect(
validate(attributeMap, { list: { contains: 'a' } }, [options])
).toBeUndefined();
expect(
validate(attributeMap, { str: { contains: 'i' } }, [options])
).toBeUndefined();
expect(
validate(attributeMap, { str: { contains: ['i'] } }, [options])
).toBeUndefined();
expect(
validate(attributeMap, { list: { contains: ['a'] } }, [options])
).toBeUndefined();
expect(
validate(attributeMap, { list: { contains: ['z', 'd'] } }, [options])
).toBeUndefined();
expect(
validate(attributeMap, { list: { contains: ['z'] } }, [options])
).not.toBeUndefined();
describe('equals', () => {
it('returned undefined on strictly equals', () => {
expect(
validate(attributeMap, { listStr: { equals: ['Attenuation'] } }, [
options,
])
).not.toBeUndefined();
expect(
validate(attributeMap, { listStr: { equals: 'Attenuation' } }, [
options,
])
).not.toBeUndefined();
expect(
validate(
attributeMap,
{ listStr: { equals: 'Attenuation Corrected' } },
[options]
)
).toBeUndefined();
expect(
validate(
attributeMap,
{ listStr: { equals: ['Attenuation Corrected'] } },
[options]
)
).toBeUndefined();
expect(
validate(attributeMap, { str: { equals: 'Attenuation Corrected' } }, [
options,
])
).toBeUndefined();
expect(
validate(
attributeMap,
{ str: { equals: { value: 'Attenuation Corrected' } } },
[options]
)
).toBeUndefined();
expect(
validate(attributeMap, { str: { equals: ['Attenuation Corrected'] } }, [
options,
])
).toBeUndefined();
expect(
validate(attributeMap, { str: { equals: ['Attenuation'] } }, [options])
).not.toBeUndefined();
expect(
validate(attributeMap, { list: { equals: ['abc', 'def', 'GHI'] } }, [
options,
])
).toBeUndefined();
expect(
validate(attributeMap, { list: { equals: ['abc', 'GHI', 'def'] } }, [
options,
])
).not.toBeUndefined();
expect(
validate(
attributeMap,
{ list: { equals: { value: ['abc', 'def', 'GHI'] } } },
[options]
)
).toBeUndefined();
});
});
});
describe('doesNotEqual', () => {
it('returns undefined if value does not equal ', () => {
expect(
validate(
attributeMap,
{ listStr: { doesNotEqual: 'Attenuation Corrected' } },
[options]
)
).not.toBeUndefined();
expect(
validate(
attributeMap,
{ listStr: { doesNotEqual: ['Attenuation Corrected'] } },
[options]
)
).not.toBeUndefined();
expect(
validate(attributeMap, { listStr: { doesNotEqual: 'Attenuation' } }, [
options,
])
).toBeUndefined();
describe('containsI', () => {
it('returns match any list contains case insensitive', () => {
expect(
validate(attributeMap, { upper: { containsI: ['bye', 'pre'] } }, [
options,
])
).not.toBeUndefined();
expect(
validate(attributeMap, { list: { containsI: 'hi' } }, [options])
).toBeUndefined();
expect(
validate(attributeMap, { list: { containsI: ['hi', 'bye'] } }, [
options,
])
).toBeUndefined();
expect(
validate(attributeMap, { list: { containsI: ['bye', 'hi'] } }, [
options,
])
).toBeUndefined();
expect(
validate(attributeMap, { list: { containsI: ['ig', 'hi'] } }, [options])
).toBeUndefined();
expect(
validate(attributeMap, { upper: { containsI: ['bye', 'per'] } }, [
options,
])
).toBeUndefined();
expect(
validate(attributeMap, { str: { doesNotEqual: 'Attenuation' } }, [
options,
])
).toBeUndefined();
expect(
validate(
attributeMap,
{ str: { doesNotEqual: { value: 'Attenuation' } } },
[options]
)
).toBeUndefined();
expect(
validate(attributeMap, { str: { doesNotEqual: ['Attenuation'] } }, [
options,
])
).toBeUndefined();
expect(
validate(attributeMap, { str: { doesNotEqual: ['abc', 'def'] } }, [
options,
])
).toBeUndefined();
expect(
validate(
attributeMap,
{ list: { doesNotEqual: ['abc', 'GHI', 'def'] } },
[options]
)
).toBeUndefined();
expect(
validate(
attributeMap,
{ list: { doesNotEqual: ['abc', 'def', 'GHI'] } },
[options]
)
).not.toBeUndefined();
});
});
});
describe('includes', () => {
it('returns match any list includes', () => {
expect(
validate(
attributeMap,
{ listStr: { includes: 'Attenuation Corrected' } },
[options]
)
).not.toBeUndefined();
expect(
validate(
attributeMap,
{ listStr: { includes: ['Attenuation Corrected'] } },
[options]
)
).toBeUndefined();
expect(
validate(
attributeMap,
{ listStr: { includes: ['Attenuation Corrected', 'Corrected'] } },
[options]
)
).toBeUndefined();
expect(
validate(
attributeMap,
{ listStr: { includes: ['Attenuation', 'Corrected'] } },
[options]
)
).not.toBeUndefined();
expect(
validate(
attributeMap,
{ str: { includes: ['Attenuation Corrected', 'Corrected'] } },
[options]
)
).toBeUndefined();
expect(
validate(
attributeMap,
{ str: { includes: ['Attenuation', 'Corrected'] } },
[options]
)
).not.toBeUndefined();
expect(
validate(attributeMap, { list: { includes: ['abc'] } }, [options])
).toBeUndefined();
expect(
validate(attributeMap, { list: { includes: ['GHI', 'HI'] } }, [options])
).toBeUndefined();
expect(
validate(attributeMap, { list: { includes: ['HI', 'bye'] } }, [options])
).not.toBeUndefined();
});
});
describe('doesNotInclude', () => {
it('returns undefined if list does not includes', () => {
expect(
validate(
attributeMap,
{ listStr: { doesNotInclude: 'Attenuation Corrected' } },
[options]
)
).not.toBeUndefined();
expect(
validate(
attributeMap,
{
listStr: { doesNotInclude: ['Attenuation Corrected', 'Corrected'] },
},
[options]
)
).not.toBeUndefined();
expect(
validate(
attributeMap,
{ listStr: { doesNotInclude: ['Attenuation', 'Corrected'] } },
[options]
)
).toBeUndefined();
expect(
validate(
attributeMap,
{ str: { doesNotInclude: ['Attenuation Corrected', 'Corrected'] } },
[options]
)
).not.toBeUndefined();
expect(
validate(
attributeMap,
{ str: { doesNotInclude: ['Attenuation', 'Corrected'] } },
[options]
)
).toBeUndefined();
expect(
validate(attributeMap, { list: { doesNotInclude: ['Corr'] } }, [
options,
])
).toBeUndefined();
expect(
validate(attributeMap, { list: { doesNotInclude: 'abc' } }, [options])
).not.toBeUndefined();
expect(
validate(
attributeMap,
{ list: { doesNotInclude: { value: ['abc'] } } },
[options]
)
).not.toBeUndefined();
expect(
validate(
attributeMap,
{ list: { doesNotInclude: { value: ['att', 'cor'] } } },
[options]
)
).toBeUndefined();
expect(
validate(
attributeMap,
{ list: { doesNotInclude: { value: ['abc', 'def', 'dog'] } } },
[options]
)
).not.toBeUndefined();
});
});
describe('containsI', () => {
it('returns match any list contains case insensitive', () => {
expect(
validate(attributeMap, { upper: { containsI: ['hi', 'pre'] } }, [
options,
])
).not.toBeUndefined();
expect(
validate(attributeMap, { list: { containsI: 'hi' } }, [options])
).toBeUndefined();
expect(
validate(attributeMap, { list: { containsI: ['ghi', 'bye'] } }, [
options,
])
).toBeUndefined();
expect(
validate(attributeMap, { list: { containsI: ['bye', 'hi'] } }, [
options,
])
).toBeUndefined();
expect(
validate(attributeMap, { list: { containsI: ['ig', 'hi'] } }, [options])
).toBeUndefined();
expect(
validate(attributeMap, { upper: { containsI: ['bye', 'per'] } }, [
options,
])
).toBeUndefined();
});
});
describe('contains', () => {
it('returns match any list contains', () => {
expect(
validate(attributeMap, { str: { contains: 'Corr' } }, [options])
).toBeUndefined();
expect(
validate(attributeMap, { str: { contains: { value: 'Corr' } } }, [
options,
])
).toBeUndefined();
expect(
validate(attributeMap, { str: { contains: ['Corr'] } }, [options])
).toBeUndefined();
expect(
validate(attributeMap, { str: { contains: ['corr'] } }, [options])
).not.toBeUndefined();
expect(
validate(attributeMap, { str: { contains: ['Att', 'Wall'] } }, [
options,
])
).toBeUndefined();
expect(
validate(attributeMap, { list: { contains: 'GH' } }, [options])
).toBeUndefined();
expect(
validate(attributeMap, { list: { contains: ['ab'] } }, [options])
).toBeUndefined();
describe('equals', () => {
it('returned undefined on equals', () => {
expect(
validate(attributeMap, { str: { equals: attributeMap.str } }, [options])
).toBeUndefined();
expect(
validate(
attributeMap,
{ num: { equals: { value: attributeMap.num } } },
[options]
)
).toBeUndefined();
expect(
validate(attributeMap, { list: { contains: ['z', 'bc'] } }, [options])
).toBeUndefined();
expect(
validate(attributeMap, { list: { contains: ['z'] } }, [options])
).not.toBeUndefined();
});
});
it('returns error on not equals', () => {
expect(
validate(attributeMap, { str: { equals: 'abc' } }, [options])
).not.toBeUndefined();
expect(
validate(
attributeMap,
{ num: { equals: { value: 1 + attributeMap.num } } },
[options]
)
).not.toBeUndefined();
describe('doesNotContain', () => {
it('returns undefined if string does not contain specified value', () => {
expect(
validate(attributeMap, { str: { doesNotContain: ['att', 'wall'] } }, [
options,
])
).toBeUndefined();
expect(
validate(attributeMap, { str: { doesNotContain: 'Corr' } }, [options])
).not.toBeUndefined();
expect(
validate(attributeMap, { str: { doesNotContain: 'corr' } }, [options])
).toBeUndefined();
expect(
validate(attributeMap, { str: { doesNotContain: { value: 'corr' } } }, [
options,
])
).toBeUndefined();
expect(
validate(attributeMap, { str: { doesNotContain: ['att', 'cor'] } }, [
options,
])
).toBeUndefined();
expect(
validate(attributeMap, { str: { doesNotContain: ['Att', 'cor'] } }, [
options,
])
).not.toBeUndefined();
expect(
validate(attributeMap, { str: { doesNotContain: ['bye', 'hi'] } }, [
options,
])
).toBeUndefined();
expect(
validate(attributeMap, { list: { doesNotContain: ['GHI', 'hi'] } }, [
options,
])
).not.toBeUndefined();
expect(
validate(attributeMap, { list: { doesNotContain: ['hi'] } }, [options])
).toBeUndefined();
});
});
});
describe('greaterThan', () => {
it('returns undefined on greaterThan', () => {
expect(
validate(
attributeMap,
{ num: { greaterThan: { value: attributeMap.num - 1 } } },
[options]
)
).toBeUndefined();
expect(
validate(attributeMap, { num: { greaterThan: attributeMap.num - 1 } }, [
options,
])
).toBeUndefined();
describe('doesNotContainI', () => {
it('returns undefined if string does not contain specified value', () => {
expect(
validate(attributeMap, { str: { doesNotContainI: 'corr' } }, [options])
).not.toBeUndefined();
expect(
validate(attributeMap, { str: { doesNotContainI: 'Corr' } }, [options])
).not.toBeUndefined();
expect(
validate(attributeMap, { str: { doesNotContainI: ['att', 'cor'] } }, [
options,
])
).not.toBeUndefined();
expect(
validate(attributeMap, { str: { doesNotContainI: ['Att', 'wall'] } }, [
options,
])
).not.toBeUndefined();
expect(
validate(attributeMap, { str: { doesNotContainI: ['bye', 'hi'] } }, [
options,
])
).toBeUndefined();
expect(
validate(attributeMap, { list: { doesNotContainI: ['bye', 'ABC'] } }, [
options,
])
).not.toBeUndefined();
expect(
validate(attributeMap, { list: { doesNotContainI: 'bye' } }, [options])
).toBeUndefined();
expect(
validate(attributeMap, { list: { doesNotContainI: ['bye', 'ABC'] } }, [
options,
])
).not.toBeUndefined();
});
});
describe('startsWith', () => {
it('returns undefined if string starts with specified value', () => {
expect(
validate(attributeMap, { str: { startsWith: { value: 'Atte' } } }, [
options,
])
).toBeUndefined();
expect(
validate(attributeMap, { str: { startsWith: 'Att' } }, [options])
).toBeUndefined();
expect(
validate(attributeMap, { str: { startsWith: ['cat', 'dog', 'Att'] } }, [
options,
])
).toBeUndefined();
expect(
validate(attributeMap, { str: { startsWith: ['cat', 'dog'] } }, [
options,
])
).not.toBeUndefined();
expect(
validate(attributeMap, { list: { startsWith: ['GH'] } }, [options])
).toBeUndefined();
expect(
validate(attributeMap, { list: { startsWith: ['de', 'bye'] } }, [
options,
])
).toBeUndefined();
expect(
validate(attributeMap, { list: { startsWith: ['hi', 'bye'] } }, [
options,
])
).not.toBeUndefined();
});
});
describe('endsWith', () => {
it('returns undefined if string ends with specified value', () => {
expect(
validate(attributeMap, { str: { endsWith: 'ted' } }, [options])
).toBeUndefined();
expect(
validate(attributeMap, { str: { endsWith: { value: 'ted' } } }, [
options,
])
).toBeUndefined();
expect(
validate(attributeMap, { str: { endsWith: ['ted'] } }, [options])
).toBeUndefined();
expect(
validate(attributeMap, { str: { endsWith: ['Att'] } }, [options])
).not.toBeUndefined();
expect(
validate(attributeMap, { str: { endsWith: ['cat', 'dog', 'ted'] } }, [
options,
])
).toBeUndefined();
expect(
validate(attributeMap, { list: { endsWith: ['HI'] } }, [options])
).toBeUndefined();
expect(
validate(attributeMap, { list: { endsWith: ['bc', 'dog', 'ted'] } }, [
options,
])
).toBeUndefined();
expect(
validate(attributeMap, { list: { endsWith: ['bye', 'dog'] } }, [
options,
])
).not.toBeUndefined();
});
});
it('returns error on not greater than', () => {
expect(
validate(
attributeMap,
{ num: { greaterThan: { value: attributeMap.num } } },
[options]
)
).not.toBeUndefined();
expect(
validate(attributeMap, { num: { greaterThan: attributeMap.num } }, [
options,
])
).not.toBeUndefined();
describe('greaterThan', () => {
it('returns undefined on greaterThan', () => {
expect(
validate(
attributeMap,
{ num: { greaterThan: { value: attributeMap.num - 1 } } },
[options]
)
).toBeUndefined();
expect(
validate(attributeMap, { num: { greaterThan: attributeMap.num - 1 } }, [
options,
])
).toBeUndefined();
expect(
validate(
attributeMap,
{ num: { greaterThan: [attributeMap.num - 1] } },
[options]
)
).toBeUndefined();
expect(
validate(
attributeMap,
{ num: { greaterThan: [attributeMap.num + 1] } },
[options]
)
).not.toBeUndefined();
});
});
it('returns error on undefined value', () => {
expect(
validate(
attributeMap,
{ numUndefined: { greaterThan: { value: 3 } } },
[options]
)
).not.toBeUndefined();
describe('lessThan', () => {
it('returns undefined on lessThan', () => {
expect(
validate(
attributeMap,
{ num: { lessThan: { value: attributeMap.num + 1 } } },
[options]
)
).toBeUndefined();
expect(
validate(attributeMap, { num: { lessThan: attributeMap.num + 1 } }, [
options,
])
).toBeUndefined();
expect(
validate(attributeMap, { num: { lessThan: [attributeMap.num + 1] } }, [
options,
])
).toBeUndefined();
expect(
validate(attributeMap, { num: { lessThan: [attributeMap.num - 1] } }, [
options,
])
).not.toBeUndefined();
});
});
describe('range', () => {
it('returns undefined if the value is between', () => {
expect(
validate(
attributeMap,
{ num: { range: [attributeMap.num + 1, attributeMap.num - 1] } },
[options]
)
).toBeUndefined();
expect(
validate(attributeMap, { num: { range: [1, 4] } }, [options])
).toBeUndefined();
expect(
validate(attributeMap, { num: { range: [1, 2] } }, [options])
).not.toBeUndefined();
expect(
validate(attributeMap, { num: { range: [4, 5] } }, [options])
).not.toBeUndefined();
expect(
validate(attributeMap, { num: { range: [5] } }, [options])
).not.toBeUndefined();
expect(
validate(attributeMap, { num: { range: 5 } }, [options])
).not.toBeUndefined();
});
});
});
});
+2 -1
View File
@@ -8,5 +8,6 @@ import moment from 'moment';
* @returns {string} Formatted date
*/
export default (date, format = 'DD-MMM-YYYY') => {
return moment(date).format(format);
// moment(undefined) returns the current date, so return the empty string instead
return date ? moment(date).format(format) : '';
};
@@ -0,0 +1,58 @@
const generateAcceptHeader = (
configAcceptHeader = [],
requestTransferSyntaxUID = null,
omitQuotationForMultipartRequest = false
): string[] => {
//if acceptedHeader is passed by config use it as it.
if (configAcceptHeader.length > 0) {
return configAcceptHeader;
}
let acceptHeader = ['multipart/related'];
if (requestTransferSyntaxUID && typeForTS[requestTransferSyntaxUID]) {
const type = typeForTS[requestTransferSyntaxUID];
acceptHeader.push('type=' + type);
acceptHeader.push('transfer-syntax=' + requestTransferSyntaxUID);
} else {
acceptHeader.push('type=application/octet-stream');
}
if (!omitQuotationForMultipartRequest) {
//need to add quotation for each mime type of each accept entry
acceptHeader = acceptHeader.map(mime => {
if (mime.startsWith('type=')) {
const quotedParam = 'type="' + mime.substring(5, mime.length) + '"';
return quotedParam;
}
if (mime.startsWith('transfer-syntax=')) {
const quotedParam =
'transfer-syntax="' + mime.substring(16, mime.length) + '"';
return quotedParam;
} else {
return mime;
}
});
}
return [acceptHeader.join('; ')];
};
const typeForTS = {
'*': 'application/octet-stream',
'1.2.840.10008.1.2.1': 'application/octet-stream',
'1.2.840.10008.1.2': 'application/octet-stream',
'1.2.840.10008.1.2.2': 'application/octet-stream',
'1.2.840.10008.1.2.4.70': 'image/jpeg',
'1.2.840.10008.1.2.4.50': 'image/jpeg',
'1.2.840.10008.1.2.4.51': 'image/dicom+jpeg',
'1.2.840.10008.1.2.4.57': 'image/jpeg',
'1.2.840.10008.1.2.5': 'image/dicom-rle',
'1.2.840.10008.1.2.4.80': 'image/jls',
'1.2.840.10008.1.2.4.81': 'image/jls',
'1.2.840.10008.1.2.4.90': 'image/jp2',
'1.2.840.10008.1.2.4.91': 'image/jp2',
'1.2.840.10008.1.2.4.92': 'image/jpx',
'1.2.840.10008.1.2.4.93': 'image/jpx',
};
export default generateAcceptHeader;
+3
View File
@@ -13,6 +13,7 @@ import Queue from './Queue';
import isDicomUid from './isDicomUid';
import formatDate from './formatDate';
import formatPN from './formatPN';
import generateAcceptHeader from './generateAcceptHeader';
import resolveObjectPath from './resolveObjectPath';
import hierarchicalListUtils from './hierarchicalListUtils';
import progressTrackingUtils from './progressTrackingUtils';
@@ -74,6 +75,7 @@ const utils = {
subscribeToNextViewportGridChange,
splitComma,
getSplitParam,
generateAcceptHeader,
};
export {
@@ -105,6 +107,7 @@ export {
downloadCSVReport,
splitComma,
getSplitParam,
generateAcceptHeader,
};
export default utils;
+1
View File
@@ -24,6 +24,7 @@ describe('Top level exports', () => {
'b64toBlob',
'formatDate',
'formatPN',
'generateAcceptHeader',
'isEqualWithin',
//'loadAndCacheDerivedDisplaySets',
'isDisplaySetReconstructable',
+9 -7
View File
@@ -31,16 +31,18 @@ Key features:
</div>
</div>
<br/>
<br/>
| | |
| :-: | :--- |
| <img src="./assets/img/demo-measurements.jpg" alt="Measurement tracking" width="350"/> | Measurement Tracking |
| <img src="./assets/img/demo-segmentation.png" alt="Segmentations" width="350"/> | Labelmap Segmentations |
| <img src="./assets/img/demo-ptct.png" alt="Hanging Protocols" width="350"/> | Fusion and Custom Hanging protocols |
| <img src="./assets/img/demo-microscopy.png" alt="Microscopy" width="350"/> | Slide Microscopy |
| <img src="./assets/img/demo-volumeRendering.png" alt="Volume Rendering" width="350"/> | Volume Rendering |
| | | |
| :-: | :--- | :--- |
| <img src="https://github.com/OHIF/Viewers/blob/master/platform/docs/docs/assets/img/demo-measurements.jpg?raw=true" alt="Measurement tracking" width="350"/> | Measurement Tracking | [Demo](https://viewer.ohif.org/viewer?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125095438.5) |
| <img src="https://github.com/OHIF/Viewers/blob/master/platform/docs/docs/assets/img/demo-segmentation.png?raw=true" alt="Segmentations" width="350"/> | Labelmap Segmentations | [Demo](https://viewer.ohif.org/viewer?StudyInstanceUIDs=1.3.12.2.1107.5.2.32.35162.30000015050317233592200000046) |
| <img src="https://github.com/OHIF/Viewers/blob/master/platform/docs/docs/assets/img/demo-ptct.png?raw=true" alt="Hanging Protocols" width="350"/> | Fusion and Custom Hanging protocols | [Demo](https://viewer.ohif.org/tmtv?StudyInstanceUIDs=1.3.6.1.4.1.14519.5.2.1.7009.2403.334240657131972136850343327463) |
| <img src="https://github.com/OHIF/Viewers/blob/master/platform/docs/docs/assets/img/demo-microscopy.png?raw=true" alt="Microscopy" width="350"/> | Slide Microscopy | [Demo](https://viewer.ohif.org/microscopy?StudyInstanceUIDs=2.25.275741864483510678566144889372061815320) |
| <img src="https://github.com/OHIF/Viewers/blob/master/platform/docs/docs/assets/img/demo-volumeRendering.png?raw=true" alt="Volume Rendering" width="350"/> | Volume Rendering | [Demo](https://viewer.ohif.org/viewer?StudyInstanceUIDs=1.3.6.1.4.1.25403.345050719074.3824.20170125095438.5&hangingprotocolId=mprAnd3DVolumeViewport) |
Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

@@ -48,6 +48,7 @@ window.config = {
enableStudyLazyLoad: true,
supportsFuzzyMatching: true,
supportsWildcard: true,
omitQuotationForMultipartRequest: true,
},
},
],
@@ -97,6 +98,7 @@ window.config = ({ servicesManager } = {}) => {
enableStudyLazyLoad: true,
supportsFuzzyMatching: true,
supportsWildcard: true,
omitQuotationForMultipartRequest: true,
},
},
],
@@ -112,6 +114,8 @@ Here are a list of some options available:
- `maxNumberOfWebWorkers`: The maximum number of web workers to use for
decoding. Defaults to minimum of `navigator.hardwareConcurrency` and
what is specified by `maxNumberOfWebWorkers`. Some windows machines require smaller values.
- `acceptHeader` : accept header to request specific dicom transfer syntax ex : [ 'multipart/related; type=image/jls; q=1', 'multipart/related; type=application/octet-stream; q=0.1' ]
- `requestTransferSyntaxUID` : Request a specific Tansfer syntax from dicom web server ex: 1.2.840.10008.1.2.4.80 (applyed only if acceptHeader is not set)
- `omitQuotationForMultipartRequest`: Some servers (e.g., .NET) require the `multipart/related` request to be sent without quotation marks. Defaults to `false`. If your server doesn't require this, then setting this flag to `true` might improve performance (by removing the need for preflight requests). Also note that
if auth headers are used, a preflight request is required.
- `maxNumRequests`: The maximum number of requests to allow in parallel. It is an object with keys of `interaction`, `thumbnail`, and `prefetch`. You can specify a specific number for each type.
@@ -1,5 +1,5 @@
---
sidebar_position: 8
sidebar_position: 7
sidebar_label: Authorization
---
@@ -70,7 +70,7 @@ and registered extension's features, are configured using this file.
The easiest way to apply your own configuration is to modify the `default.js`
file. For more advanced configuration options, check out our
[configuration essentials guide](../configuration/index.md).
[configuration essentials guide](../configuration/configurationFiles.md).
## Next Steps
@@ -108,6 +108,10 @@ yarn global add http-server
npx http-server ./dist
```
:::caution
In the video below notice that there is `platform/viewer` which has been renamed to `platform/app` in the latest version
:::
<div style={{padding:"56.25% 0 0 0", position:"relative"}}>
<iframe src="https://player.vimeo.com/video/551957266?badge=0&amp;autopause=0&amp;player_id=0&amp;app_id=58479" frameBorder="0" allow="autoplay; fullscreen; picture-in-picture" allowFullScreen style= {{ position:"absolute",top:0,left:0,width:"100%",height:"100%"}} title="measurement-report"></iframe>
</div>
@@ -127,6 +131,6 @@ web application. For a starting point, check out this repository's own use of:
[circleci]: https://circleci.com/gh/OHIF/Viewers
[circleci-config]: https://github.com/OHIF/Viewers/blob/master/.circleci/config.yml
[netlify]: https://app.netlify.com/sites/ohif/deploys
[netlify.toml]: https://github.com/OHIF/Viewers/blob/master/netlify.toml
[netlify.toml]: https://github.com/OHIF/Viewers/blob/master/platform/app/netlify.toml
[build-deploy-preview.sh]: https://github.com/OHIF/Viewers/blob/master/.netlify/build-deploy-preview.sh
<!-- prettier-ignore-end -->
@@ -1,7 +0,0 @@
---
sidebar_position: 7
---
# Google Cloud Healthcare
> Coming soon - We are working on bringing Google Cloud Healthcare to OHIF-v3
+163
View File
@@ -0,0 +1,163 @@
---
sidebar_position: 7
sidebar_label: iframe
---
# iframe
With the transition to more advanced visualization, loading, and rendering techniques using WebWorkers, WASM, and WebGL, the script tag usage of the OHIF viewer v3 has been deprecated.
An alternative option for script tag usage is to employ an iframe. You can utilize the iframe element to load the OHIF viewer and establish communication with it using the postMessage API if needed.
We recommend utilizing modern development practices and incorporating OHIF viewer within your application using a more modular and integrated approach, such as leveraging bundlers, other UI
components, and frameworks.
## Static Build
You can use the iframe element to load the OHIF viewer as a child element of your application if you need the
viewer to be embedded within your application. The iframe element can be used as follows (use your own custom styles)
```html
<iframe src="./path-to-ohif-build" style="width: 100%; height: 500px; border: none"/>
```
The important thing to note here is that the iframe element is loading the OHIF viewer from the `./path-to-ohif-build`. This path can be
named anything you want, but it should be the path to the OHIF viewer build directory. The build directory is the directory that
contains the `index.html` file (See [build for production](./build-for-production.md) for more information).
It is also required that the PUBLIC_URL environment variable is set to the same path. For example, if the iframe is
`<iframe src="./ohif" />` (which means there is a `ohif` folder containing the build in your main app), then you need to:
1. use a config (e.g. config/myConfig.js) file that is using the `routerBasename` of `/ohif` (note the one / - it is not /ohif/).
2. build the viewer with `PUBLIC_URL=./ohif/ APP_CONFIG=config/myConfig.js yarn build` (note the one / - it is not /ohif/).
:::tip
Check to make sure the `app-config.js` in the build is reflecting the correct routerBasename.
:::
:::tip
The PUBLIC_URL tells the application where to find the static assets and the routerBasename will tell the application how to handle the rouets
:::
### Try it locally
Download the index.html and the build (against the /ohif/ path) from [here](https://ohif-assets.s3.us-east-2.amazonaws.com/iframe-basic/Archive.zip)
Then run the
```
npx http-server unzipped-folder
```
You should be able to see
![Alt text](../assets/img/iframe-basic.png)
:::info
Notice the Cross Origin Isolation Warning. It is present to indicate that OHIF cannot render volumes because the volume viewports
use SharedArrayBuffer which is not allowed for non cross origin isolated apps. You can read more about Cross Origin Isolation here
https://web.dev/coop-coep/ or follow the steps below to enable it.
:::
### Fixing the Cross Origin Isolation Warning to enable volume rendering
For that we need a more sophisticated setup, since we need to add the Cross Origin Embedder Policy and Cross Origin Opener Policy headers
to make the parent app cross origin isolated. For that we can use an Express server. (Note: you can use any other method
to add the headers, this is just one of the methods)
Download files from [here](https://ohif-assets.s3.us-east-2.amazonaws.com/iframe-express/Archive.zip)
```js
const express = require("express")
const app = express()
app.use((req, res, next) => {
res.setHeader("Cross-Origin-Opener-Policy", "same-origin")
res.setHeader("Cross-Origin-Embedder-Policy", "require-corp")
next()
})
app.use(express.static("public")) // 'public' should be the folder with the static OHIF build files
app.listen(8080, () => console.log("Listening on port 8080!"))
```
![Alt text](../assets/img/iframe-headers.png)
:::tip
if you are using webpack with react you can set
```js
devServer: {
headers: {
"Cross-Origin-Opener-Policy": "same-origin",
"Cross-Origin-Embedder-Policy": "require-corp"
}
}
```
:::
:::tip
If you are using Angular, you should modify the `angular.json` file to add the headers
```js
"serve": {
//
"configurations": {
//
"development": {
//
"headers": {
"Cross-Origin-Opener-Policy": "same-origin",
"Cross-Origin-Embedder-Policy": "require-corp"
}
}
},
//
},
```
:::
## Development Server
If you are not using the static build, you can use the iframe to load the viewer from the local development server. For example, if you are running the viewer locally on port 3000, you can use the following iframe element to load the viewer:
```html
// e.g., app running on 3001 and iframe loading the viewer from 3000
<iframe src="http://localhost:3000" style="width: 100%; height: 500px; border: none"/>
```
Notice that not including the static build removes the need for
the PUBLIC_URL and the routerBasename. However, the Cross Origin Resource Policy (CORP)
headers must be set because the viewer will be loaded from a different port. You can read
more about CORP [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cross-Origin_Resource_Policy).
Basically in the development server that is serving the viewer, add the following headers:
```js
// use this if the embedding app is running on the same site as OHIF
// (e.g. parent/embedding app is http://localhost:3001 and OHIF is http://localhost:3000)
"Cross-Origin-Resource-Policy": "same-site"
or
// use this if the embedding app is a completely different origin than OHIF (e.g.
// parent/embedding app is http://192.168.1.2 and OHIF is http://localhost:3000)
"Cross-Origin-Resource-Policy": "cross-origin"
```
:::info
You can't set the `Cross-Origin-Resource-Policy` to `same-origin` since the viewer is loaded from a different port.
:::
:::tip
If you are using webpack to serve the viewer it would be
```js
devServer: {
headers: {
"Cross-Origin-Resource-Policy": "same-site" // cross-origin is also valid
}
}
```
+15 -18
View File
@@ -5,29 +5,21 @@ sidebar_label: Overview
# Deployment
The OHIF Viewer can be embedded in other web applications via it's [packaged
script source][viewer-npm], or served up as a stand-alone PWA ([progressive web
application][pwa-url]) by building and hosting a collection of static assets. In
The OHIF Viewer can be served as a stand-alone PWA ([progressive web
application][pwa-url]) by building and hosting a collection of static assets or be embedded in other web applications via an iframe if needed. In
either case, you will need to configure your instance of the Viewer so that it
can connect to your data source (the database or PACS that provides the data
your Viewer will display).
## Overview
:::tip
Our goal is to make deployment as simple and painless as possible; however,
there is an inherent amount of complexity in configuring and deploying web
applications. If you find yourself a little lost, please don't hesitate to
[reach out for help](/help)
reach out to for help.
:::
## Deployment Scenarios
### Embedded Viewer (deprecated)
`OHIF-v3` has deprecated deploying the viewer as an embedded viewer the number
of underlying libraries that run web workers are increasing for OHIF. An example
of these libraries is OHIF's 3D rendering functionality that is provided by
`vtk-js`.
### Stand-alone Viewer
Deploying the OHIF Viewer as a stand-alone web application provides many
@@ -43,7 +35,6 @@ _Today:_
_In the future:_
- The ability to package the viewer for [App Store distribution][app-store]
- Leverage `service-workers` for offline support and speed benefits from caching
#### Hosted Static Assets
@@ -52,10 +43,6 @@ HTML, CSS, JS, Font Files, and Images. We "build" those files from our
`source code` with configuration specific to our project. We then make those
files publicly accessible by hosting them on a Web Server.
If you have not deployed a web application before, this may be a good time to
[reach out for help](/help), as these steps assume prior web development and
deployment experience.
##### Part 1 - Build Production Assets
"Building", or creating, the files you will need is the same regardless of the
@@ -84,6 +71,16 @@ _Advanced_
- [GCP + Cloudflare](./static-assets.md#gcp--cloudflare)
- [Azure](./static-assets.md#azure)
### Embedded Viewer (iframe)
`OHIF-v3` has deprecated deploying the viewer as an embedded viewer via a script
tag as the number of underlying libraries that run web workers are increasing for OHIF. An example of these libraries is OHIF's 3D rendering functionality that is provided by
`vtk-js`.
However, you can still embed the viewer using an iframe. You can utilize the iframe element to load the OHIF viewer and establish communication with it using the postMessage API if needed.
Read more about how to use the iframe [here](./iframe.md).
## Data
The OHIF Viewer is able to connect to any data source that implements the [DICOM
@@ -1,7 +0,0 @@
---
sidebar_position: 6
---
# User Account Control
> Coming soon - We are working on bringing the User Account Control to OHIF-v3
+1 -1
View File
@@ -113,7 +113,7 @@ files:
### create-extension
Similar to the `create-extension` command, you can use the `create-extension`
Similar to the `create-mode` command, you can use the `create-extension`
command to create a new extension template. This command will create a new
extension template in the directory that you specify the path.
+1 -1
View File
@@ -1,6 +1,6 @@
---
sidebar_position: 10
sidebar_label: 💥 Migration Guide (NEW)💥
sidebar_label: Migration Guide (NEW)
---
# Migration Guide
Loaded 100 of 539 files, more files were not shown because too many files have changed in this diff. Show more