Re-add extensions as top level dir

This commit is contained in:
dannyrb committed 2019-04-16 12:00:06 -04:00
1 parent 77ec71a12a
commit 780f84af89
86 files changed
+51115

No files matched your search

@@ -0,0 +1,14 @@
{
"presets": [
["@babel/preset-env", {
"targets": {
"ie": "11"
}
}],
"@babel/preset-react"
],
"plugins": [
"@babel/plugin-proposal-class-properties",
"@babel/plugin-transform-runtime"
]
}
@@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
@@ -0,0 +1,16 @@
{
"extends": [
"react-app",
"eslint:recommended",
"plugin:react/recommended"
],
"parser": "babel-eslint",
"env": {
"jest": true
},
"settings": {
"react": {
"version": "detect",
},
},
}
@@ -0,0 +1,23 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.
# dependencies
node_modules
# builds
build
dist
.rpt2_cache
# misc
.DS_Store
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.idea
@@ -0,0 +1,3 @@
{
"singleQuote": true
}
@@ -0,0 +1,4 @@
language: node_js
node_js:
- 9
- 8
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Open Health Imaging Foundation
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,86 @@
{
"name": "ohif-dicom-html-extension",
"version": "0.0.2",
"description": "OHIF extension for rendering structured reports to HTML",
"author": "OHIF",
"license": "MIT",
"repository": "OHIF/Viewers",
"main": "dist/index.umd.js",
"module": "dist/index.es.js",
"jsnext:main": "dist/index.es.js",
"engines": {
"node": ">=8",
"npm": ">=5"
},
"scripts": {
"build": "rollup -c",
"prepublishOnly": "npm run build",
"start": "rollup -c -w",
"lint": "eslint -c .eslintrc --fix src && prettier --single-quote --write src/**/*.{js,jsx,json,css}"
},
"peerDependencies": {
"dcmjs": "^0.3.3",
"ohif-core": "^0.2.6",
"prop-types": "^15.6.2",
"react": "^15.0.0 || ^16.0.0",
"react-dom": "^15.0.0 || ^16.0.0"
},
"devDependencies": {
"@babel/core": "^7.2.2",
"@babel/plugin-external-helpers": "^7.2.0",
"@babel/plugin-proposal-class-properties": "^7.2.3",
"@babel/plugin-transform-runtime": "^7.2.0",
"@babel/preset-env": "^7.2.3",
"@babel/preset-react": "^7.0.0",
"@svgr/rollup": "^4.1.0",
"babel-eslint": "^9.0.0",
"cross-env": "^5.2.0",
"eslint": "5.6.0",
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-node": "^8.0.0",
"eslint-plugin-promise": "^4.0.1",
"eslint-plugin-react": "^7.11.1",
"gh-pages": "^2.0.1",
"husky": "^1.3.1",
"lint-staged": "^8.1.0",
"lodash.clonedeep": "^4.5.0",
"prettier": "^1.15.3",
"react": "^16.6.3",
"react-dom": "^16.6.3",
"react-scripts": "^2.1.2",
"rollup": "^0.68.2",
"rollup-plugin-babel": "^4.2.0",
"rollup-plugin-commonjs": "^9.2.0",
"rollup-plugin-node-builtins": "^2.1.2",
"rollup-plugin-node-resolve": "^4.0.0",
"rollup-plugin-peer-deps-external": "^2.2.0",
"rollup-plugin-postcss": "^1.6.3",
"rollup-plugin-url": "^2.1.0",
"stylelint": "^9.9.0",
"stylelint-config-recommended": "^2.1.0",
"stylus": "^0.54.5"
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"src/**/*.{js,jsx,json,css}": [
"prettier --single-quote --write",
"git add"
]
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
],
"files": [
"dist"
],
"dependencies": {
"@babel/runtime": "^7.2.0"
}
}
@@ -0,0 +1,55 @@
import babel from 'rollup-plugin-babel'
import commonjs from 'rollup-plugin-commonjs'
import external from 'rollup-plugin-peer-deps-external'
import postcss from 'rollup-plugin-postcss'
import resolve from 'rollup-plugin-node-resolve'
import url from 'rollup-plugin-url'
import svgr from '@svgr/rollup'
import pkg from './package.json'
// Deal with https://github.com/rollup/rollup-plugin-commonjs/issues/297
import builtins from 'rollup-plugin-node-builtins';
const globals = {
'react': 'React',
'react-dom': 'ReactDOM',
'prop-types': 'PropTypes',
'ohif-core': 'OHIF',
'dcmjs': 'dcmjs'
};
export default {
input: 'src/index.js',
output: [
{
file: pkg.main,
format: 'umd',
name: 'ohif-dicom-pdf-extension',
sourcemap: true,
globals
},
{
file: pkg.module,
format: 'es',
sourcemap: true,
globals
}
],
plugins: [
builtins(),
external(),
postcss({
modules: false
}),
url(),
svgr(),
babel({
exclude: 'node_modules/**',
plugins: [ '@babel/external-helpers' ],
externalHelpers: true,
runtimeHelpers: true
}),
resolve(),
commonjs()
],
external: Object.keys(pkg.peerDependencies || {}),
}
@@ -0,0 +1,11 @@
:root {
--text-color-primary: white;
}
.DicomHtmlViewport {
padding: 20px;
overflow-y: scroll;
width: 100%;
height: 100%;
color: var(--text-color-primary);
}
@@ -0,0 +1,242 @@
import React, { Component } from 'react';
import * as dcmjs from 'dcmjs';
import TypedArrayProp from './TypedArrayProp';
import './DicomHtmlViewport.css';
function getRelationshipString(data) {
switch (data.RelationshipType) {
case 'HAS CONCEPT MOD':
return 'Concept modifier: ';
case 'HAS OBS CONTEXT':
return 'Observation context: ';
default:
return '';
}
}
const getMeaningString = data => {
if (data.ConceptNameCodeSequence) {
const { CodeMeaning } = data.ConceptNameCodeSequence;
return `${CodeMeaning} = `;
}
return '';
};
function getValueString(data) {
switch (data.ValueType) {
case 'CODE':
const {
CodeMeaning,
CodeValue,
CodingSchemeDesignator
} = data.ConceptNameCodeSequence;
return `${CodeMeaning} (${CodeValue}, ${CodingSchemeDesignator})`;
case 'PNAME':
return data.PersonName;
case 'TEXT':
return data.TextValue;
case 'UIDREF':
return data.UID;
case 'NUM':
const { MeasuredValueSequence } = data;
const numValue = MeasuredValueSequence.NumericValue;
const codeValue =
MeasuredValueSequence.MeasurementUnitsCodeSequence.CodeValue;
return `${numValue} ${codeValue}`;
}
}
function constructPlainValue(data) {
const value = getValueString(data);
if (value) {
return getRelationshipString(data) + getMeaningString(data) + value;
}
}
function constructContentSequence(data, header) {
if (!data.ContentSequence) {
return;
}
const items = data.ContentSequence.map(item => parseContent(item)).filter(
item => item
);
if (!items.length) {
return;
}
const result = {
items
};
if (header) {
result.header = header;
}
return result;
}
function parseContent(data) {
if (data.ValueType) {
if (data.ValueType === 'CONTAINER') {
const header = data.ConceptNameCodeSequence.CodeMeaning;
return constructContentSequence(data, header);
}
return constructPlainValue(data);
}
if (data.ContentSequence) {
return constructContentSequence(data);
}
}
const { DicomMetaDictionary, DicomMessage } = dcmjs.data;
function getMainData(data) {
const root = [];
const patientValue = `${data.PatientName} (${data.PatientSex}, #${
data.PatientID
})`;
root.push(getMainDataItem('Patient', patientValue));
const studyValue = data.StudyDescription;
root.push(getMainDataItem('Study', studyValue));
const seriesValue = `${data.SeriesDescription} (#${data.SeriesNumber})`;
root.push(getMainDataItem('Series', seriesValue));
const manufacturerValue = `${data.Manufacturer} (${
data.ManufacturerModelName
}, #${data.DeviceSerialNumber})`;
root.push(getMainDataItem('Manufacturer', manufacturerValue));
const mainDataObjects = {
CompletionFlag: 'Completion flag',
VerificationFlag: 'Verification flag'
};
Object.keys(mainDataObjects).forEach(key => {
if (!data[key]) {
return;
}
const item = getMainDataItem(mainDataObjects[key], data[key]);
root.push(item);
});
// TODO: Format these dates
const contentDateTimeValue = `${data.ContentDate} ${data.ContentTime}`;
root.push(getMainDataItem('Content Date/Time', contentDateTimeValue));
root.push();
return <div>{root}</div>;
}
const getContentSequence = (data, level = 1) => {
let header;
if (data.ConceptNameCodeSequence) {
const {
CodeMeaning,
CodeValue,
CodingSchemeDesignator
} = data.ConceptNameCodeSequence;
header = `${CodeMeaning} (${CodeValue} - ${CodingSchemeDesignator})`;
}
const root = [];
if (header) {
const HeaderDynamicLevel = `h${level}`;
root.push(<HeaderDynamicLevel key={header}>{header}</HeaderDynamicLevel>);
}
Object.keys(data).forEach(key => {
const value = data[key];
let content;
if (value instanceof Object) {
content = getContentSequence(value, level + 1);
} else {
content = (
<div key={key}>
{key} - {data[key]}
</div>
);
}
root.push(content);
});
return <div>{root}</div>;
};
function getMainDataItem(key, value) {
return (
<div key={key}>
<b>{key}</b>: {value}
</div>
);
}
class DicomHtmlViewport extends Component {
state = {
content: null,
error: null
};
static propTypes = {
byteArray: TypedArrayProp.uint8
};
componentDidMount() {
const dataSet = this.setContentFromByteArray(this.props.byteArray);
}
setContentFromByteArray(byteArray) {
const arrayBuffer = byteArray.buffer;
const dicomData = DicomMessage.readFile(arrayBuffer);
const dataset = DicomMetaDictionary.naturalizeDataset(dicomData.dict);
dataset._meta = DicomMetaDictionary.namifyDataset(dicomData.meta);
const mainData = getMainData(dataset);
const contentSequence = getContentSequence(dataset);
const content = (
<>
{mainData}
{contentSequence}
</>
);
this.setState({
content
});
}
render() {
return (
<div className={'DicomHtmlViewport'}>
{this.state.content}
{this.state.error && <h2>{JSON.stringify(this.state.error)}</h2>}
</div>
);
}
}
export default DicomHtmlViewport;
@@ -0,0 +1,27 @@
import OHIFDicomHtmlViewport from './OHIFDicomHtmlViewport.js';
import OHIFDicomHtmlSopClassHandler from './OHIFDicomHtmlSopClassHandler.js';
export default class OHIFDicomHtmlExtension {
/**
* Extension ID is a unique id, might be used for namespacing extension specific redux actions/reducers (?)
*/
getExtensionId() {
return 'html';
}
getViewportModule() {
return OHIFDicomHtmlViewport;
}
getSopClassHandler() {
return OHIFDicomHtmlSopClassHandler;
}
getPanelModuleDefinition() {
return null;
}
getToolbarModuleDefinition() {
return null;
}
}
@@ -0,0 +1,41 @@
import OHIF from 'ohif-core';
const { plugins, utils } = OHIF;
const { PLUGIN_TYPES } = plugins;
// TODO: Should probably use dcmjs for this
const SOP_CLASS_UIDS = {
BASIC_TEXT_SR: '1.2.840.10008.5.1.4.1.1.88.11',
ENHANCED_SR: '1.2.840.10008.5.1.4.1.1.88.22',
COMPREHENSIVE_SR: '1.2.840.10008.5.1.4.1.1.88.33',
PROCEDURE_LOG_STORAGE: '1.2.840.10008.5.1.4.1.1.88.40',
MAMMOGRAPHY_CAD_SR: '1.2.840.10008.5.1.4.1.1.88.50',
CHEST_CAD_SR: '1.2.840.10008.5.1.4.1.1.88.65',
X_RAY_RADIATION_DOSE_SR: '1.2.840.10008.5.1.4.1.1.88.67'
};
const sopClassUids = Object.values(SOP_CLASS_UIDS);
// TODO: Handle the case where there is more than one SOP Class Handler for the
// same SOP Class
const OHIFDicomHtmlSopClassHandler = {
id: 'OHIFDicomHtmlSopClassHandler',
type: PLUGIN_TYPES.SOP_CLASS_HANDLER,
sopClassUids,
getDisplaySetFromSeries(series, study, dicomWebClient, authorizationHeaders) {
const instance = series.getFirstInstance();
return {
plugin: 'html',
displaySetInstanceUid: utils.guid(),
wadoRoot: study.getData().wadoRoot,
wadoUri: instance.getData().wadouri,
sopInstanceUid: instance.getSOPInstanceUID(),
seriesInstanceUid: series.getSeriesInstanceUID(),
studyInstanceUid: study.getStudyInstanceUID(),
authorizationHeaders
};
}
};
export default OHIFDicomHtmlSopClassHandler;
@@ -0,0 +1,84 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import DicomHtmlViewport from './DicomHtmlViewport';
class OHIFDicomHtmlViewport extends Component {
static propTypes = {
studies: PropTypes.object,
displaySet: PropTypes.object,
viewportIndex: PropTypes.number
};
state = {
byteArray: null,
error: null
};
componentDidMount() {
const { displaySet } = this.props.viewportData;
const {
studyInstanceUid,
seriesInstanceUid,
sopInstanceUid,
wadoRoot,
wadoUri,
authorizationHeaders
} = displaySet;
this.retrieveDicomData(
studyInstanceUid,
seriesInstanceUid,
sopInstanceUid,
wadoRoot,
wadoUri,
authorizationHeaders
).then(
byteArray => {
this.setState({
byteArray
});
},
error => {
this.setState({
error
});
throw new Error(error);
}
);
}
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 (
<>
{this.state.byteArray && (
<DicomHtmlViewport byteArray={this.state.byteArray} />
)}
{this.state.error && <h2>{JSON.stringify(this.state.error)}</h2>}
</>
);
}
}
export default OHIFDicomHtmlViewport;
@@ -0,0 +1,205 @@
// https://github.com/facebook/prop-types/issues/69
export const TypedArrayProp = {
any: (props, propName, componentName) => {
let obj = props[propName];
if (
!(
obj instanceof Float64Array ||
obj instanceof Float32Array ||
obj instanceof Int32Array ||
obj instanceof Int16Array ||
obj instanceof Int8Array ||
obj instanceof Uint32Array ||
obj instanceof Uint16Array ||
obj instanceof Uint8Array ||
obj instanceof Uint8ClampedArray
)
) {
return new Error(
'Invalid prop `' +
propName +
'` supplied to' +
' `' +
componentName +
'`. Expected a typed array.'
);
}
},
float64: (props, propName, componentName) => {
if (!(props[propName] instanceof Float64Array)) {
return new Error(
'Invalid prop `' +
propName +
'` supplied to' +
' `' +
componentName +
'`. Expected a Float64Array.'
);
}
},
float32: (props, propName, componentName) => {
if (!(props[propName] instanceof Float32Array)) {
return new Error(
'Invalid prop `' +
propName +
'` supplied to' +
' `' +
componentName +
'`. Expected a Float32Array.'
);
}
},
float: (props, propName, componentName) => {
if (
!(
props[propName] instanceof Float64Array ||
props[propName] instanceof Float32Array
)
) {
return new Error(
'Invalid prop `' +
propName +
'` supplied to' +
' `' +
componentName +
'`. Expected a Float32Array or Float64Array.'
);
}
},
int32: (props, propName, componentName) => {
if (!(props[propName] instanceof Int32Array)) {
return new Error(
'Invalid prop `' +
propName +
'` supplied to' +
' `' +
componentName +
'`. Expected an Int32Array.'
);
}
},
int16: (props, propName, componentName) => {
if (!(props[propName] instanceof Int16Array)) {
return new Error(
'Invalid prop `' +
propName +
'` supplied to' +
' `' +
componentName +
'`. Expected an In16Array.'
);
}
},
int8: (props, propName, componentName) => {
if (!(props[propName] instanceof Int8Array)) {
return new Error(
'Invalid prop `' +
propName +
'` supplied to' +
' `' +
componentName +
'`. Expected an Int8Array.'
);
}
},
int: (props, propName, componentName) => {
if (
!(
props[propName] instanceof Int32Array ||
props[propName] instanceof Int16Array ||
props[propName] instanceof Int8Array
)
) {
return new Error(
'Invalid prop `' +
propName +
'` supplied to' +
' `' +
componentName +
'`. Expected an Int32Array, In16Array, or Int8Array.'
);
}
},
uint32: (props, propName, componentName) => {
if (!(props[propName] instanceof Uint32Array)) {
return new Error(
'Invalid prop `' +
propName +
'` supplied to' +
' `' +
componentName +
'`. Expected a Uint32Array.'
);
}
},
uint16: (props, propName, componentName) => {
if (!(props[propName] instanceof Uint16Array)) {
return new Error(
'Invalid prop `' +
propName +
'` supplied to' +
' `' +
componentName +
'`. Expected a Uint16Array.'
);
}
},
uint8: (props, propName, componentName) => {
if (!(props[propName] instanceof Uint8Array)) {
return new Error(
'Invalid prop `' +
propName +
'` supplied to' +
' `' +
componentName +
'`. Expected a Uint8Array.'
);
}
},
uint8clamped: (props, propName, componentName) => {
if (!(props[propName] instanceof Uint8ClampedArray)) {
return new Error(
'Invalid prop `' +
propName +
'` supplied to' +
' `' +
componentName +
'`. Expected a Uint8ClampedArray.'
);
}
},
uint: (props, propName, componentName) => {
if (
!(
props[propName] instanceof Uint32Array ||
props[propName] instanceof Uint16Array ||
props[propName] instanceof Uint8Array ||
props[propName] instanceof Uint8ClampedArray
)
) {
return new Error(
'Invalid prop `' +
propName +
'` supplied to' +
' `' +
componentName +
'`. Expected a Uint32Array, Uint16Array, Uint8Array, or Uint8ClampedArray.'
);
}
}
};
export { TypedArrayProp as default };
@@ -0,0 +1,3 @@
import OHIFDicomHtmlExtension from './OHIFDicomHtmlExtension.js';
export default OHIFDicomHtmlExtension;
File diff suppressed because it is too large. Load diff