Compare commits

..
Author SHA1 Message Date
ohif-bot c4f9d5e750 chore(release): publish [skip ci]
- @ohif/extension-vtk@0.50.23
 - @ohif/ui@0.51.3
 - @ohif/viewer@1.2.7
2019-10-01 07:11:22 +00:00
Erik Ziegler cf40a8388c fix: Address issues with touch devices and drag/drop causing crashes (#982) 2019-10-01 09:00:55 +02:00
Erik Ziegler d596ab5c3d ci: Try to fix codecov threshold (#976) 2019-09-30 12:24:14 +02:00
Erik Ziegler 72198eee7d ci: Increase build timeout limit as a stopgap measure (#977) 2019-09-30 12:19:44 +02:00
Danny Brown 057f68a9b4 fix: yarn run start as alias for yarn run dev (#973) 2019-09-28 20:25:47 -04:00
Steve Pieper 034ca64d6b docs: Update node version in readme (#971)
Fixes #970
2019-09-28 19:10:20 -04:00
ohif-bot 4a0901aee9 chore(release): publish [skip ci]
- @ohif/extension-vtk@0.50.22
 - @ohif/core@1.0.1
 - @ohif/viewer@1.2.6
2019-09-27 15:42:12 +00:00
Bhavesh Pandey d0ec9cf0e0 fix: Check for Value in 00081155 sequence (Few patient protocol images doesn't have this value) and removed a duplicate declaration (#921) 2019-09-27 11:30:26 -04:00
ohif-bot 71bea0d4d8 chore(release): publish [skip ci]
- @ohif/extension-cornerstone@1.2.5
 - @ohif/viewer@1.2.5
2019-09-27 13:18:31 +00:00
Danny Brown e607ed2aa0 fix: version bump issue (#963)
* Revert "fix: version bump issue (#962)"

This reverts commit c80ea17cc2.

* fix: stick
2019-09-27 09:04:29 -04:00
ohif-bot 4939f43509 chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.0.0
 - @ohif/viewer@2.0.0
2019-09-27 12:54:25 +00:00
Danny Brown c80ea17cc2 fix: version bump issue (#962)
* fix: version bump issue

* fix: catch the cornerstone-bump as well
2019-09-27 08:43:40 -04:00
ohif-bot aa7dc1e2c1 chore(release): publish [skip ci]
- @ohif/extension-cornerstone@2.0.0
 - @ohif/extension-dicom-html@1.0.0
 - @ohif/extension-dicom-pdf@1.0.0
 - @ohif/extension-vtk@0.50.21
 - @ohif/core@1.0.0
 - @ohif/viewer@2.0.0
2019-09-27 11:58:23 +00:00
ladeirarodolfo e7e1a8a6cd fix: 🐛 Add DicomLoaderService & FileLoaderService to fix SR, PDF, and SEG support in local file and WADO-RS-only use cases (#862)
* fix: 🐛 Local file: failing when retrieving segmentation data

Fix segmentation data retrieval issues for local file. Changed from
fecthing to use cornerstone loadAndCache method

BREAKING CHANGE: DICOM Seg

Closes: part of #838

* Switch SEG retrieval to WADO-RS

* Forgot a debugger

* refactor: 💡 Code refactor. Minor changes into methods

* fix: 🐛 Load local files: PDF

Items:
1. FileLoaderService: used for serveral operations on local files(load it,  get list of studies, group them, accepting dicom and pdf)
2. DicomLoaderService: used for loading dicom based on dataset and studies. Depending on type of dicom loader might change. WIP
3. Refactor PDF and handleSegmentationStorage to use DicomLoaderService

* fix: 🐛 Code review

* fix: 🐛 Code review. Changed:Folder organization and dicom file

Move fileLoaderService and others to a specific folder. When loading
dicom file change to only retrieve the file(not use cornerstone to cache
or anything else).

* fix: 🐛 Code review. Move dicomLoaderService to core

Moved dicomLoaderService to ohif/core and localFileLoaders to a specific
folder.

* fix: 🐛 Code review

Simplified method to get study for dicom file. Added error handling on
file loading. DicomLoaderService to be exposed on ohif/core/utils
instead.

* fix: 🐛 Reduce local load to one method only

Reduced local file load to one method only

* fix: 🐛 HTML to use dicomLoaderService. Prefer wadors than (uri)

* fix: 🐛 Code implementation for multiframe files

* fix: 🐛 Code review. Default local loader to dicom

Closes: 838

* fix: 🐛 Code review. Use relative path to require DICOMWeb

Closes: 838

* fix: 🐛 Code review. Fix unit test. Added DicomLoaderService mod

Closes: 838

* fix: 🐛 Code review. Add 'Seg' on left thumb

When getting/creating dataset get modality for file/image read

Closes: 838
2019-09-27 13:47:08 +02:00
ohif-bot e66c27affb chore(release): publish [skip ci]
- @ohif/viewer@1.2.3
2019-09-27 11:10:33 +00:00
Danny Brown ae26904885 Revert "* fix: 🐛 Enabling workbox to cache webfonts and fonts-stylesheets, add local fonts for UMD build (#906)" (#961)
This reverts commit 69b96478cb.
2019-09-27 06:54:47 -04:00
ohif-bot 8bb730318a chore(release): publish [skip ci]
- @ohif/viewer@1.2.2
2019-09-27 09:20:09 +00:00
Gustavo André Lelis 69b96478cb * fix: 🐛 Enabling workbox to cache webfonts and fonts-stylesheets, add local fonts for UMD build (#906)
* fix: 🐛 commonjs/umd fonts import

Importing font into index-umd.js and creating a simple font css file
with font-face

Closes: #724

* fix: 🐛 Enabling workbox to cache webfonts and fonts-stylesheets

Closes: #724

* fix: 🐛 Adding all type of fonts from google - Roboto

Closes: #724
2019-09-27 11:11:05 +02:00
ohif-bot 816666df7b chore(release): publish [skip ci]
- @ohif/viewer@1.2.1
2019-09-26 18:24:46 +00:00
Danny Brown e375a4ab11 fix: google cloud support w/ docker (via env var) (#958)
* fix: make sure google config is in output

* fix: update config sub to use new paths and file names
2019-09-26 14:08:44 -04:00
ohif-bot 7f4d71e412 chore(release): publish [skip ci]
- @ohif/viewer@1.2.0
2019-09-26 17:56:20 +00:00
ladeirarodolfo a90605c82d feat: 🎸 React custom component on toolbar button (#935)
* feat: 🎸 React custom component on toolbar button

Toolbar button can consume/render react custom components. Its only
required to expose the component into toolbar definitions
(toolbarModule)

Closes: 924

* feat: 🎸 Core review. Minor changes on documentation

Closes: 924

* feat: 🎸 Code review

* feat: 🎸 Code review. Changing function call to variable decl
2019-09-26 13:45:19 -04:00
ohif-bot 5b523c2afa chore(release): publish [skip ci]
- @ohif/extension-vtk@0.50.20
 - @ohif/ui@0.51.2
 - @ohif/viewer@1.1.14
2019-09-26 14:21:10 +00:00
Gustavo André Lelis 5551f817e3 fix: 🐛 Set series into active viewport by clicking on thumbnail (#945)
* fix: 🐛 Set series into active viewport by clicking on thumbnail

Users should be able to set the series for the active viewport by
clicking it's thumbnail into Study browser

Closes: #895

* fix: 🐛 Set series into active viewport by clicking on thumbnail

Small refactor into onClick data callback and thumbnailClick function

Closes: #895

* fix: 🐛 Refactor thumbnail click code changes

* fix: 🐛 We must clear the sopInstanceUid if we don't update it together with the viewportSpecificData, preventing console warning.

* fix: 🐛  Cleaning up a few more code changes
2019-09-26 10:08:30 -04:00
ohif-bot a9ca3fa026 chore(release): publish [skip ci]
- @ohif/extension-cornerstone@1.2.2
 - @ohif/extension-dicom-html@0.50.8
 - @ohif/extension-dicom-microscopy@0.50.6
 - @ohif/extension-vtk@0.50.19
 - @ohif/viewer@1.1.13
2019-09-26 12:24:19 +00:00
Erik Ziegler 89380353d2 fix: Add some code splitting for PWA build (#937)
* fix: Add some code splitting for PWA build

* Fix missing files
2019-09-26 11:35:13 +02:00
ohif-bot edb86fad55 chore(release): publish [skip ci]
- @ohif/viewer@1.1.12
2019-09-26 03:56:47 +00:00
Danny Brown 395a1be3fb Merge pull request #952 from dannyrb/fix/docker-ci
ci: docker publish (first attempt)
2019-09-25 23:45:59 -04:00
dannyrb 65d852b9e4 enable ci 2019-09-25 23:39:42 -04:00
dannyrb e797a3a141 Add modified dockerfile from previous version 2019-09-25 23:16:00 -04:00
Danny Brown 246936f90b Merge pull request #949 from TZubiri/2dmpr-ui-fix
ui fix: Made mpr more accessible from toolbar.
2019-09-24 21:29:43 -04:00
Tomas Zubiri c57298475a UI fix: Collapsed 2dMPR button to main toolbar 2019-09-24 18:43:32 -03:00
Tomas Zubiri 6a5ebb3e25 initial attempt to simplify code and ui 2019-09-24 18:20:06 -03:00
63 changed files with 1484 additions and 422 deletions

No files matched your search

+8 -8
View File
@@ -149,13 +149,13 @@ jobs:
command: |
# This file will exist if a new version was published by
# our `semantic-release` command in the previous job
if [[ ! -e tmp/updated-version.txt ]]; then
if [[ ! -e platform/viewer/success_version.txt ]]; then
exit 0
else
# Remove npm config
rm -f ./.npmrc
# Set our version number using vars
export IMAGE_VERSION=$(cat tmp/updated-version.txt)
export IMAGE_VERSION=$(cat platform/viewer/success_version.txt)
export IMAGE_VERSION_FULL=v$IMAGE_VERSION.${CIRCLE_BUILD_NUM}
echo $IMAGE_VERSION
echo $IMAGE_VERSION_FULL
@@ -175,7 +175,7 @@ jobs:
- run:
name: 'Build Demo Site'
command: yarn run build:demo
no_output_timeout: 30m
no_output_timeout: 45m
- run:
name: 'Upload SourceMaps, Send Deploy Notification'
command: |
@@ -324,6 +324,7 @@ workflows:
requires:
- 'E2E: Script Tag'
- 'E2E: PWA'
# Update docs.ohif.org
- DOCS_PUBLISH:
requires:
- 'E2E: Script Tag'
@@ -332,12 +333,11 @@ workflows:
requires:
- 'E2E: Script Tag'
- 'E2E: PWA'
# Update viewer.ohif.org
- VIEWER_DEMO_PUBLISH:
requires:
- VIEWER_DEMO_BUILD
# Update hub.docker.org
# - DOCKER_PUBLISH:
# requires:
# - NPM_PUBLISH
# Update viewer.ohif.org
# Update docs.ohif.org
- DOCKER_PUBLISH:
requires:
- NPM_PUBLISH
+3 -1
View File
@@ -8,11 +8,13 @@ coverage:
status:
project:
default:
threshold: 0.10%
threshold: 0.5%
core:
flags: core
threshold: 0.5%
viewer:
flags: viewer
threshold: 0.5%
patch: off
flags:
core:
+12
View File
@@ -0,0 +1,12 @@
server {
listen 80;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
+19
View File
@@ -0,0 +1,19 @@
#!/bin/bash
# If CLIENT_ID is specified, use the google.js configuration with the modified ID
if [ ! -z "$CLIENT_ID" ]
then
echo "Google Cloud Healthcare $CLIENT_ID has been provided: "
echo $CLIENT_ID
echo "Updating config..."
# - Use SED to replace the CLIENT_ID that is currently in google.js
sed -i -e "s/YOURCLIENTID.apps.googleusercontent.com/$CLIENT_ID/g" /usr/share/nginx/html/google.js
# - Copy google.js to overwrite app-config.js
cp /usr/share/nginx/html/google.js /usr/share/nginx/html/app-config.js
fi
echo "Starting Nginx to serve the OHIF Viewer..."
exec "$@"
+30
View File
@@ -0,0 +1,30 @@
# Reduces size of context and hides
# files from Docker (can't COPY or ADD these)
# Output
dist/
build/
# Dependencies
node_modules/
# Root
README.md
Dockerfile
dockerfile
# Misc. Config
.git
.DS_Store
.gitignore
.vscode
.circleci
# Unnecessary things to pull into container
.circleci/
.github/
.netlify/
.scripts/
.vscode/
coverage/
docs/
+64
View File
@@ -0,0 +1,64 @@
# This dockerfile is used to publish the `ohif/viewer` image on dockerhub.
#
# It's a good example of how to build our static application and package it
# with a web server capable of hosting it as static content.
#
# docker build
# --------------
# If you would like to use this dockerfile to build and tag an image, make sure
# you set the context to the project's root directory:
# https://docs.docker.com/engine/reference/commandline/build/
#
#
# SUMMARY
# --------------
# This dockerfile has two stages:
#
# 1. Building the React application for production
# 2. Setting up our Nginx (Alpine Linux) image w/ step one's output
#
# Stage 1: Build the application
# docker build -t ohif/viewer:latest .
FROM node:10.16.3-slim as builder
RUN mkdir /usr/src/app
WORKDIR /usr/src/app
# Copy Files
COPY .docker /usr/src/app/.docker
COPY .webpack /usr/src/app/.webpack
COPY extensions /usr/src/app/extensions
COPY platform /usr/src/app/platform
COPY .browserslistrc /usr/src/app/.browserslistrc
COPY aliases.config.js /usr/src/app/aliases.config.js
COPY babel.config.js /usr/src/app/babel.config.js
COPY lerna.json /usr/src/app/lerna.json
COPY package.json /usr/src/app/package.json
COPY postcss.config.js /usr/src/app/postcss.config.js
COPY yarn.lock /usr/src/app/yarn.lock
# Run the install before copying the rest of the files
RUN yarn config set workspaces-experimental true
RUN yarn install
ENV PATH /usr/src/app/node_modules/.bin:$PATH
# ENV GENERATE_SOURCEMAP=false
# ENV REACT_APP_CONFIG=config/default.js
RUN yarn run build
# Stage 2: Bundle the built application into a Docker container
# which runs Nginx using Alpine Linux
FROM nginx:1.15.5-alpine
RUN apk add --no-cache bash
RUN rm -rf /etc/nginx/conf.d
COPY .docker/Viewer-v2.x /etc/nginx/conf.d
COPY .docker/Viewer-v2.x/entrypoint.sh /usr/src/
RUN chmod 777 /usr/src/entrypoint.sh
COPY --from=builder /usr/src/app/platform/viewer/dist /usr/share/nginx/html
EXPOSE 80
EXPOSE 443
ENTRYPOINT ["/usr/src/entrypoint.sh"]
CMD ["nginx", "-g", "daemon off;"]
+1 -1
View File
@@ -132,7 +132,7 @@ deployment recipe.
### Requirements
- [Yarn 1.17.3+](https://yarnpkg.com/en/docs/install)
- [Node 8+](https://nodejs.org/en/)
- [Node 10+](https://nodejs.org/en/)
- Yarn Workspaces should be enabled on your machine:
- `yarn config set workspaces-experimental true`
+26
View File
@@ -160,6 +160,7 @@ An extension can register a Toolbar Module by providing a `getToolbarModule()`
method that returns a React Component. The component does not receive any props.
If you want to modify or react to state, you will need to connect to the redux
store.
The given toolbar must determine its set of elements and the context of them. The set of elements will be listed on toolbar `definitions`.
![Toolbar Extension](../assets/img/extensions-toolbar.gif)
@@ -170,6 +171,31 @@ Toolbar components are rendered in the `ToolbarRow` component.
For a complete example implementation,
[check out the OHIFCornerstoneViewport's Toolbar Module](https://github.com/OHIF/Viewers/blob/master/extensions/cornerstone/src/toolbarModule.js).
##### Toolbar Custom Component
Toolbar elements can define its own custom react component to be consumed when rendering it. So far, it accepts `Functional` and `Class` Components. For that, you just need to expose your `CustomToolbarComponent` as the value of key `CustomComponent`. In case the property `CustomComponent` is not present, a default toolbar component will be used to render it. See bellow
```js
definitions: [
...
{
id: 'Custom',
label: 'Custom',
icon: 'custom-icon',
CustomComponent: CustomToolbarComponent,
}
...
]
```
`CustomComponent` components will receive the following props:
- parentContext: parent context. (In most of the cases it will be a ToolbarRow instance)
- toolbarClickCallback: callback method when clicking on toolbar
- button: its own definition object
- key: react key prop
- activeButtons: list of active elements
- isActive: if current
#### SopClassHandler
...
@@ -1,3 +1,4 @@
const webpack = require('webpack');
const merge = require('webpack-merge');
const path = require('path');
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
@@ -34,5 +35,10 @@ module.exports = (env, argv) => {
libraryExport: 'default',
filename: pkg.main,
},
plugins: [
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1,
}),
],
});
};
+55
View File
@@ -3,6 +3,61 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.2.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.2.4...@ohif/extension-cornerstone@1.2.5) (2019-09-27)
### Bug Fixes
* version bump issue ([#963](https://github.com/OHIF/Viewers/issues/963)) ([e607ed2](https://github.com/OHIF/Viewers/commit/e607ed2)), closes [#962](https://github.com/OHIF/Viewers/issues/962)
# [2.0.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.2.2...@ohif/extension-cornerstone@2.0.0) (2019-09-27)
### Bug Fixes
* 🐛 Add DicomLoaderService & FileLoaderService to fix SR, PDF, and SEG support in local file and WADO-RS-only use cases ([#862](https://github.com/OHIF/Viewers/issues/862)) ([e7e1a8a](https://github.com/OHIF/Viewers/commit/e7e1a8a)), closes [#838](https://github.com/OHIF/Viewers/issues/838)
* version bump issue ([#962](https://github.com/OHIF/Viewers/issues/962)) ([c80ea17](https://github.com/OHIF/Viewers/commit/c80ea17))
### BREAKING CHANGES
* DICOM Seg
# [2.0.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.2.2...@ohif/extension-cornerstone@2.0.0) (2019-09-27)
### Bug Fixes
* 🐛 Add DicomLoaderService & FileLoaderService to fix SR, PDF, and SEG support in local file and WADO-RS-only use cases ([#862](https://github.com/OHIF/Viewers/issues/862)) ([e7e1a8a](https://github.com/OHIF/Viewers/commit/e7e1a8a)), closes [#838](https://github.com/OHIF/Viewers/issues/838)
### BREAKING CHANGES
* DICOM Seg
## [1.2.2](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.2.1...@ohif/extension-cornerstone@1.2.2) (2019-09-26)
### Bug Fixes
* Add some code splitting for PWA build ([#937](https://github.com/OHIF/Viewers/issues/937)) ([8938035](https://github.com/OHIF/Viewers/commit/8938035))
## [1.2.1](https://github.com/OHIF/Viewers/compare/@ohif/extension-cornerstone@1.2.0...@ohif/extension-cornerstone@1.2.1) (2019-09-17)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-cornerstone",
"version": "1.2.1",
"version": "1.2.5",
"description": "OHIF extension for Cornerstone",
"author": "OHIF",
"license": "MIT",
@@ -0,0 +1,37 @@
/**
* We use this component to leverage "Code Splitting"
*
* Link: https://serverless-stack.com/chapters/code-splitting-in-create-react-app.html
*/
import React, { Component } from 'react';
export default function asyncComponent(importComponent) {
class AsyncComponent extends Component {
constructor(props) {
super(props);
this.state = {
component: null,
};
}
async componentDidMount() {
// Add dynamically loaded component to state
const { default: component } = await importComponent();
this.setState({
component: component,
});
}
render() {
const C = this.state.component;
// Render the loaded component, or null
return C ? <C {...this.props} /> : null;
}
}
return AsyncComponent;
}
@@ -4,7 +4,7 @@ import OHIF from '@ohif/core';
import cornerstone from 'cornerstone-core';
import cornerstoneTools from 'cornerstone-tools';
const { StackManager } = OHIF.utils;
const { StackManager, DicomLoaderService } = OHIF.utils;
function getDisplaySet(studies, studyInstanceUid, displaySetInstanceUid) {
const study = studies.find(
@@ -52,14 +52,6 @@ function addSegMetadataToCornerstoneToolState(
}
}
function retrieveDicomData(wadoUri) {
// TODO: Authorization header depends on the server. If we ever have multiple servers
// we will need to figure out how / when to pass this information in.
return fetch(wadoUri, {
headers: OHIF.DICOMWeb.getAuthorizationHeader(),
}).then(response => response.arrayBuffer());
}
async function handleSegmentationStorage(
studies,
studyInstanceUid,
@@ -73,8 +65,11 @@ async function handleSegmentationStorage(
studyInstanceUid,
displaySetInstanceUid
);
const segWadoUri = displaySet.images[0].getData().wadouri;
const arrayBuffer = await retrieveDicomData(segWadoUri);
const arrayBuffer = await DicomLoaderService.findDicomDataPromise(
displaySet,
studies
);
const dicomData = dcmjs.data.DicomMessage.readFile(arrayBuffer);
const dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(
dicomData.dict
+3 -1
View File
@@ -1,7 +1,9 @@
import OHIFCornerstoneViewport from './OHIFCornerstoneViewport.js';
import asyncComponent from './asyncComponent.js';
import commandsModule from './commandsModule.js';
import toolbarModule from './toolbarModule.js';
const OHIFCornerstoneViewport = asyncComponent(() => import( /* webpackChunkName: "OHIFCornerstoneViewport" */ './OHIFCornerstoneViewport.js'));
/**
*
*/
@@ -1,3 +1,4 @@
const webpack = require('webpack');
const merge = require('webpack-merge');
const path = require('path');
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
@@ -34,5 +35,10 @@ module.exports = (env, argv) => {
libraryExport: 'default',
filename: pkg.main,
},
plugins: [
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1,
}),
],
});
};
+27
View File
@@ -3,6 +3,33 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.0.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@0.50.8...@ohif/extension-dicom-html@1.0.0) (2019-09-27)
### Bug Fixes
* 🐛 Add DicomLoaderService & FileLoaderService to fix SR, PDF, and SEG support in local file and WADO-RS-only use cases ([#862](https://github.com/OHIF/Viewers/issues/862)) ([e7e1a8a](https://github.com/OHIF/Viewers/commit/e7e1a8a)), closes [#838](https://github.com/OHIF/Viewers/issues/838)
### BREAKING CHANGES
* DICOM Seg
## [0.50.8](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@0.50.7...@ohif/extension-dicom-html@0.50.8) (2019-09-26)
### Bug Fixes
* Add some code splitting for PWA build ([#937](https://github.com/OHIF/Viewers/issues/937)) ([8938035](https://github.com/OHIF/Viewers/commit/8938035))
## [0.50.7](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-html@0.50.6...@ohif/extension-dicom-html@0.50.7) (2019-09-12)
**Note:** Version bump only for package @ohif/extension-dicom-html
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-dicom-html",
"version": "0.50.7",
"version": "1.0.0",
"description": "OHIF extension for rendering structured reports to HTML",
"author": "OHIF",
"license": "MIT",
@@ -1,46 +1,34 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import DicomHtmlViewport from './DicomHtmlViewport';
import OHIF from '@ohif/core';
const { DicomLoaderService } = OHIF.utils;
class OHIFDicomHtmlViewport extends Component {
static propTypes = {
studies: PropTypes.object,
displaySet: PropTypes.object,
viewportIndex: PropTypes.number
viewportIndex: PropTypes.number,
};
state = {
byteArray: null,
error: null
error: null,
};
componentDidMount() {
const { displaySet } = this.props.viewportData;
const {
studyInstanceUid,
seriesInstanceUid,
sopInstanceUid,
wadoRoot,
wadoUri,
authorizationHeaders
} = displaySet;
const { displaySet, studies } = this.props.viewportData;
this.retrieveDicomData(
studyInstanceUid,
seriesInstanceUid,
sopInstanceUid,
wadoRoot,
wadoUri,
authorizationHeaders
).then(
byteArray => {
DicomLoaderService.findDicomDataPromise(displaySet, studies).then(
data => {
const byteArray = new Uint8Array(data);
this.setState({
byteArray
byteArray: byteArray,
});
},
error => {
this.setState({
error
error,
});
throw new Error(error);
@@ -48,27 +36,6 @@ class OHIFDicomHtmlViewport extends Component {
);
}
retrieveDicomData(
studyInstanceUid,
seriesInstanceUid,
sopInstanceUid,
wadoRoot,
wadoUri,
authorizationHeaders
) {
// TODO: Passing in a lot of data we aren't using
// TODO: Authorization header depends on the server. If we ever have multiple servers
// we will need to figure out how / when to pass this information in.
return fetch(wadoUri, {
headers: authorizationHeaders
})
.then(response => response.arrayBuffer())
.then(arraybuffer => {
return new Uint8Array(arraybuffer);
});
}
render() {
return (
<>
@@ -0,0 +1,37 @@
/**
* We use this component to leverage "Code Splitting"
*
* Link: https://serverless-stack.com/chapters/code-splitting-in-create-react-app.html
*/
import React, { Component } from 'react';
export default function asyncComponent(importComponent) {
class AsyncComponent extends Component {
constructor(props) {
super(props);
this.state = {
component: null,
};
}
async componentDidMount() {
// Add dynamically loaded component to state
const { default: component } = await importComponent();
this.setState({
component: component,
});
}
render() {
const C = this.state.component;
// Render the loaded component, or null
return C ? <C {...this.props} /> : null;
}
}
return AsyncComponent;
}
+8 -2
View File
@@ -1,5 +1,11 @@
import asyncComponent from './asyncComponent.js';
import OHIFDicomHtmlSopClassHandler from './OHIFDicomHtmlSopClassHandler.js';
import OHIFDicomHtmlViewport from './OHIFDicomHtmlViewport.js';
const OHIFDicomHtmlViewport = asyncComponent(() =>
import(
/* webpackChunkName: "OHIFDicomHtmlViewport" */ './OHIFDicomHtmlViewport.js'
)
);
export default {
/**
@@ -12,5 +18,5 @@ export default {
},
getSopClassHandlerModule() {
return OHIFDicomHtmlSopClassHandler;
}
},
};
@@ -1,3 +1,4 @@
const webpack = require('webpack');
const merge = require('webpack-merge');
const path = require('path');
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
@@ -34,5 +35,10 @@ module.exports = (env, argv) => {
libraryExport: 'default',
filename: pkg.main,
},
plugins: [
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1
})
]
});
};
+11
View File
@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.50.6](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.50.5...@ohif/extension-dicom-microscopy@0.50.6) (2019-09-26)
### Bug Fixes
* Add some code splitting for PWA build ([#937](https://github.com/OHIF/Viewers/issues/937)) ([8938035](https://github.com/OHIF/Viewers/commit/8938035))
## [0.50.5](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-microscopy@0.50.4...@ohif/extension-dicom-microscopy@0.50.5) (2019-09-10)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-dicom-microscopy",
"version": "0.50.5",
"version": "0.50.6",
"description": "OHIF extension for Dicom Microscopy",
"author": "OHIF",
"license": "MIT",
@@ -1,10 +1,7 @@
import React, { Component } from 'react';
import ReactResizeDetector from 'react-resize-detector';
import { api } from 'dicom-microscopy-viewer';
import debounce from 'lodash.debounce';
const microscopyViewer = api.VLWholeSlideMicroscopyImageViewer;
class DicomMicroscopyViewport extends Component {
state = {
error: null,
@@ -56,9 +53,12 @@ class DicomMicroscopyViewport extends Component {
}
return Promise.all(promises);
})
.then(metadata => {
.then(async metadata => {
metadata = metadata.filter(m => m);
const { api } = await import(/* webpackChunkName: "dicom-microscopy-viewer" */ 'dicom-microscopy-viewer');
const microscopyViewer = api.VLWholeSlideMicroscopyImageViewer;
this.viewer = new microscopyViewer({
client: dicomWebClient,
metadata,
@@ -0,0 +1,37 @@
/**
* We use this component to leverage "Code Splitting"
*
* Link: https://serverless-stack.com/chapters/code-splitting-in-create-react-app.html
*/
import React, { Component } from 'react';
export default function asyncComponent(importComponent) {
class AsyncComponent extends Component {
constructor(props) {
super(props);
this.state = {
component: null,
};
}
async componentDidMount() {
// Add dynamically loaded component to state
const { default: component } = await importComponent();
this.setState({
component: component,
});
}
render() {
const C = this.state.component;
// Render the loaded component, or null
return C ? <C {...this.props} /> : null;
}
}
return AsyncComponent;
}
+7 -1
View File
@@ -1,5 +1,11 @@
import asyncComponent from './asyncComponent.js';
import DicomMicroscopySopClassHandler from './DicomMicroscopySopClassHandler.js';
import DicomMicroscopyViewport from './DicomMicroscopyViewport.js';
const DicomMicroscopyViewport = asyncComponent(() =>
import(
/* webpackChunkName: "DicomMicroscopyViewport" */ './DicomMicroscopyViewport.js'
)
);
export default {
/**
+16
View File
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [1.0.0](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-pdf@0.50.4...@ohif/extension-dicom-pdf@1.0.0) (2019-09-27)
### Bug Fixes
* 🐛 Add DicomLoaderService & FileLoaderService to fix SR, PDF, and SEG support in local file and WADO-RS-only use cases ([#862](https://github.com/OHIF/Viewers/issues/862)) ([e7e1a8a](https://github.com/OHIF/Viewers/commit/e7e1a8a)), closes [#838](https://github.com/OHIF/Viewers/issues/838)
### BREAKING CHANGES
* DICOM Seg
## [0.50.4](https://github.com/OHIF/Viewers/compare/@ohif/extension-dicom-pdf@0.50.3...@ohif/extension-dicom-pdf@0.50.4) (2019-09-10)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-dicom-pdf",
"version": "0.50.4",
"version": "1.0.0",
"description": "OHIF extension for Dicom PDF",
"author": "OHIF",
"license": "MIT",
+20 -15
View File
@@ -4,41 +4,46 @@ import TypedArrayProp from './TypedArrayProp';
// TODO: Should probably use dcmjs for this
const SOP_CLASS_UIDS = {
ENCAPSULATED_PDF: '1.2.840.10008.5.1.4.1.1.104.1'
ENCAPSULATED_PDF: '1.2.840.10008.5.1.4.1.1.104.1',
};
class DicomPDFViewport extends Component {
state = {
fileURL: null,
error: null
error: null,
};
static propTypes = {
byteArray: TypedArrayProp.uint8
byteArray: TypedArrayProp.uint8,
};
renderPDF = dataSet => {
const sopClassUid = dataSet.string('x00080016');
renderPDF = (dataSet, byteArray) => {
let pdfByteArray = byteArray;
if (sopClassUid !== SOP_CLASS_UIDS.ENCAPSULATED_PDF) {
throw new Error('This is not a DICOM-encapsulated PDF');
if (dataSet) {
const sopClassUid = dataSet.string('x00080016');
if (sopClassUid !== SOP_CLASS_UIDS.ENCAPSULATED_PDF) {
throw new Error('This is not a DICOM-encapsulated PDF');
}
const fileTag = dataSet.elements.x00420011;
const offset = fileTag.dataOffset;
const remainder = offset + fileTag.length;
pdfByteArray = dataSet.byteArray.slice(offset, remainder);
}
const fileTag = dataSet.elements.x00420011;
const offset = fileTag.dataOffset;
const remainder = offset + fileTag.length;
const pdfByteArray = dataSet.byteArray.slice(offset, remainder);
const PDF = new Blob([pdfByteArray], { type: 'application/pdf' });
const fileURL = URL.createObjectURL(PDF);
this.setState({
fileURL
fileURL,
});
};
parseByteArray = byteArray => {
const options = {
untilTag: ''
untilTag: '',
};
let dataSet;
@@ -47,7 +52,7 @@ class DicomPDFViewport extends Component {
dataSet = dicomParser.parseDicom(byteArray, options);
} catch (error) {
this.setState({
error
error,
});
}
@@ -57,7 +62,7 @@ class DicomPDFViewport extends Component {
componentDidMount() {
const dataSet = this.parseByteArray(this.props.byteArray);
this.renderPDF(dataSet);
this.renderPDF(dataSet, this.props.byteArray);
}
render() {
@@ -1,60 +1,54 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import OHIF from "@ohif/core";
import OHIFComponentPlugin from "./OHIFComponentPlugin.js";
import DicomPDFViewport from "./DicomPDFViewport";
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import OHIF from '@ohif/core';
import OHIFComponentPlugin from './OHIFComponentPlugin.js';
import DicomPDFViewport from './DicomPDFViewport';
const { DICOMWeb } = OHIF;
const { DicomLoaderService } = OHIF.utils;
class OHIFDicomPDFViewport extends Component {
static propTypes = {
studies: PropTypes.object,
displaySet: PropTypes.object,
viewportIndex: PropTypes.number
viewportIndex: PropTypes.number,
};
state = {
byteArray: null,
error: null
error: null,
};
static id = "DicomPDFViewportPDF";
static id = 'DicomPDFViewportPDF';
static init() {
console.log("DicomPDFViewport init()");
console.log('DicomPDFViewport init()');
}
static destroy() {
console.log("DicomPDFViewport destroy()");
console.log('DicomPDFViewport destroy()');
}
componentDidMount() {
const { displaySet } = this.props.viewportData;
const { displaySet, studies } = this.props.viewportData;
const {
studyInstanceUid,
seriesInstanceUid,
sopInstanceUid,
wadoRoot,
wadoUri,
authorizationHeaders
authorizationHeaders,
} = displaySet;
this.retrieveDicomData(
studyInstanceUid,
seriesInstanceUid,
sopInstanceUid,
wadoRoot,
wadoUri,
authorizationHeaders
).then(
byteArray => {
DicomLoaderService.findDicomDataPromise(displaySet, studies).then(
data => {
const byteArray = new Uint8Array(data);
this.setState({
byteArray
byteArray: byteArray,
});
},
error => {
this.setState({
error
error,
});
throw new Error(error);
@@ -62,27 +56,6 @@ class OHIFDicomPDFViewport extends Component {
);
}
retrieveDicomData(
studyInstanceUid,
seriesInstanceUid,
sopInstanceUid,
wadoRoot,
wadoUri,
authorizationHeaders
) {
// TODO: Passing in a lot of data we aren't using
// TODO: Authorization header depends on the server. If we ever have multiple servers
// we will need to figure out how / when to pass this information in.
return fetch(wadoUri, {
headers: authorizationHeaders
})
.then(response => response.arrayBuffer())
.then(arraybuffer => {
return new Uint8Array(arraybuffer);
});
}
render() {
const { id, init, destroy } = OHIFDicomPDFViewport;
const pluginProps = { id, init, destroy };
+6
View File
@@ -1,3 +1,4 @@
const webpack = require('webpack');
const merge = require('webpack-merge');
const path = require('path');
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
@@ -34,5 +35,10 @@ module.exports = (env, argv) => {
libraryExport: 'default',
filename: pkg.main,
},
plugins: [
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1,
}),
],
});
};
+43
View File
@@ -3,6 +3,49 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.50.23](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.22...@ohif/extension-vtk@0.50.23) (2019-10-01)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.50.22](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.21...@ohif/extension-vtk@0.50.22) (2019-09-27)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.50.21](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.20...@ohif/extension-vtk@0.50.21) (2019-09-27)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.50.20](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.19...@ohif/extension-vtk@0.50.20) (2019-09-26)
**Note:** Version bump only for package @ohif/extension-vtk
## [0.50.19](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.18...@ohif/extension-vtk@0.50.19) (2019-09-26)
### Bug Fixes
* Add some code splitting for PWA build ([#937](https://github.com/OHIF/Viewers/issues/937)) ([8938035](https://github.com/OHIF/Viewers/commit/8938035))
## [0.50.18](https://github.com/OHIF/Viewers/compare/@ohif/extension-vtk@0.50.17...@ohif/extension-vtk@0.50.18) (2019-09-23)
**Note:** Version bump only for package @ohif/extension-vtk
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-vtk",
"version": "0.50.18",
"version": "0.50.23",
"description": "OHIF extension for VTK.js",
"author": "OHIF",
"license": "MIT",
@@ -51,8 +51,8 @@
"react-vtkjs-viewport": "0.1.0"
},
"devDependencies": {
"@ohif/core": "^0.50.10",
"@ohif/ui": "^0.51.1",
"@ohif/core": "^1.0.1",
"@ohif/ui": "^0.51.3",
"cornerstone-tools": "^4.0.9",
"cornerstone-wado-image-loader": "^3.0.0",
"dcmjs": "^0.6.1",
+37
View File
@@ -0,0 +1,37 @@
/**
* We use this component to leverage "Code Splitting"
*
* Link: https://serverless-stack.com/chapters/code-splitting-in-create-react-app.html
*/
import React, { Component } from 'react';
export default function asyncComponent(importComponent) {
class AsyncComponent extends Component {
constructor(props) {
super(props);
this.state = {
component: null,
};
}
async componentDidMount() {
// Add dynamically loaded component to state
const { default: component } = await importComponent();
this.setState({
component: component,
});
}
render() {
const C = this.state.component;
// Render the loaded component, or null
return C ? <C {...this.props} /> : null;
}
}
return AsyncComponent;
}
+5 -1
View File
@@ -1,9 +1,13 @@
import OHIFVTKViewport from './OHIFVTKViewport.js';
import asyncComponent from './asyncComponent.js';
import commandsModule from './commandsModule.js';
import toolbarModule from './toolbarModule.js';
// This feels weird
// import loadLocales from './loadLocales';
const OHIFVTKViewport = asyncComponent(() =>
import(/* webpackChunkName: "OHIFVTKViewport" */ './OHIFVTKViewport.js')
);
const vtkExtension = {
/**
* Only required property. Should be a unique value across all extensions.
+1
View File
@@ -22,6 +22,7 @@
"dev:project": ".scripts/dev.sh",
"dev:orthanc": "lerna run dev:orthanc --stream",
"orthanc:up": "docker-compose -f .docker/Nginx-Orthanc/docker-compose.yml up",
"start": "yarn run dev",
"test": "yarn run test:unit",
"test:unit": "jest --collectCoverage",
"test:unit:ci": "lerna run test:unit:ci --parallel --stream",
+27
View File
@@ -3,6 +3,33 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.0.1](https://github.com/OHIF/Viewers/compare/@ohif/core@1.0.0...@ohif/core@1.0.1) (2019-09-27)
### Bug Fixes
* Check for Value in 00081155 sequence (Few patient protocol images doesn't have this value) and removed a duplicate declaration ([#921](https://github.com/OHIF/Viewers/issues/921)) ([d0ec9cf](https://github.com/OHIF/Viewers/commit/d0ec9cf))
# [1.0.0](https://github.com/OHIF/Viewers/compare/@ohif/core@0.50.10...@ohif/core@1.0.0) (2019-09-27)
### Bug Fixes
* 🐛 Add DicomLoaderService & FileLoaderService to fix SR, PDF, and SEG support in local file and WADO-RS-only use cases ([#862](https://github.com/OHIF/Viewers/issues/862)) ([e7e1a8a](https://github.com/OHIF/Viewers/commit/e7e1a8a)), closes [#838](https://github.com/OHIF/Viewers/issues/838)
### BREAKING CHANGES
* DICOM Seg
## [0.50.10](https://github.com/OHIF/Viewers/compare/@ohif/core@0.50.9...@ohif/core@0.50.10) (2019-09-23)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/core",
"version": "0.50.10",
"version": "1.0.1",
"description": "Generic business logic for web-based medical imaging applications",
"author": "OHIF Core Team",
"license": "MIT",
@@ -129,6 +129,7 @@ export class StudyMetadata extends Metadata {
);
if (displaySet) {
displaySet.sopClassModule = true;
displaySets.push(displaySet);
return;
@@ -214,7 +215,7 @@ export class StudyMetadata extends Metadata {
* @returns {boolean} True on success, false on failure.
*/
addDisplaySet(displaySet) {
if (displaySet instanceof ImageSet) {
if (displaySet instanceof ImageSet || displaySet.sopClassModule) {
this._displaySets.push(displaySet);
return true;
}
@@ -133,7 +133,8 @@ function getSourceImageInstanceUid(instance) {
if (
SourceImageSequence &&
SourceImageSequence.Value &&
SourceImageSequence.Value.length
SourceImageSequence.Value.length &&
SourceImageSequence.Value[0]['00081155'].Value
) {
return SourceImageSequence.Value[0]['00081155'].Value[0];
}
@@ -293,7 +294,6 @@ async function resultDataToStudyMetadata(server, studyInstanceUid, resultData) {
// TODO: Pass a reference ID to the server instead of including the URLs here
const studyData = {
seriesList: [],
studyInstanceUid,
wadoUriRoot: server.wadoUriRoot,
wadoRoot: server.wadoRoot,
qidoRoot: server.qidoRoot,
@@ -402,6 +402,7 @@ async function resultDataToStudyMetadata(server, studyInstanceUid, resultData) {
baseWadoRsUri: baseWadoRsUri,
wadouri: WADOProxy.convertURL(wadouri, server),
wadorsuri: WADOProxy.convertURL(wadorsuri, server),
wadoRoot: server.wadoRoot,
imageRendering: server.imageRendering,
thumbnailRendering: server.thumbnailRendering,
};
@@ -0,0 +1,199 @@
import cornerstone from 'cornerstone-core';
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
import { api } from 'dicomweb-client';
import DICOMWeb from '../DICOMWeb';
const getImageId = imageObj => {
if (!imageObj) {
return;
}
return typeof imageObj.getImageId === 'function'
? imageObj.getImageId()
: imageObj.url;
};
const findImageIdOnStudies = (studies, displaySetInstanceUid) => {
const study = studies.find(study => {
const displaySet = study.displaySets.some(
displaySet => displaySet.displaySetInstanceUid === displaySetInstanceUid
);
return displaySet;
});
const { seriesList = [] } = study;
const { instances = [] } = seriesList[0] || {};
const instance = instances[0];
return getImageId(instance);
};
const someInvalidStrings = strings => {
const stringsArray = Array.isArray(strings) ? strings : [strings];
const emptyString = string => !string;
let invalid = stringsArray.some(emptyString);
return invalid;
};
const getImageInstance = dataset => {
return dataset && dataset.images && dataset.images[0];
};
const getImageInstanceId = imageInstance => {
return getImageId(imageInstance);
};
const fetchIt = (url, headers = DICOMWeb.getAuthorizationHeader()) => {
return fetch(url, headers).then(response => response.arrayBuffer());
};
const cornerstoneRetriever = imageId => {
return cornerstone.loadAndCacheImage(imageId).then(image => {
return image && image.data && image.data.byteArray.buffer;
});
};
const wadorsRetriever = (
url,
studyInstanceUID,
seriesInstanceUID,
sopInstanceUID,
headers = DICOMWeb.getAuthorizationHeader()
) => {
const config = {
url,
headers,
};
const dicomWeb = new api.DICOMwebClient(config);
return dicomWeb.retrieveInstance({
studyInstanceUID,
seriesInstanceUID,
sopInstanceUID,
});
};
const getImageLoaderType = imageId => {
const loaderRegExp = /^\w+\:/;
const loaderType = loaderRegExp.exec(imageId);
return (
(loaderRegExp.lastIndex === 0 &&
loaderType &&
loaderType[0] &&
loaderType[0].replace(':', '')) ||
''
);
};
const DicomLoaderService = new (class {
getLocalData(dataset, studies) {
if (dataset && dataset.localFile) {
// Use referenced imageInstance
const imageInstance = getImageInstance(dataset);
let imageId = getImageInstanceId(imageInstance);
// or Try to get it from studies
if (someInvalidStrings(imageId)) {
imageId = findImageIdOnStudies(studies, dataset.displaySetInstanceUid);
}
if (!someInvalidStrings(imageId)) {
return cornerstoneWADOImageLoader.wadouri.loadFileRequest(imageId);
}
}
}
getDataByImageType(dataset) {
const imageInstance = getImageInstance(dataset);
if (imageInstance) {
const imageId = getImageInstanceId(imageInstance);
let getDicomDataMethod = fetchIt;
const loaderType = getImageLoaderType(imageId);
switch (loaderType) {
case 'dicomfile':
getDicomDataMethod = cornerstoneRetriever.bind(this, imageId);
break;
case 'wadors':
const url = imageInstance.getData().wadoRoot;
const studyInstanceUID = imageInstance.getStudyInstanceUID();
const seriesInstanceUID = imageInstance.getSeriesInstanceUID();
const sopInstanceUID = imageInstance.getSOPInstanceUID();
const invalidParams = someInvalidStrings([
url,
studyInstanceUID,
seriesInstanceUID,
sopInstanceUID,
]);
if (invalidParams) {
return;
}
getDicomDataMethod = wadorsRetriever.bind(
this,
url,
studyInstanceUID,
seriesInstanceUID,
sopInstanceUID
);
break;
case 'wadouri':
// Strip out the image loader specifier
imageId = imageId.substring(imageId.indexOf(':') + 1);
if (someInvalidStrings(imageId)) {
return;
}
getDicomDataMethod = fetchIt.bind(this, imageId);
break;
}
return getDicomDataMethod();
}
}
getDataByDatasetType(dataset) {
const {
studyInstanceUid,
seriesInstanceUid,
sopInstanceUid,
authorizationHeaders,
wadoRoot,
wadoUri,
} = dataset;
// Retrieve wadors or just try to fetch wadouri
if (!someInvalidStrings(wadoRoot)) {
return wadorsRetriever(
wadoRoot,
studyInstanceUid,
seriesInstanceUid,
sopInstanceUid,
authorizationHeaders
);
} else if (!someInvalidStrings(wadoUri)) {
return fetchIt(wadoUri, { headers: authorizationHeaders });
}
}
*getLoaderIterator(dataset, studies) {
yield this.getLocalData(dataset, studies);
yield this.getDataByImageType(dataset);
yield this.getDataByDatasetType(dataset);
}
findDicomDataPromise(dataset, studies) {
const loaderIterator = this.getLoaderIterator(dataset, studies);
// it returns first valid retriever method.
for (const loader of loaderIterator) {
if (loader) {
return loader;
}
}
// in case of no valid loader
throw new Error('Invalid dicom data loader');
}
})();
export default DicomLoaderService;
+9 -4
View File
@@ -5,11 +5,16 @@ function getWADORSImageUrl(instance, frame) {
return;
}
// We need to sum 1 because WADO-RS frame number is 1-based
frame = frame ? parseInt(frame) + 1 : 1;
// Use null to obtain an imageId which represents the instance
if (frame === null) {
wadorsuri = wadorsuri.replace(/frames\/(\d+)/, '');
} else {
// We need to sum 1 because WADO-RS frame number is 1-based
frame = frame ? parseInt(frame) + 1 : 1;
// Replaces /frame/1 by /frame/{frame}
wadorsuri = wadorsuri.replace(/frames\/(\d+)/, `frames/${frame}`);
// Replaces /frame/1 by /frame/{frame}
wadorsuri = wadorsuri.replace(/frames\/(\d+)/, `frames/${frame}`);
}
return wadorsuri;
}
+3
View File
@@ -8,6 +8,7 @@ import sortBy from './sortBy.js';
import studyMetadataManager from './studyMetadataManager';
import updateMetaDataManager from './updateMetaDataManager.js';
import writeScript from './writeScript.js';
import DicomLoaderService from './dicomLoaderService.js';
const utils = {
guid,
@@ -21,6 +22,7 @@ const utils = {
// Updates WADO-RS metaDataManager
updateMetaDataManager,
DICOMTagDescriptions,
DicomLoaderService,
};
export {
@@ -35,6 +37,7 @@ export {
// Updates WADO-RS metaDataManager
updateMetaDataManager,
DICOMTagDescriptions,
DicomLoaderService,
};
export default utils;
+1
View File
@@ -14,6 +14,7 @@ describe('Top level exports', () => {
// Updates WADO-RS metaDataManager
'updateMetaDataManager',
'DICOMTagDescriptions',
'DicomLoaderService',
].sort();
const exports = Object.keys(utils.default).sort();
+22
View File
@@ -3,6 +3,28 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.51.3](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.51.2...@ohif/ui@0.51.3) (2019-10-01)
### Bug Fixes
* Address issues with touch devices and drag/drop causing crashes ([#982](https://github.com/OHIF/Viewers/issues/982)) ([cf40a83](https://github.com/OHIF/Viewers/commit/cf40a83))
## [0.51.2](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.51.1...@ohif/ui@0.51.2) (2019-09-26)
### Bug Fixes
* 🐛 Set series into active viewport by clicking on thumbnail ([#945](https://github.com/OHIF/Viewers/issues/945)) ([5551f81](https://github.com/OHIF/Viewers/commit/5551f81)), closes [#895](https://github.com/OHIF/Viewers/issues/895) [#895](https://github.com/OHIF/Viewers/issues/895)
## [0.51.1](https://github.com/OHIF/Viewers/compare/@ohif/ui@0.51.0...@ohif/ui@0.51.1) (2019-09-19)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/ui",
"version": "0.51.1",
"version": "0.51.3",
"description": "A set of React components for Medical Imaging Viewers",
"author": "OHIF Contributors",
"license": "MIT",
@@ -99,13 +99,13 @@ class ThumbnailEntry extends Component {
onClick = () => {
if (this.props.onClick) {
this.props.onClick();
this.props.onClick(this.props.displaySetInstanceUid);
}
};
onDoubleClick = () => {
if (this.props.onDoubleClick) {
this.props.onDoubleClick();
this.props.onDoubleClick(this.props.displaySetInstanceUid);
}
};
}
@@ -10,10 +10,11 @@ const isTouchDevice = !!('ontouchstart' in window || navigator.maxTouchPoints);
//
// http://react-dnd.github.io/react-dnd/docs/api/drag-drop-context
export default function viewerbaseDragDropContext(DecoratedClass) {
const backend = isTouchDevice ? TouchBackend({ enableMouseEvents: true }) : HTML5Backend;
const backend = isTouchDevice ? TouchBackend : HTML5Backend;
const opts = isTouchDevice ? { enableMouseEvents: true } : {};
return (props) => (
<DndProvider backend={backend}>
<DndProvider backend={backend} opts={opts}>
<DecoratedClass {...props}/>
</DndProvider>
);
@@ -1,3 +1,4 @@
const webpack = require('webpack');
const path = require('path');
const merge = require('webpack-merge');
const webpackCommon = require('./../../../.webpack/webpack.commonjs.js');
@@ -54,6 +55,9 @@ module.exports = (env, argv) => {
template: `${PUBLIC_DIR}/html-templates/${HTML_TEMPLATE}`,
filename: 'index.html',
}),
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1
})
],
});
};
+6
View File
@@ -71,6 +71,12 @@ module.exports = (env, argv) => {
// Ignore our configuration files
ignore: ['config/*', 'html-templates/*', '.DS_Store'],
},
// Short term solution to make sure GCloud config is available in output
// for our docker implementation
{
from: `${PUBLIC_DIR}/config/google.js`,
to: `${DIST_DIR}/google.js`,
},
// Copy over and rename our target app config file
{
from: `${PUBLIC_DIR}/${APP_CONFIG}`,
+128
View File
@@ -3,6 +3,134 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.2.7](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.2.6...@ohif/viewer@1.2.7) (2019-10-01)
**Note:** Version bump only for package @ohif/viewer
## [1.2.6](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.2.5...@ohif/viewer@1.2.6) (2019-09-27)
**Note:** Version bump only for package @ohif/viewer
## [1.2.5](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.2.4...@ohif/viewer@1.2.5) (2019-09-27)
### Bug Fixes
* version bump issue ([#963](https://github.com/OHIF/Viewers/issues/963)) ([e607ed2](https://github.com/OHIF/Viewers/commit/e607ed2)), closes [#962](https://github.com/OHIF/Viewers/issues/962)
# [2.0.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.2.3...@ohif/viewer@2.0.0) (2019-09-27)
### Bug Fixes
* 🐛 Add DicomLoaderService & FileLoaderService to fix SR, PDF, and SEG support in local file and WADO-RS-only use cases ([#862](https://github.com/OHIF/Viewers/issues/862)) ([e7e1a8a](https://github.com/OHIF/Viewers/commit/e7e1a8a)), closes [#838](https://github.com/OHIF/Viewers/issues/838)
* version bump issue ([#962](https://github.com/OHIF/Viewers/issues/962)) ([c80ea17](https://github.com/OHIF/Viewers/commit/c80ea17))
### BREAKING CHANGES
* DICOM Seg
# [2.0.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.2.3...@ohif/viewer@2.0.0) (2019-09-27)
### Bug Fixes
* 🐛 Add DicomLoaderService & FileLoaderService to fix SR, PDF, and SEG support in local file and WADO-RS-only use cases ([#862](https://github.com/OHIF/Viewers/issues/862)) ([e7e1a8a](https://github.com/OHIF/Viewers/commit/e7e1a8a)), closes [#838](https://github.com/OHIF/Viewers/issues/838)
### BREAKING CHANGES
* DICOM Seg
## [1.2.3](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.2.2...@ohif/viewer@1.2.3) (2019-09-27)
**Note:** Version bump only for package @ohif/viewer
## [1.2.2](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.2.1...@ohif/viewer@1.2.2) (2019-09-27)
**Note:** Version bump only for package @ohif/viewer
## [1.2.1](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.2.0...@ohif/viewer@1.2.1) (2019-09-26)
### Bug Fixes
* google cloud support w/ docker (via env var) ([#958](https://github.com/OHIF/Viewers/issues/958)) ([e375a4a](https://github.com/OHIF/Viewers/commit/e375a4a))
# [1.2.0](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.1.14...@ohif/viewer@1.2.0) (2019-09-26)
### Features
* 🎸 React custom component on toolbar button ([#935](https://github.com/OHIF/Viewers/issues/935)) ([a90605c](https://github.com/OHIF/Viewers/commit/a90605c))
## [1.1.14](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.1.13...@ohif/viewer@1.1.14) (2019-09-26)
### Bug Fixes
* 🐛 Set series into active viewport by clicking on thumbnail ([#945](https://github.com/OHIF/Viewers/issues/945)) ([5551f81](https://github.com/OHIF/Viewers/commit/5551f81)), closes [#895](https://github.com/OHIF/Viewers/issues/895) [#895](https://github.com/OHIF/Viewers/issues/895)
## [1.1.13](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.1.12...@ohif/viewer@1.1.13) (2019-09-26)
### Bug Fixes
* Add some code splitting for PWA build ([#937](https://github.com/OHIF/Viewers/issues/937)) ([8938035](https://github.com/OHIF/Viewers/commit/8938035))
## [1.1.12](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.1.11...@ohif/viewer@1.1.12) (2019-09-26)
**Note:** Version bump only for package @ohif/viewer
## [1.1.11](https://github.com/OHIF/Viewers/compare/@ohif/viewer@1.1.10...@ohif/viewer@1.1.11) (2019-09-23)
**Note:** Version bump only for package @ohif/viewer
+10 -9
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/viewer",
"version": "1.1.11",
"version": "1.2.7",
"description": "OHIF Viewer",
"author": "OHIF Contributors",
"license": "MIT",
@@ -33,7 +33,8 @@
"test:e2e:serve": "serve -l 3000 -s dist",
"test:unit": "jest --watchAll",
"test:unit:ci": "jest --ci --runInBand --collectCoverage",
"prepublishOnly": "yarn run build:package"
"prepublishOnly": "yarn run build:package",
"postpublish": "node -p -e \"require('./package.json').version\" > success_version.txt"
},
"files": [
"dist",
@@ -45,14 +46,14 @@
},
"dependencies": {
"@babel/runtime": "^7.5.5",
"@ohif/core": "^0.50.10",
"@ohif/extension-cornerstone": "^1.2.1",
"@ohif/extension-dicom-html": "^0.50.7",
"@ohif/extension-dicom-microscopy": "^0.50.5",
"@ohif/extension-dicom-pdf": "^0.50.4",
"@ohif/extension-vtk": "^0.50.18",
"@ohif/core": "^1.0.1",
"@ohif/extension-cornerstone": "^2.0.0",
"@ohif/extension-dicom-html": "^1.0.0",
"@ohif/extension-dicom-microscopy": "^0.50.6",
"@ohif/extension-dicom-pdf": "^1.0.0",
"@ohif/extension-vtk": "^0.50.23",
"@ohif/i18n": "^0.50.4",
"@ohif/ui": "^0.51.1",
"@ohif/ui": "^0.51.3",
"@tanem/react-nprogress": "^1.1.25",
"classnames": "^2.2.6",
"core-js": "^3.2.1",
+33 -21
View File
@@ -7,13 +7,7 @@ import { CSSTransition } from 'react-transition-group';
import { connect } from 'react-redux';
import { ViewerbaseDragDropContext } from '@ohif/ui';
import { SignoutCallbackComponent } from 'redux-oidc';
// import asyncComponent from './components/AsyncComponent.js'
import IHEInvokeImageDisplay from './routes/IHEInvokeImageDisplay.js';
import ViewerRouting from './routes/ViewerRouting.js';
import ViewerLocalFileData from './connectedComponents/ViewerLocalFileData.js';
import StudyListRouting from './studylist/StudyListRouting.js';
import StandaloneRouting from './routes/StandaloneRouting.js';
import CallbackPage from './routes/CallbackPage.js';
import asyncComponent from './components/AsyncComponent.js';
import NotFound from './routes/NotFound.js';
import { Bar, Container } from './components/LoadingBar/';
import './OHIFStandaloneViewer.css';
@@ -24,18 +18,32 @@ import './theme-tide.css';
import AppContext from './context/AppContext';
// Dynamic Import Routes (CodeSplitting)
// const IHEInvokeImageDisplay = asyncComponent(() =>
// import('./routes/IHEInvokeImageDisplay.js')
// )
// const ViewerRouting = asyncComponent(() => import('./routes/ViewerRouting.js'))
// const StudyListRouting = asyncComponent(() =>
// import('./studylist/StudyListRouting.js')
// )
// const StandaloneRouting = asyncComponent(() =>
// import('./routes/StandaloneRouting.js')
// )
// const CallbackPage = asyncComponent(() => import('./CallbackPage.js'))
//
const IHEInvokeImageDisplay = asyncComponent(() =>
import(
/* webpackChunkName: "IHEInvokeImageDisplay" */ './routes/IHEInvokeImageDisplay.js'
)
);
const ViewerRouting = asyncComponent(() =>
import(/* webpackChunkName: "ViewerRouting" */ './routes/ViewerRouting.js')
);
const StudyListRouting = asyncComponent(() =>
import(
/* webpackChunkName: "StudyListRouting" */ './studylist/StudyListRouting.js'
)
);
const StandaloneRouting = asyncComponent(() =>
import(
/* webpackChunkName: "StandaloneRouting" */ './routes/StandaloneRouting.js'
)
);
const CallbackPage = asyncComponent(() =>
import(/* webpackChunkName: "CallbackPage" */ './routes/CallbackPage.js')
);
const ViewerLocalFileData = asyncComponent(() =>
import(
/* webpackChunkName: "ViewerLocalFileData" */ './connectedComponents/ViewerLocalFileData.js'
)
);
const reload = () => window.location.reload();
@@ -188,10 +196,14 @@ class OHIFStandaloneViewer extends Component {
classNames="fade"
unmountOnExit
onEnter={() => {
this.setState({ isLoading: true });
this.setState({
isLoading: true,
});
}}
onEntered={() => {
this.setState({ isLoading: false });
this.setState({
isLoading: false,
});
}}
>
{match === null ? (
@@ -38,59 +38,13 @@ const mergeProps = (propsFromState, propsFromDispatch, ownProps) => {
// TODO: Do not display certain options if the current display set
// cannot be displayed using these view types
const buttons = [
/*{
text: 'Acquired',
type: 'command',
icon: 'bars',
active: false,
onClick: () => {
console.warn('Original Acquisition');
const layoutData = setSingleLayoutData(
layout.viewports,
activeViewportIndex,
{ plugin: 'cornerstone' }
);
setLayout({ viewports: layoutData });
},
},
{
text: 'Axial',
icon: 'cube',
active: false,
onClick: () => {
commandsManager.runCommand('axial');
},
},
{
text: 'Sagittal',
icon: 'cube',
active: false,
onClick: () => {
commandsManager.runCommand('sagittal');
},
},
{
text: 'Coronal',
icon: 'cube',
active: false,
onClick: () => {
commandsManager.runCommand('coronal');
},
},*/
{
label: "2D MPR",
icon: "cube",
onClick: () => {
const mpr = () => {
commandsManager.runCommand("mpr2d");
}
}
];
;
return {
buttons
mpr
};
};
@@ -100,4 +54,4 @@ const ConnectedPluginSwitch = connect(
mergeProps
)(PluginSwitch);
export default ConnectedPluginSwitch;
export default ConnectedPluginSwitch;
@@ -1,7 +1,10 @@
import OHIF from '@ohif/core';
import { connect } from 'react-redux';
import { StudyBrowser } from '@ohif/ui';
import cloneDeep from 'lodash.clonedeep';
const { setActiveViewportSpecificData } = OHIF.redux.actions;
// TODO
// - Determine in which display set is active from Redux (activeViewportIndex and layout viewportData)
// - Pass in errors and stack loading progress from Redux
@@ -32,9 +35,21 @@ const mapStateToProps = (state, ownProps) => {
};
};
const mapDispatchToProps = dispatch => {
return {
onThumbnailClick: displaySetInstanceUid => {
dispatch(
setActiveViewportSpecificData({
displaySetInstanceUid,
})
);
},
};
};
const ConnectedStudyBrowser = connect(
mapStateToProps,
null
mapDispatchToProps
)(StudyBrowser);
export default ConnectedStudyBrowser;
@@ -1,11 +1,11 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { ExpandableToolMenu } from '@ohif/ui';
import { ToolbarButton } from '@ohif/ui';
import './PluginSwitch.css';
class PluginSwitch extends Component {
static propTypes = {
buttons: PropTypes.array,
mpr: PropTypes.func,
};
static defaultProps = {};
@@ -13,7 +13,9 @@ class PluginSwitch extends Component {
render() {
return (
<div className="PluginSwitch">
<ExpandableToolMenu buttons={this.props.buttons} label={'View'} />
<ToolbarButton label = "2D MPR"
icon = "cube"
onClick = {this.props.mpr} />
</div>
);
}
@@ -150,45 +150,82 @@ class ToolbarRow extends Component {
}
}
function _getCustomButtonComponent(button, activeButtons) {
const CustomComponent = button.CustomComponent;
const isValidComponent = typeof CustomComponent === 'function';
// Check if its a valid customComponent. Later on an CustomToolbarComponent interface could be implemented.
if (isValidComponent) {
const parentContext = this;
const isActive = activeButtons.includes(button.id);
return (
<CustomComponent
parentContext={parentContext}
toolbarClickCallback={_handleToolbarButtonClick.bind(this)}
button={button}
key={button.id}
activeButtons={activeButtons}
isActive={isActive}
/>
);
}
}
function _getExpandableButtonComponent(button, activeButtons) {
// Iterate over button definitions and update `onClick` behavior
let activeCommand;
const childButtons = button.buttons.map(childButton => {
childButton.onClick = _handleToolbarButtonClick.bind(this, childButton);
if (activeButtons.indexOf(childButton.id) > -1) {
activeCommand = childButton.id;
}
return childButton;
});
return (
<ExpandableToolMenu
key={button.id}
label={button.label}
icon={button.icon}
buttons={childButtons}
activeCommand={activeCommand}
/>
);
}
function _getDefaultButtonComponent(button, activeButtons) {
return (
<ToolbarButton
key={button.id}
label={button.label}
icon={button.icon}
onClick={_handleToolbarButtonClick.bind(this, button)}
isActive={activeButtons.includes(button.id)}
/>
);
}
/**
* Determine which extension buttons should be showing, if they're
* active, and what their onClick behavior should be.
*/
function _getButtonComponents(toolbarButtons, activeButtons) {
return toolbarButtons.map((button, index) => {
let activeCommand = undefined;
const _this = this;
return toolbarButtons.map(button => {
const hasCustomComponent = button.CustomComponent;
const hasNestedButtonDefinitions = button.buttons && button.buttons.length;
if (button.buttons && button.buttons.length) {
// Iterate over button definitions and update `onClick` behavior
const childButtons = button.buttons.map(childButton => {
childButton.onClick = _handleToolbarButtonClick.bind(this, childButton);
if (activeButtons.indexOf(childButton.id) > -1) {
activeCommand = childButton.id;
}
return childButton;
});
return (
<ExpandableToolMenu
key={button.id}
label={button.label}
icon={button.icon}
buttons={childButtons}
activeCommand={activeCommand}
/>
);
if (hasCustomComponent) {
return _getCustomButtonComponent.call(_this, button, activeButtons);
}
return (
<ToolbarButton
key={button.id}
label={button.label}
icon={button.icon}
onClick={_handleToolbarButtonClick.bind(this, button)}
isActive={activeButtons.includes(button.id)}
/>
);
if (hasNestedButtonDefinitions) {
return _getExpandableButtonComponent.call(_this, button, activeButtons);
}
return _getDefaultButtonComponent.call(_this, button, activeButtons);
});
}
@@ -79,6 +79,9 @@ class ViewerLocalFileData extends Component {
studyMetadata.createDisplaySets(sopClassHandlerModules);
studyMetadata.setDisplaySets(study.displaySets);
studyMetadata.forEachDisplaySet(displayset => {
displayset.localFile = true;
});
// Updates WADO-RS metaDataManager
updateMetaDataManager(study);
+19 -153
View File
@@ -1,160 +1,26 @@
import cornerstone from 'cornerstone-core';
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
import * as dcmjs from 'dcmjs';
import FileLoaderService from './localFileLoaders/fileLoaderService';
function datasetsToStudies(datasets) {
const StudyInstanceUIDs = new Set();
datasets.forEach(ds => {
StudyInstanceUIDs.add(ds.StudyInstanceUID);
});
const processFile = async file => {
try {
const fileLoaderService = new FileLoaderService(file);
const imageId = fileLoaderService.addFile(file);
const image = await fileLoaderService.loadFile(file, imageId);
const dataset = await fileLoaderService.getDataset(image, imageId);
const studies = await fileLoaderService.getStudies(dataset, imageId);
const studies = [];
Array.from(StudyInstanceUIDs).forEach(studyInstanceUid => {
const studyDatasets = datasets.filter(
ds => ds.StudyInstanceUID === studyInstanceUid
return studies;
} catch (error) {
console.log(
error.name,
':Error when trying to load and process local files:',
error.message
);
const firstDataset = studyDatasets[0];
const study = {
studyInstanceUid: firstDataset.StudyInstanceUID,
studyDate: firstDataset.StudyDate,
studyTime: firstDataset.StudyTime,
accessionNumber: firstDataset.AccessionNumber,
referringPhysicianName: firstDataset.ReferringPhysicianName,
patientName: firstDataset.PatientName,
patientId: firstDataset.PatientID,
patientBirthdate: firstDataset.PatientBirthDate,
patientSex: firstDataset.PatientSex,
studyId: firstDataset.StudyID,
studyDescription: firstDataset.StudyDescription,
//numberOfStudyRelatedSeries: DICOMWeb.getString(study['00201206']),
//numberOfStudyRelatedInstances: DICOMWeb.getString(study['00201208']),
// modality: DICOMWeb.getString(study['00080060']),
// modalitiesInStudy: DICOMWeb.getString(study['00080061']),
//modalities:
seriesList: [],
};
const SeriesInstanceUIDs = new Set();
studyDatasets.forEach(ds => {
SeriesInstanceUIDs.add(ds.SeriesInstanceUID);
});
Array.from(SeriesInstanceUIDs).forEach(seriesInstanceUid => {
const seriesDatasets = studyDatasets.filter(
ds => ds.SeriesInstanceUID === seriesInstanceUid
);
const SOPInstanceUIDs = new Set();
seriesDatasets.forEach(ds => {
SOPInstanceUIDs.add(ds.SOPInstanceUID);
study.seriesList.push({
seriesInstanceUid: ds.SeriesInstanceUID,
seriesDescription: ds.SeriesDescription,
seriesNumber: ds.SeriesNumber,
instances: [],
});
});
Array.from(SOPInstanceUIDs).forEach(sopInstanceUid => {
const instance = seriesDatasets.find(
a => a.SOPInstanceUID === sopInstanceUid
);
const series = study.seriesList.find(
a => a.seriesInstanceUid === seriesInstanceUid
);
series.instances.push({
sopInstanceUid: instance.SOPInstanceUID,
sopClassUid: instance.SOPClassUID,
rows: instance.Rows,
columns: instance.Columns,
numberOfFrames: instance.NumberOfFrames,
instanceNumber: instance.InstanceNumber,
getImageId: () => instance.imageId, // TODO: Change getImageId to check for instance.imageId property first.
/*imageType: DICOMWeb.getString(instance['00080008']),
modality: DICOMWeb.getString(instance['00080060']),
instanceNumber: DICOMWeb.getNumber(instance['00200013']),
imagePositionPatient: DICOMWeb.getString(instance['00200032']),
imageOrientationPatient: DICOMWeb.getString(instance['00200037']),
frameOfReferenceUID: DICOMWeb.getString(instance['00200052']),
sliceLocation: DICOMWeb.getNumber(instance['00201041']),
samplesPerPixel: DICOMWeb.getNumber(instance['00280002']),
photometricInterpretation: DICOMWeb.getString(instance['00280004']),
planarConfiguration: DICOMWeb.getNumber(instance['00280006']),
pixelSpacing: DICOMWeb.getString(instance['00280030']),
pixelAspectRatio: DICOMWeb.getString(instance['00280034']),
bitsAllocated: DICOMWeb.getNumber(instance['00280100']),
bitsStored: DICOMWeb.getNumber(instance['00280101']),
highBit: DICOMWeb.getNumber(instance['00280102']),
pixelRepresentation: DICOMWeb.getNumber(instance['00280103']),
smallestPixelValue: DICOMWeb.getNumber(instance['00280106']),
largestPixelValue: DICOMWeb.getNumber(instance['00280107']),
windowCenter: DICOMWeb.getString(instance['00281050']),
windowWidth: DICOMWeb.getString(instance['00281051']),
rescaleIntercept: DICOMWeb.getNumber(instance['00281052']),
rescaleSlope: DICOMWeb.getNumber(instance['00281053']),
rescaleType: DICOMWeb.getNumber(instance['00281054']),
sourceImageInstanceUid: getSourceImageInstanceUid(instance),
laterality: DICOMWeb.getString(instance['00200062']),
viewPosition: DICOMWeb.getString(instance['00185101']),
acquisitionDateTime: DICOMWeb.getString(instance['0008002A']),
frameIncrementPointer: getFrameIncrementPointer(instance['00280009']),
frameTime: DICOMWeb.getNumber(instance['00181063']),
frameTimeVector: parseFloatArray(
DICOMWeb.getString(instance['00181065'])
),
sliceThickness: DICOMWeb.getNumber(instance['00180050']),
spacingBetweenSlices: DICOMWeb.getString(instance['00180088']),
lossyImageCompression: DICOMWeb.getString(instance['00282110']),
derivationDescription: DICOMWeb.getString(instance['00282111']),
lossyImageCompressionRatio: DICOMWeb.getString(instance['00282112']),
lossyImageCompressionMethod: DICOMWeb.getString(instance['00282114']),
echoNumber: DICOMWeb.getString(instance['00180086']),
contrastBolusAgent: DICOMWeb.getString(instance['00180010']),
radiopharmaceuticalInfo: getRadiopharmaceuticalInfo(instance),
wadouri: WADOProxy.convertURL(wadouri, server),
wadorsuri: WADOProxy.convertURL(wadorsuri, server),*/
});
});
});
studies.push(study);
});
console.warn(studies);
return studies;
}
}
};
export default async function filesToStudies(files) {
const imagePromises = files.map(file => {
const imageId = cornerstoneWADOImageLoader.wadouri.fileManager.add(file);
return cornerstone
.loadAndCacheImage(imageId)
.catch(error => console.warn(error));
});
const processFilesPromises = files.map(processFile);
const studies = await Promise.all(processFilesPromises);
const images = await Promise.all(imagePromises);
const datasets = [];
images.forEach(image => {
if (!image || !image.data) {
return;
}
const arrayBuffer = image.data.byteArray.buffer;
const dicomData = dcmjs.data.DicomMessage.readFile(arrayBuffer);
const dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(
dicomData.dict
);
dataset._meta = dcmjs.data.DicomMetaDictionary.namifyDataset(
dicomData.meta
);
dataset.imageId = image.imageId;
datasets.push(dataset);
});
return datasetsToStudies(datasets);
return FileLoaderService.groupSeries(studies.flat());
}
@@ -0,0 +1,192 @@
import * as dcmjs from 'dcmjs';
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
import FileLoader from './fileLoader';
const DICOMFileLoader = new (class extends FileLoader {
fileType = 'application/dicom';
loadFile(file, imageId) {
return cornerstoneWADOImageLoader.wadouri.loadFileRequest(imageId);
}
getDataset(image, imageId) {
let dataset = {};
try {
const dicomData = dcmjs.data.DicomMessage.readFile(image);
dataset = dcmjs.data.DicomMetaDictionary.naturalizeDataset(
dicomData.dict
);
dataset._meta = dcmjs.data.DicomMetaDictionary.namifyDataset(
dicomData.meta
);
} catch (e) {
console.log('Error on getting dicom file dataset. It defaults to empty');
}
// Set imageId on dataset to be consumed later on
dataset.imageId = imageId;
return dataset;
}
getStudies(dataset, imageId) {
return this.getStudyFromDataset(dataset);
}
getStudyFromDataset(dataset = {}) {
const {
StudyInstanceUID,
StudyDate,
StudyTime,
AccessionNumber,
ReferringPhysicianName,
PatientName,
PatientID,
PatientBirthDate,
PatientSex,
StudyID,
StudyDescription,
/*
NumberOfStudyRelatedSeries,
NumberOfStudyRelatedInstances,
Modality,
ModalitiesInStudy,
*/
SeriesInstanceUID,
SeriesDescription,
SeriesNumber,
SOPInstanceUID,
SOPClassUID,
Rows,
Columns,
NumberOfFrames,
InstanceNumber,
imageId,
Modality,
/*ImageType,
InstanceNumber,
ImagePositionPatient,
ImageOrientationPatient,
FrameOfReferenceUID,
SliceLocation,
SamplesPerPixel,
PhotometricInterpretation,
PlanarConfiguration,
PixelSpacing,
PixelAspectRatio,
BitsAllocated,
BitsStored,
HighBit,
PixelRepresentation,
SmallestPixelValue,
LargestPixelValue,
WindowCenter,
WindowWidth,
RescaleIntercept,
RescaleSlope,
RescaleType,
Laterality,
ViewPosition,
AcquisitionDateTime,
FrameIncrementPointer,
FrameTime,
FrameTimeVector,
SliceThickness,
SpacingBetweenSlices,
LossyImageCompression,
DerivationDescription,
LossyImageCompressionRatio,
LossyImageCompressionMethod,
EchoNumber,
ContrastBolusAgent,
*/
} = dataset;
const instance = {
sopInstanceUid: SOPInstanceUID,
sopClassUid: SOPClassUID,
rows: Rows,
columns: Columns,
numberOfFrames: NumberOfFrames,
instanceNumber: InstanceNumber,
url: imageId,
modality: Modality,
/*
TODO: in case necessary to uncoment this block, double check every property
imageType: ImageType || DICOMWeb.getString(dataset['00080008']),
instanceNumber: InstanceNumber || DICOMWeb.getNumber(dataset['00200013']),
imagePositionPatient: ImagePositionPatient || DICOMWeb.getString(dataset['00200032']),
imageOrientationPatient: ImageOrientationPatient || DICOMWeb.getString(dataset['00200037']),
frameOfReferenceUID: FrameOfReferenceUID || DICOMWeb.getString(dataset['00200052']),
sliceLocation: SliceLocation || DICOMWeb.getNumber(dataset['00201041']),
samplesPerPixel: SamplesPerPixel || DICOMWeb.getNumber(dataset['00280002']),
photometricInterpretation: PhotometricInterpretation || DICOMWeb.getString(dataset['00280004']),
planarConfiguration: PlanarConfiguration || DICOMWeb.getNumber(dataset['00280006']),
pixelSpacing: PixelSpacing || DICOMWeb.getString(dataset['00280030']),
pixelAspectRatio: PixelAspectRatio || DICOMWeb.getString(dataset['00280034']),
bitsAllocated: BitsAllocated || DICOMWeb.getNumber(dataset['00280100']),
bitsStored: BitsStored || DICOMWeb.getNumber(dataset['00280101']),
highBit: HighBit || DICOMWeb.getNumber(dataset['00280102']),
pixelRepresentation: PixelRepresentation || DICOMWeb.getNumber(dataset['00280103']),
smallestPixelValue: SmallestPixelValue || DICOMWeb.getNumber(dataset['00280106']),
largestPixelValue: LargestPixelValue || DICOMWeb.getNumber(dataset['00280107']),
windowCenter: WindowCenter || DICOMWeb.getString(dataset['00281050']),
windowWidth: WindowWidth || DICOMWeb.getString(dataset['00281051']),
rescaleIntercept: RescaleIntercept || DICOMWeb.getNumber(dataset['00281052']),
rescaleSlope: RescaleSlope || DICOMWeb.getNumber(dataset['00281053']),
rescaleType: RescaleType || DICOMWeb.getNumber(dataset['00281054']),
sourceImageInstanceUid: getSourceImageInstanceUid(dataset),
laterality: Laterality || DICOMWeb.getString(dataset['00200062']),
viewPosition: ViewPosition || DICOMWeb.getString(dataset['00185101']),
acquisitionDateTime: AcquisitionDateTime || DICOMWeb.getString(dataset['0008002A']),
frameIncrementPointer: FrameIncrementPointer || getFrameIncrementPointer(dataset['00280009']),
frameTime: FrameTime || DICOMWeb.getNumber(dataset['00181063']),
frameTimeVector: FrameTimeVector || parseFloatArray(
DICOMWeb.getString(dataset['00181065'])
),
sliceThickness: SliceThickness || DICOMWeb.getNumber(dataset['00180050']),
spacingBetweenSlices: SpacingBetweenSlices || DICOMWeb.getString(dataset['00180088']),
lossyImageCompression: LossyImageCompression || DICOMWeb.getString(dataset['00282110']),
derivationDescription: DerivationDescription || DICOMWeb.getString(dataset['00282111']),
lossyImageCompressionRatio: LossyImageCompressionRatio || DICOMWeb.getString(dataset['00282112']),
lossyImageCompressionMethod: LossyImageCompressionMethod || DICOMWeb.getString(dataset['00282114']),
echoNumber: EchoNumber || DICOMWeb.getString(dataset['00180086']),
contrastBolusAgent: ContrastBolusAgent || DICOMWeb.getString(dataset['00180010']),
radiopharmaceuticalInfo: getRadiopharmaceuticalInfo(dataset),
wadouri: WADOProxy.convertURL(wadouri, server),
wadorsuri: WADOProxy.convertURL(wadorsuri, server),*/
};
const series = {
seriesInstanceUid: SeriesInstanceUID,
seriesDescription: SeriesDescription,
seriesNumber: SeriesNumber,
instances: [instance],
};
const study = {
studyInstanceUid: StudyInstanceUID,
studyDate: StudyDate,
studyTime: StudyTime,
accessionNumber: AccessionNumber,
referringPhysicianName: ReferringPhysicianName,
patientName: PatientName,
patientId: PatientID,
patientBirthdate: PatientBirthDate,
patientSex: PatientSex,
studyId: StudyID,
studyDescription: StudyDescription,
/*
TODO: in case necessary to uncomment this block, double check every property
numberOfStudyRelatedSeries: NumberOfStudyRelatedSeries || DICOMWeb.getString(dataset['00201206']),
numberOfStudyRelatedInstances: NumberOfStudyRelatedInstances || DICOMWeb.getString(dataset['00201208']),
modality: Modality || DICOMWeb.getString(dataset['00080060']),
modalitiesInStudy: ModalitiesInStudy || DICOMWeb.getString(dataset['00080061']),
modalities:
*/
seriesList: [series],
};
return study;
}
})();
export default DICOMFileLoader;
@@ -0,0 +1,6 @@
export default class FileLoader {
fileType;
loadFile(file, imageId) { }
getDataset(image, imageId) { }
getStudies(dataset, imageId) { }
}
@@ -0,0 +1,82 @@
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
import FileLoader from './fileLoader';
import PDFFileLoader from './pdfFileLoader';
import DICOMFileLoader from './dicomFileLoader';
class FileLoaderService extends FileLoader {
fileType;
loader;
constructor(file) {
super();
const fileType = file && file.type;
this.loader = this.getLoader(fileType);
this.fileType = this.loader.fileType;
}
static groupSeries(studies) {
const groupBy = (list, groupByKey, listKey) => {
let nonKeyCounter = 1;
return list.reduce((acc, obj) => {
let key = obj[groupByKey];
const list = obj[listKey];
// in case key not found, group it using counter
key = !!key ? key : '' + nonKeyCounter++;
if (!acc[key]) {
acc[key] = { ...obj };
acc[key][listKey] = [];
}
acc[key][listKey].push(...list);
return acc;
}, {});
};
const studiesGrouped = Object.values(
groupBy(studies, 'studyInstanceUid', 'seriesList')
);
const result = studiesGrouped.map(studyGroup => {
const seriesGrouped = groupBy(
studyGroup.seriesList,
'seriesInstanceUid',
'instances'
);
studyGroup.seriesList = Object.values(seriesGrouped);
return studyGroup;
});
return result;
}
addFile(file) {
return cornerstoneWADOImageLoader.wadouri.fileManager.add(file);
}
loadFile(file, imageId) {
return this.loader.loadFile(file, imageId);
}
getDataset(image, imageId) {
return this.loader.getDataset(image, imageId);
}
getStudies(dataset, imageId) {
return this.loader.getStudies(dataset, imageId);
}
getLoader(fileType) {
if (fileType === 'application/pdf') {
return PDFFileLoader;
} else {
// Default to dicom loader
return DICOMFileLoader;
}
}
}
export default FileLoaderService;
@@ -0,0 +1,58 @@
import cornerstoneWADOImageLoader from 'cornerstone-wado-image-loader';
import FileLoader from './fileLoader';
const PDFFileLoader = new (class extends FileLoader {
fileType = 'application/pdf';
loadFile(file, imageId) {
return cornerstoneWADOImageLoader.wadouri.loadFileRequest(imageId);
}
getDataset(image, imageId) {
const dataset = {};
dataset.imageId = image.imageId || imageId;
return dataset;
}
getStudies(dataset, imageId) {
return this.getDefaultStudy(imageId);
}
getDefaultStudy(imageId) {
const study = {
studyInstanceUid: '',
studyDate: '',
studyTime: '',
accessionNumber: '',
referringPhysicianName: '',
patientName: '',
patientId: '',
patientBirthdate: '',
patientSex: '',
studyId: '',
studyDescription: '',
seriesList: [
{
seriesInstanceUid: '',
seriesDescription: '',
seriesNumber: '',
instances: [
{
sopInstanceUid: '',
sopClassUid: '1.2.840.10008.5.1.4.1.1.104.1',
rows: '',
columns: '',
numberOfFrames: 0,
instanceNumber: 1,
getImageId: () => imageId,
isLocalFile: true,
},
],
},
],
};
return study;
}
})();
export default PDFFileLoader;