From 9e27d99256823806b804df31a7781879259230a2 Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Fri, 17 Mar 2023 13:55:34 -0400
Subject: [PATCH 01/51] skeleton for dicom-microscopy extension
---
extensions/dicom-microscopy/.gitignore | 104 +++++++++++++++
extensions/dicom-microscopy/.prettierrc | 8 ++
.../dicom-microscopy/.webpack/webpack.prod.js | 63 +++++++++
extensions/dicom-microscopy/LICENSE | 9 ++
extensions/dicom-microscopy/README.md | 10 ++
extensions/dicom-microscopy/babel.config.js | 44 ++++++
extensions/dicom-microscopy/package.json | 73 ++++++++++
extensions/dicom-microscopy/src/id.js | 5 +
extensions/dicom-microscopy/src/index.tsx | 126 ++++++++++++++++++
9 files changed, 442 insertions(+)
create mode 100644 extensions/dicom-microscopy/.gitignore
create mode 100644 extensions/dicom-microscopy/.prettierrc
create mode 100644 extensions/dicom-microscopy/.webpack/webpack.prod.js
create mode 100644 extensions/dicom-microscopy/LICENSE
create mode 100644 extensions/dicom-microscopy/README.md
create mode 100644 extensions/dicom-microscopy/babel.config.js
create mode 100644 extensions/dicom-microscopy/package.json
create mode 100644 extensions/dicom-microscopy/src/id.js
create mode 100644 extensions/dicom-microscopy/src/index.tsx
diff --git a/extensions/dicom-microscopy/.gitignore b/extensions/dicom-microscopy/.gitignore
new file mode 100644
index 0000000000..67045665db
--- /dev/null
+++ b/extensions/dicom-microscopy/.gitignore
@@ -0,0 +1,104 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# TypeScript v1 declaration files
+typings/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env
+.env.test
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+
+# Next.js build output
+.next
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and *not* Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
diff --git a/extensions/dicom-microscopy/.prettierrc b/extensions/dicom-microscopy/.prettierrc
new file mode 100644
index 0000000000..b80ec6b347
--- /dev/null
+++ b/extensions/dicom-microscopy/.prettierrc
@@ -0,0 +1,8 @@
+{
+ "trailingComma": "es5",
+ "printWidth": 80,
+ "proseWrap": "always",
+ "tabWidth": 2,
+ "semi": true,
+ "singleQuote": true
+}
diff --git a/extensions/dicom-microscopy/.webpack/webpack.prod.js b/extensions/dicom-microscopy/.webpack/webpack.prod.js
new file mode 100644
index 0000000000..e4e19f234a
--- /dev/null
+++ b/extensions/dicom-microscopy/.webpack/webpack.prod.js
@@ -0,0 +1,63 @@
+const path = require('path');
+const pkg = require('../package.json');
+
+const outputFile = 'index.umd.js';
+const rootDir = path.resolve(__dirname, '../');
+const outputFolder = path.join(__dirname, `../dist/`);
+
+// Todo: add ESM build for the extension in addition to umd build
+
+const config = {
+ mode: 'production',
+ entry: rootDir + '/' + pkg.module,
+ devtool: 'source-map',
+ output: {
+ path: outputFolder,
+ filename: outputFile,
+ library: pkg.name,
+ libraryTarget: 'umd',
+ chunkFilename: '[name].chunk.js',
+ umdNamedDefine: true,
+ globalObject: "typeof self !== 'undefined' ? self : this",
+ },
+ externals: [
+ {
+ react: {
+ root: 'React',
+ commonjs2: 'react',
+ commonjs: 'react',
+ amd: 'react',
+ },
+ '@ohif/core': {
+ commonjs2: '@ohif/core',
+ commonjs: '@ohif/core',
+ amd: '@ohif/core',
+ root: '@ohif/core',
+ },
+ '@ohif/ui': {
+ commonjs2: '@ohif/ui',
+ commonjs: '@ohif/ui',
+ amd: '@ohif/ui',
+ root: '@ohif/ui',
+ },
+ },
+ ],
+ module: {
+ rules: [
+ {
+ test: /(\.jsx|\.js|\.tsx|\.ts)$/,
+ loader: 'babel-loader',
+ exclude: /(node_modules|bower_components)/,
+ resolve: {
+ extensions: ['.js', '.jsx', '.ts', '.tsx'],
+ },
+ },
+ ],
+ },
+ resolve: {
+ modules: [path.resolve('./node_modules'), path.resolve('./src')],
+ extensions: ['.json', '.js', '.jsx', '.tsx', '.ts'],
+ },
+};
+
+module.exports = config;
diff --git a/extensions/dicom-microscopy/LICENSE b/extensions/dicom-microscopy/LICENSE
new file mode 100644
index 0000000000..effbc2d2f0
--- /dev/null
+++ b/extensions/dicom-microscopy/LICENSE
@@ -0,0 +1,9 @@
+MIT License
+
+Copyright (c) 2023 dicom-microscopy (26860200+md-prog@users.noreply.github.com)
+
+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.
\ No newline at end of file
diff --git a/extensions/dicom-microscopy/README.md b/extensions/dicom-microscopy/README.md
new file mode 100644
index 0000000000..8516bed876
--- /dev/null
+++ b/extensions/dicom-microscopy/README.md
@@ -0,0 +1,10 @@
+# dicom-microscopy
+
+## Description
+OHIF extension for DICOM microscopy
+
+## Author
+OHIF, md-prog
+
+## License
+MIT
diff --git a/extensions/dicom-microscopy/babel.config.js b/extensions/dicom-microscopy/babel.config.js
new file mode 100644
index 0000000000..92fbbdeaf9
--- /dev/null
+++ b/extensions/dicom-microscopy/babel.config.js
@@ -0,0 +1,44 @@
+module.exports = {
+ plugins: ['inline-react-svg', '@babel/plugin-proposal-class-properties'],
+ env: {
+ test: {
+ presets: [
+ [
+ // TODO: https://babeljs.io/blog/2019/03/19/7.4.0#migration-from-core-js-2
+ '@babel/preset-env',
+ {
+ modules: 'commonjs',
+ debug: false,
+ },
+ "@babel/preset-typescript",
+ ],
+ '@babel/preset-react',
+ ],
+ plugins: [
+ '@babel/plugin-proposal-object-rest-spread',
+ '@babel/plugin-syntax-dynamic-import',
+ '@babel/plugin-transform-regenerator',
+ '@babel/plugin-transform-runtime',
+ ],
+ },
+ production: {
+ presets: [
+ // WebPack handles ES6 --> Target Syntax
+ ['@babel/preset-env', { modules: false }],
+ '@babel/preset-react',
+ "@babel/preset-typescript",
+ ],
+ ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'],
+ },
+ development: {
+ presets: [
+ // WebPack handles ES6 --> Target Syntax
+ ['@babel/preset-env', { modules: false }],
+ '@babel/preset-react',
+ "@babel/preset-typescript",
+ ],
+ plugins: ['react-hot-loader/babel'],
+ ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'],
+ },
+ },
+};
diff --git a/extensions/dicom-microscopy/package.json b/extensions/dicom-microscopy/package.json
new file mode 100644
index 0000000000..99e89f866e
--- /dev/null
+++ b/extensions/dicom-microscopy/package.json
@@ -0,0 +1,73 @@
+{
+ "name": "@ohif/extension-dicom-microscopy",
+ "version": "3.0.0",
+ "description": "OHIF extension for DICOM microscopy",
+ "author": "OHIF, md-prog",
+ "license": "MIT",
+ "main": "dist/index.umd.js",
+ "files": [
+ "dist/**",
+ "public/**",
+ "README.md"
+ ],
+ "repository": "OHIF/Viewers",
+ "keywords": [
+ "ohif-extension"
+ ],
+ "module": "src/index.tsx",
+ "engines": {
+ "node": ">=14",
+ "npm": ">=6",
+ "yarn": ">=1.18.0"
+ },
+ "scripts": {
+ "dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --debug --output-pathinfo",
+ "dev:dicom-pdf": "yarn run dev",
+ "build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js",
+ "build:package": "yarn run build",
+ "start": "yarn run dev"
+ },
+ "peerDependencies": {
+ "@ohif/core": "^3.0.0",
+ "@ohif/extension-default": "^3.0.0",
+ "@ohif/extension-cornerstone": "^3.0.0",
+ "@ohif/i18n": "^1.0.0",
+ "prop-types": "^15.6.2",
+ "react": "^17.0.2",
+ "react-dom": "^17.0.2",
+ "react-i18next": "^10.11.0",
+ "react-router": "^6.8.1",
+ "react-router-dom": "^6.8.1",
+ "webpack": "^5.50.0",
+ "webpack-merge": "^5.7.3"
+ },
+ "dependencies": {
+ "@babel/runtime": "^7.20.13"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.17.8",
+ "@babel/plugin-proposal-class-properties": "^7.16.7",
+ "@babel/plugin-proposal-object-rest-spread": "^7.17.3",
+ "@babel/plugin-proposal-private-methods": "^7.18.6",
+ "@babel/plugin-syntax-dynamic-import": "^7.8.3",
+ "@babel/plugin-transform-arrow-functions": "^7.16.7",
+ "@babel/plugin-transform-regenerator": "^7.16.7",
+ "@babel/plugin-transform-runtime": "^7.17.0",
+ "babel-plugin-inline-react-svg": "^2.0.1",
+ "@babel/plugin-transform-typescript": "^7.13.0",
+ "@babel/preset-env": "^7.16.11",
+ "@babel/preset-react": "^7.16.7",
+ "@babel/preset-typescript": "^7.13.0",
+ "babel-eslint": "^8.0.3",
+ "babel-loader": "^8.0.0-beta.4",
+ "clean-webpack-plugin": "^4.0.0",
+ "copy-webpack-plugin": "^10.2.0",
+ "cross-env": "^7.0.3",
+ "dotenv": "^14.1.0",
+ "eslint": "^5.0.1",
+ "eslint-loader": "^2.0.0",
+ "webpack": "^5.50.0",
+ "webpack-merge": "^5.7.3",
+ "webpack-cli": "^4.7.2"
+ }
+}
diff --git a/extensions/dicom-microscopy/src/id.js b/extensions/dicom-microscopy/src/id.js
new file mode 100644
index 0000000000..ebe5acd98a
--- /dev/null
+++ b/extensions/dicom-microscopy/src/id.js
@@ -0,0 +1,5 @@
+import packageJson from '../package.json';
+
+const id = packageJson.name;
+
+export { id };
diff --git a/extensions/dicom-microscopy/src/index.tsx b/extensions/dicom-microscopy/src/index.tsx
new file mode 100644
index 0000000000..fa659bced1
--- /dev/null
+++ b/extensions/dicom-microscopy/src/index.tsx
@@ -0,0 +1,126 @@
+import { id } from './id';
+
+/**
+ * You can remove any of the following modules if you don't need them.
+ */
+export default {
+ /**
+ * Only required property. Should be a unique value across all extensions.
+ * You ID can be anything you want, but it should be unique.
+ */
+ id,
+
+ /**
+ * Perform any pre-registration tasks here. This is called before the extension
+ * is registered. Usually we run tasks such as: configuring the libraries
+ * (e.g. cornerstone, cornerstoneTools, ...) or registering any services that
+ * this extension is providing.
+ */
+ preRegistration: ({
+ servicesManager,
+ commandsManager,
+ configuration = {},
+ }) => {},
+ /**
+ * PanelModule should provide a list of panels that will be available in OHIF
+ * for Modes to consume and render. Each panel is defined by a {name,
+ * iconName, iconLabel, label, component} object. Example of a panel module
+ * is the StudyBrowserPanel that is provided by the default extension in OHIF.
+ */
+ getPanelModule: ({
+ servicesManager,
+ commandsManager,
+ extensionManager,
+ }) => {},
+ /**
+ * ViewportModule should provide a list of viewports that will be available in OHIF
+ * for Modes to consume and use in the viewports. Each viewport is defined by
+ * {name, component} object. Example of a viewport module is the CornerstoneViewport
+ * that is provided by the Cornerstone extension in OHIF.
+ */
+ getViewportModule: ({
+ servicesManager,
+ commandsManager,
+ extensionManager,
+ }) => {},
+ /**
+ * ToolbarModule should provide a list of tool buttons that will be available in OHIF
+ * for Modes to consume and use in the toolbar. Each tool button is defined by
+ * {name, defaultComponent, clickHandler }. Examples include radioGroupIcons and
+ * splitButton toolButton that the default extension is providing.
+ */
+ getToolbarModule: ({
+ servicesManager,
+ commandsManager,
+ extensionManager,
+ }) => {},
+ /**
+ * LayoutTemplateMOdule should provide a list of layout templates that will be
+ * available in OHIF for Modes to consume and use to layout the viewer.
+ * Each layout template is defined by a { name, id, component}. Examples include
+ * the default layout template provided by the default extension which renders
+ * a Header, left and right sidebars, and a viewport section in the middle
+ * of the viewer.
+ */
+ getLayoutTemplateModule: ({
+ servicesManager,
+ commandsManager,
+ extensionManager,
+ }) => {},
+ /**
+ * SopClassHandlerModule should provide a list of sop class handlers that will be
+ * available in OHIF for Modes to consume and use to create displaySets from Series.
+ * Each sop class handler is defined by a { name, sopClassUids, getDisplaySetsFromSeries}.
+ * Examples include the default sop class handler provided by the default extension
+ */
+ getSopClassHandlerModule: ({
+ servicesManager,
+ commandsManager,
+ extensionManager,
+ }) => {},
+ /**
+ * HangingProtocolModule should provide a list of hanging protocols that will be
+ * available in OHIF for Modes to use to decide on the structure of the viewports
+ * and also the series that hung in the viewports. Each hanging protocol is defined by
+ * { name, protocols}. Examples include the default hanging protocol provided by
+ * the default extension that shows 2x2 viewports.
+ */
+ getHangingProtocolModule: ({
+ servicesManager,
+ commandsManager,
+ extensionManager,
+ }) => {},
+ /**
+ * CommandsModule should provide a list of commands that will be available in OHIF
+ * for Modes to consume and use in the viewports. Each command is defined by
+ * an object of { actions, definitions, defaultContext } where actions is an
+ * object of functions, definitions is an object of available commands, their
+ * options, and defaultContext is the default context for the command to run against.
+ */
+ getCommandsModule: ({
+ servicesManager,
+ commandsManager,
+ extensionManager,
+ }) => {},
+ /**
+ * ContextModule should provide a list of context that will be available in OHIF
+ * and will be provided to the Modes. A context is a state that is shared OHIF.
+ * Context is defined by an object of { name, context, provider }. Examples include
+ * the measurementTracking context provided by the measurementTracking extension.
+ */
+ getContextModule: ({
+ servicesManager,
+ commandsManager,
+ extensionManager,
+ }) => {},
+ /**
+ * DataSourceModule should provide a list of data sources to be used in OHIF.
+ * DataSources can be used to map the external data formats to the OHIF's
+ * native format. DataSources are defined by an object of { name, type, createDataSource }.
+ */
+ getDataSourcesModule: ({
+ servicesManager,
+ commandsManager,
+ extensionManager,
+ }) => {},
+};
From ed5455ba2dff6c703cd3a6f75020e5185e8cbdb2 Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Fri, 17 Mar 2023 13:58:21 -0400
Subject: [PATCH 02/51] skeleton for microscopy mode
---
modes/microscopy/.gitignore | 104 +++++++++++++++++++++
modes/microscopy/.prettierrc | 8 ++
modes/microscopy/.webpack/webpack.prod.js | 62 +++++++++++++
modes/microscopy/LICENSE | 9 ++
modes/microscopy/README.md | 10 ++
modes/microscopy/babel.config.js | 44 +++++++++
modes/microscopy/package.json | 64 +++++++++++++
modes/microscopy/src/id.js | 5 +
modes/microscopy/src/index.tsx | 106 ++++++++++++++++++++++
9 files changed, 412 insertions(+)
create mode 100644 modes/microscopy/.gitignore
create mode 100644 modes/microscopy/.prettierrc
create mode 100644 modes/microscopy/.webpack/webpack.prod.js
create mode 100644 modes/microscopy/LICENSE
create mode 100644 modes/microscopy/README.md
create mode 100644 modes/microscopy/babel.config.js
create mode 100644 modes/microscopy/package.json
create mode 100644 modes/microscopy/src/id.js
create mode 100644 modes/microscopy/src/index.tsx
diff --git a/modes/microscopy/.gitignore b/modes/microscopy/.gitignore
new file mode 100644
index 0000000000..67045665db
--- /dev/null
+++ b/modes/microscopy/.gitignore
@@ -0,0 +1,104 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# TypeScript v1 declaration files
+typings/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env
+.env.test
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+
+# Next.js build output
+.next
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and *not* Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
diff --git a/modes/microscopy/.prettierrc b/modes/microscopy/.prettierrc
new file mode 100644
index 0000000000..b80ec6b347
--- /dev/null
+++ b/modes/microscopy/.prettierrc
@@ -0,0 +1,8 @@
+{
+ "trailingComma": "es5",
+ "printWidth": 80,
+ "proseWrap": "always",
+ "tabWidth": 2,
+ "semi": true,
+ "singleQuote": true
+}
diff --git a/modes/microscopy/.webpack/webpack.prod.js b/modes/microscopy/.webpack/webpack.prod.js
new file mode 100644
index 0000000000..c872977954
--- /dev/null
+++ b/modes/microscopy/.webpack/webpack.prod.js
@@ -0,0 +1,62 @@
+const path = require('path');
+const pkg = require('../package.json');
+
+const outputFile = 'index.umd.js';
+const rootDir = path.resolve(__dirname, '../');
+const outputFolder = path.join(__dirname, `../dist/`);
+
+// Todo: add ESM build for the mode in addition to umd build
+const config = {
+ mode: 'production',
+ entry: rootDir + '/' + pkg.module,
+ devtool: 'source-map',
+ output: {
+ path: outputFolder,
+ filename: outputFile,
+ library: pkg.name,
+ libraryTarget: 'umd',
+ chunkFilename: '[name].chunk.js',
+ umdNamedDefine: true,
+ globalObject: "typeof self !== 'undefined' ? self : this",
+ },
+ externals: [
+ {
+ react: {
+ root: 'React',
+ commonjs2: 'react',
+ commonjs: 'react',
+ amd: 'react',
+ },
+ '@ohif/core': {
+ commonjs2: '@ohif/core',
+ commonjs: '@ohif/core',
+ amd: '@ohif/core',
+ root: '@ohif/core',
+ },
+ '@ohif/ui': {
+ commonjs2: '@ohif/ui',
+ commonjs: '@ohif/ui',
+ amd: '@ohif/ui',
+ root: '@ohif/ui',
+ },
+ },
+ ],
+ module: {
+ rules: [
+ {
+ test: /(\.jsx|\.js|\.tsx|\.ts)$/,
+ loader: 'babel-loader',
+ exclude: /(node_modules|bower_components)/,
+ resolve: {
+ extensions: ['.js', '.jsx', '.ts', '.tsx'],
+ },
+ },
+ ],
+ },
+ resolve: {
+ modules: [path.resolve('./node_modules'), path.resolve('./src')],
+ extensions: ['.json', '.js', '.jsx', '.tsx', '.ts'],
+ },
+};
+
+module.exports = config;
diff --git a/modes/microscopy/LICENSE b/modes/microscopy/LICENSE
new file mode 100644
index 0000000000..ccd0edc309
--- /dev/null
+++ b/modes/microscopy/LICENSE
@@ -0,0 +1,9 @@
+MIT License
+
+Copyright (c) 2023 microscopy (26860200+md-prog@users.noreply.github.com)
+
+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.
\ No newline at end of file
diff --git a/modes/microscopy/README.md b/modes/microscopy/README.md
new file mode 100644
index 0000000000..679ea76216
--- /dev/null
+++ b/modes/microscopy/README.md
@@ -0,0 +1,10 @@
+# microscopy
+
+## Description
+OHIF mode for DICOM microscopy
+
+## Author
+OHIF, md-prog
+
+## License
+MIT
diff --git a/modes/microscopy/babel.config.js b/modes/microscopy/babel.config.js
new file mode 100644
index 0000000000..92fbbdeaf9
--- /dev/null
+++ b/modes/microscopy/babel.config.js
@@ -0,0 +1,44 @@
+module.exports = {
+ plugins: ['inline-react-svg', '@babel/plugin-proposal-class-properties'],
+ env: {
+ test: {
+ presets: [
+ [
+ // TODO: https://babeljs.io/blog/2019/03/19/7.4.0#migration-from-core-js-2
+ '@babel/preset-env',
+ {
+ modules: 'commonjs',
+ debug: false,
+ },
+ "@babel/preset-typescript",
+ ],
+ '@babel/preset-react',
+ ],
+ plugins: [
+ '@babel/plugin-proposal-object-rest-spread',
+ '@babel/plugin-syntax-dynamic-import',
+ '@babel/plugin-transform-regenerator',
+ '@babel/plugin-transform-runtime',
+ ],
+ },
+ production: {
+ presets: [
+ // WebPack handles ES6 --> Target Syntax
+ ['@babel/preset-env', { modules: false }],
+ '@babel/preset-react',
+ "@babel/preset-typescript",
+ ],
+ ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'],
+ },
+ development: {
+ presets: [
+ // WebPack handles ES6 --> Target Syntax
+ ['@babel/preset-env', { modules: false }],
+ '@babel/preset-react',
+ "@babel/preset-typescript",
+ ],
+ plugins: ['react-hot-loader/babel'],
+ ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'],
+ },
+ },
+};
diff --git a/modes/microscopy/package.json b/modes/microscopy/package.json
new file mode 100644
index 0000000000..d7016be014
--- /dev/null
+++ b/modes/microscopy/package.json
@@ -0,0 +1,64 @@
+{
+ "name": "@ohif/mode-microscopy",
+ "version": "3.0.0",
+ "description": "OHIF mode for DICOM microscopy",
+ "author": "OHIF, md-prog",
+ "license": "MIT",
+ "main": "dist/index.umd.js",
+ "files": [
+ "dist/**",
+ "public/**",
+ "README.md"
+ ],
+ "repository": "OHIF/Viewers",
+ "keywords": [
+ "ohif-mode"
+ ],
+ "module": "src/index.tsx",
+ "engines": {
+ "node": ">=14",
+ "npm": ">=6",
+ "yarn": ">=1.16.0"
+ },
+ "scripts": {
+ "dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --debug --output-pathinfo",
+ "dev:cornerstone": "yarn run dev",
+ "build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js",
+ "build:package": "yarn run build",
+ "start": "yarn run dev",
+ "test:unit": "jest --watchAll",
+ "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests"
+ },
+ "peerDependencies": {
+ "@ohif/core": "^3.0.0"
+ },
+ "dependencies": {
+ "@babel/runtime": "^7.20.13"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.17.8",
+ "@babel/plugin-proposal-class-properties": "^7.16.7",
+ "@babel/plugin-proposal-object-rest-spread": "^7.17.3",
+ "@babel/plugin-proposal-private-methods": "^7.18.6",
+ "@babel/plugin-syntax-dynamic-import": "^7.8.3",
+ "@babel/plugin-transform-arrow-functions": "^7.16.7",
+ "@babel/plugin-transform-regenerator": "^7.16.7",
+ "@babel/plugin-transform-runtime": "^7.17.0",
+ "@babel/plugin-transform-typescript": "^7.13.0",
+ "@babel/preset-env": "^7.16.11",
+ "@babel/preset-react": "^7.16.7",
+ "@babel/preset-typescript": "^7.13.0",
+ "babel-eslint": "^8.0.3",
+ "babel-loader": "^8.0.0-beta.4",
+ "babel-plugin-inline-react-svg": "^2.0.1",
+ "clean-webpack-plugin": "^4.0.0",
+ "copy-webpack-plugin": "^10.2.0",
+ "cross-env": "^7.0.3",
+ "dotenv": "^14.1.0",
+ "eslint": "^5.0.1",
+ "eslint-loader": "^2.0.0",
+ "webpack": "^5.50.0",
+ "webpack-merge": "^5.7.3",
+ "webpack-cli": "^4.7.2"
+ }
+}
diff --git a/modes/microscopy/src/id.js b/modes/microscopy/src/id.js
new file mode 100644
index 0000000000..ebe5acd98a
--- /dev/null
+++ b/modes/microscopy/src/id.js
@@ -0,0 +1,5 @@
+import packageJson from '../package.json';
+
+const id = packageJson.name;
+
+export { id };
diff --git a/modes/microscopy/src/index.tsx b/modes/microscopy/src/index.tsx
new file mode 100644
index 0000000000..a9c28dd939
--- /dev/null
+++ b/modes/microscopy/src/index.tsx
@@ -0,0 +1,106 @@
+import { id } from './id';
+
+const ohif = {
+ layout: '@ohif/extension-default.layoutTemplateModule.viewerLayout',
+ sopClassHandler: '@ohif/extension-default.sopClassHandlerModule.stack',
+ hangingProtocol: '@ohif/extension-default.hangingProtocolModule.default',
+ leftPanel: '@ohif/extension-default.panelModule.seriesList',
+ rightPanel: '@ohif/extension-default.panelModule.measure',
+};
+
+const cornerstone = {
+ viewport: '@ohif/extension-cornerstone.viewportModule.cornerstone',
+};
+
+/**
+ * Just two dependencies to be able to render a viewport with panels in order
+ * to make sure that the mode is working.
+ */
+const extensionDependencies = {
+ '@ohif/extension-default': '^3.0.0',
+ '@ohif/extension-cornerstone': '^3.0.0',
+};
+
+function modeFactory({ modeConfiguration }) {
+ return {
+ /**
+ * Mode ID, which should be unique among modes used by the viewer. This ID
+ * is used to identify the mode in the viewer's state.
+ */
+ id,
+ routeName: 'template',
+ /**
+ * Mode name, which is displayed in the viewer's UI in the workList, for the
+ * user to select the mode.
+ */
+ displayName: 'Template Mode',
+ /**
+ * Runs when the Mode Route is mounted to the DOM. Usually used to initialize
+ * Services and other resources.
+ */
+ onModeEnter: ({ servicesManager, extensionManager }) => {},
+ /**
+ * Runs when the Mode Route is unmounted from the DOM. Usually used to clean
+ * up resources and states
+ */
+ onModeExit: () => {},
+ /** */
+ validationTags: {
+ study: [],
+ series: [],
+ },
+ /**
+ * A boolean return value that indicates whether the mode is valid for the
+ * modalities of the selected studies. For instance a PET/CT mode should be
+ */
+ isValidMode: ({ modalities }) => true,
+ /**
+ * Mode Routes are used to define the mode's behavior. A list of Mode Route
+ * that includes the mode's path and the layout to be used. The layout will
+ * include the components that are used in the layout. For instance, if the
+ * default layoutTemplate is used (id: '@ohif/extension-default.layoutTemplateModule.viewerLayout')
+ * it will include the leftPanels, rightPanels, and viewports. However, if
+ * you define another layoutTemplate that includes a Footer for instance,
+ * you should provide the Footer component here too. Note: We use Strings
+ * to reference the component's ID as they are registered in the internal
+ * ExtensionManager. The template for the string is:
+ * `${extensionId}.{moduleType}.${componentId}`.
+ */
+ routes: [
+ {
+ path: 'template',
+ layoutTemplate: ({ location, servicesManager }) => {
+ return {
+ id: ohif.layout,
+ props: {
+ leftPanels: [ohif.leftPanel],
+ rightPanels: [ohif.rightPanel],
+ viewports: [
+ {
+ namespace: cornerstone.viewport,
+ displaySetsToDisplay: [ohif.sopClassHandler],
+ },
+ ],
+ },
+ };
+ },
+ },
+ ],
+ /** List of extensions that are used by the mode */
+ extensions: extensionDependencies,
+ /** HangingProtocol used by the mode */
+ // hangingProtocol: [''],
+ /** SopClassHandlers used by the mode */
+ sopClassHandlers: [ohif.sopClassHandler],
+ /** hotkeys for mode */
+ hotkeys: [''],
+ };
+}
+
+const mode = {
+ id,
+ modeFactory,
+ extensionDependencies,
+};
+
+export default mode;
From 0b568ea2145be43e3147e0f7be5b33c53ddfcf96 Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Fri, 17 Mar 2023 15:00:02 -0400
Subject: [PATCH 03/51] [feat] ported @radicalimaging/microscopy-dicom to
OHIF's default extension
---
extensions/dicom-microscopy/README.md | 2 +-
extensions/dicom-microscopy/package.json | 13 +-
.../src/DicomMicroscopySRSopClassHandler.js | 141 ++++
.../src/DicomMicroscopySopClassHandler.js | 112 ++++
.../src/DicomMicroscopyViewport.css | 9 +
.../src/DicomMicroscopyViewport.tsx | 181 +++++
.../dicom-microscopy/src/commandsModule.ts | 346 ++++++++++
.../MicroscopyDeleteDialog.js | 23 +
.../MicroscopyDeleteDialog.styl | 2 +
.../MicroscopyLabelDialog.js | 61 ++
.../MicroscopyLabelDialog.styl | 3 +
.../MicroscopyPanel/MicroscopyPanel.tsx | 619 ++++++++++++++++++
.../ViewportOverlay/ViewportOverlay.css | 87 +++
.../src/components/ViewportOverlay/index.tsx | 155 +++++
.../listComponentGenerator.tsx | 12 +
.../src/components/ViewportOverlay/utils.ts | 102 +++
.../dicom-microscopy/src/getPanelModule.tsx | 46 ++
.../src/helpers/formatDICOMDate.js | 11 +
.../src/helpers/formatDICOMDate.test.js | 9 +
.../src/helpers/formatDICOMPatientName.js | 21 +
.../helpers/formatDICOMPatientName.test.js | 17 +
.../src/helpers/formatDICOMTime.js | 17 +
.../src/helpers/formatDICOMTime.test.js | 9 +
.../src/helpers/formatNumberPrecision.js | 9 +
.../src/helpers/formatNumberPrecision.test.js | 9 +
.../dicom-microscopy/src/helpers/index.js | 15 +
.../src/helpers/isValidNumber.js | 3 +
.../src/tools/microscopyManager.js | 575 ++++++++++++++++
.../src/tools/viewerManager.js | 427 ++++++++++++
.../src/utils/DEVICE_OBSERVER_UID.js | 5 +
.../dicom-microscopy/src/utils/Publisher.js | 53 ++
.../src/utils/RoiAnnotation.js | 195 ++++++
.../src/utils/areaOfPolygon.js | 17 +
.../src/utils/callInputDialog.tsx | 75 +++
.../coordinateFormatScoord3d2Geometry.js | 110 ++++
.../src/utils/dcmCodeValues.js | 14 +
.../src/utils/dicomWebClient.ts | 8 +
.../src/utils/getSourceDisplaySet.js | 31 +
.../dicom-microscopy/src/utils/loadSR.js | 200 ++++++
.../dicom-microscopy/src/utils/styles.js | 48 ++
.../dicom-microscopy/src/utils/toArray.js | 3 +
extensions/dicom-microscopy/tsconfig.json | 11 +
42 files changed, 3799 insertions(+), 7 deletions(-)
create mode 100644 extensions/dicom-microscopy/src/DicomMicroscopySRSopClassHandler.js
create mode 100644 extensions/dicom-microscopy/src/DicomMicroscopySopClassHandler.js
create mode 100644 extensions/dicom-microscopy/src/DicomMicroscopyViewport.css
create mode 100644 extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
create mode 100644 extensions/dicom-microscopy/src/commandsModule.ts
create mode 100644 extensions/dicom-microscopy/src/components/MicroscopyDeleteDialog/MicroscopyDeleteDialog.js
create mode 100644 extensions/dicom-microscopy/src/components/MicroscopyDeleteDialog/MicroscopyDeleteDialog.styl
create mode 100644 extensions/dicom-microscopy/src/components/MicroscopyLabelDialog/MicroscopyLabelDialog.js
create mode 100644 extensions/dicom-microscopy/src/components/MicroscopyLabelDialog/MicroscopyLabelDialog.styl
create mode 100644 extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx
create mode 100644 extensions/dicom-microscopy/src/components/ViewportOverlay/ViewportOverlay.css
create mode 100644 extensions/dicom-microscopy/src/components/ViewportOverlay/index.tsx
create mode 100644 extensions/dicom-microscopy/src/components/ViewportOverlay/listComponentGenerator.tsx
create mode 100644 extensions/dicom-microscopy/src/components/ViewportOverlay/utils.ts
create mode 100644 extensions/dicom-microscopy/src/getPanelModule.tsx
create mode 100644 extensions/dicom-microscopy/src/helpers/formatDICOMDate.js
create mode 100644 extensions/dicom-microscopy/src/helpers/formatDICOMDate.test.js
create mode 100644 extensions/dicom-microscopy/src/helpers/formatDICOMPatientName.js
create mode 100644 extensions/dicom-microscopy/src/helpers/formatDICOMPatientName.test.js
create mode 100644 extensions/dicom-microscopy/src/helpers/formatDICOMTime.js
create mode 100644 extensions/dicom-microscopy/src/helpers/formatDICOMTime.test.js
create mode 100644 extensions/dicom-microscopy/src/helpers/formatNumberPrecision.js
create mode 100644 extensions/dicom-microscopy/src/helpers/formatNumberPrecision.test.js
create mode 100644 extensions/dicom-microscopy/src/helpers/index.js
create mode 100644 extensions/dicom-microscopy/src/helpers/isValidNumber.js
create mode 100644 extensions/dicom-microscopy/src/tools/microscopyManager.js
create mode 100644 extensions/dicom-microscopy/src/tools/viewerManager.js
create mode 100644 extensions/dicom-microscopy/src/utils/DEVICE_OBSERVER_UID.js
create mode 100644 extensions/dicom-microscopy/src/utils/Publisher.js
create mode 100644 extensions/dicom-microscopy/src/utils/RoiAnnotation.js
create mode 100644 extensions/dicom-microscopy/src/utils/areaOfPolygon.js
create mode 100644 extensions/dicom-microscopy/src/utils/callInputDialog.tsx
create mode 100644 extensions/dicom-microscopy/src/utils/coordinateFormatScoord3d2Geometry.js
create mode 100644 extensions/dicom-microscopy/src/utils/dcmCodeValues.js
create mode 100644 extensions/dicom-microscopy/src/utils/dicomWebClient.ts
create mode 100644 extensions/dicom-microscopy/src/utils/getSourceDisplaySet.js
create mode 100644 extensions/dicom-microscopy/src/utils/loadSR.js
create mode 100644 extensions/dicom-microscopy/src/utils/styles.js
create mode 100644 extensions/dicom-microscopy/src/utils/toArray.js
create mode 100644 extensions/dicom-microscopy/tsconfig.json
diff --git a/extensions/dicom-microscopy/README.md b/extensions/dicom-microscopy/README.md
index 8516bed876..85f5a32cc4 100644
--- a/extensions/dicom-microscopy/README.md
+++ b/extensions/dicom-microscopy/README.md
@@ -4,7 +4,7 @@
OHIF extension for DICOM microscopy
## Author
-OHIF, md-prog
+Bill Wallace, md-prog, Radical Imaging
## License
MIT
diff --git a/extensions/dicom-microscopy/package.json b/extensions/dicom-microscopy/package.json
index 99e89f866e..648d7dcc4a 100644
--- a/extensions/dicom-microscopy/package.json
+++ b/extensions/dicom-microscopy/package.json
@@ -2,7 +2,7 @@
"name": "@ohif/extension-dicom-microscopy",
"version": "3.0.0",
"description": "OHIF extension for DICOM microscopy",
- "author": "OHIF, md-prog",
+ "author": "Bill Wallace, md-prog, Radical Imaging",
"license": "MIT",
"main": "dist/index.umd.js",
"files": [
@@ -29,8 +29,8 @@
},
"peerDependencies": {
"@ohif/core": "^3.0.0",
- "@ohif/extension-default": "^3.0.0",
"@ohif/extension-cornerstone": "^3.0.0",
+ "@ohif/extension-default": "^3.0.0",
"@ohif/i18n": "^1.0.0",
"prop-types": "^15.6.2",
"react": "^17.0.2",
@@ -42,7 +42,8 @@
"webpack-merge": "^5.7.3"
},
"dependencies": {
- "@babel/runtime": "^7.20.13"
+ "@babel/runtime": "^7.20.13",
+ "dicom-microscopy-viewer": "^0.44.0"
},
"devDependencies": {
"@babel/core": "^7.17.8",
@@ -53,13 +54,13 @@
"@babel/plugin-transform-arrow-functions": "^7.16.7",
"@babel/plugin-transform-regenerator": "^7.16.7",
"@babel/plugin-transform-runtime": "^7.17.0",
- "babel-plugin-inline-react-svg": "^2.0.1",
"@babel/plugin-transform-typescript": "^7.13.0",
"@babel/preset-env": "^7.16.11",
"@babel/preset-react": "^7.16.7",
"@babel/preset-typescript": "^7.13.0",
"babel-eslint": "^8.0.3",
"babel-loader": "^8.0.0-beta.4",
+ "babel-plugin-inline-react-svg": "^2.0.1",
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^10.2.0",
"cross-env": "^7.0.3",
@@ -67,7 +68,7 @@
"eslint": "^5.0.1",
"eslint-loader": "^2.0.0",
"webpack": "^5.50.0",
- "webpack-merge": "^5.7.3",
- "webpack-cli": "^4.7.2"
+ "webpack-cli": "^4.7.2",
+ "webpack-merge": "^5.7.3"
}
}
diff --git a/extensions/dicom-microscopy/src/DicomMicroscopySRSopClassHandler.js b/extensions/dicom-microscopy/src/DicomMicroscopySRSopClassHandler.js
new file mode 100644
index 0000000000..d4ba8bbb27
--- /dev/null
+++ b/extensions/dicom-microscopy/src/DicomMicroscopySRSopClassHandler.js
@@ -0,0 +1,141 @@
+import OHIF, { DicomMetadataStore } from '@ohif/core';
+import loadSR from './utils/loadSR';
+import toArray from './utils/toArray';
+import DCM_CODE_VALUES from './utils/dcmCodeValues';
+import getSourceDisplaySet from './utils/getSourceDisplaySet';
+
+const { utils } = OHIF;
+
+const SOP_CLASS_UIDS = {
+ COMPREHENSIVE_3D_SR: '1.2.840.10008.5.1.4.1.1.88.34',
+};
+
+const SOPClassHandlerId =
+ '@ohif/extension-dicom-microscopy.sopClassHandlerModule.DicomMicroscopySRSopClassHandler';
+
+function _getReferencedFrameOfReferenceUID(naturalizedDataset) {
+ const { ContentSequence } = naturalizedDataset;
+
+ const imagingMeasurementsContentItem = ContentSequence.find(
+ ci =>
+ ci.ConceptNameCodeSequence.CodeValue ===
+ DCM_CODE_VALUES.IMAGING_MEASUREMENTS
+ );
+
+ const firstMeasurementGroupContentItem = toArray(
+ imagingMeasurementsContentItem.ContentSequence
+ ).find(
+ ci =>
+ ci.ConceptNameCodeSequence.CodeValue === DCM_CODE_VALUES.MEASUREMENT_GROUP
+ );
+
+ const imageRegionContentItem = toArray(
+ firstMeasurementGroupContentItem.ContentSequence
+ ).find(
+ ci => ci.ConceptNameCodeSequence.CodeValue === DCM_CODE_VALUES.IMAGE_REGION
+ );
+
+ return imageRegionContentItem.ReferencedFrameOfReferenceUID;
+}
+
+function _getDisplaySetsFromSeries(
+ instances,
+ servicesManager,
+ extensionManager
+) {
+ // If the series has no instances, stop here
+ if (!instances || !instances.length) {
+ throw new Error('No instances were provided');
+ }
+
+ const { displaySetService } = servicesManager.services;
+
+ const instance = instances[0];
+
+ // TODO ! Consumption of DICOMMicroscopySRSOPClassHandler to a derived dataset or normal dataset?
+ // TOOD -> Easy to swap this to a "non-derived" displaySet, but unfortunately need to put it in a different extension.
+ const naturalizedDataset = DicomMetadataStore.getSeries(
+ instance.StudyInstanceUID,
+ instance.SeriesInstanceUID
+ ).instances[0];
+ const ReferencedFrameOfReferenceUID = _getReferencedFrameOfReferenceUID(
+ naturalizedDataset
+ );
+
+ const {
+ FrameOfReferenceUID,
+ SeriesDescription,
+ ContentDate,
+ ContentTime,
+ SeriesNumber,
+ StudyInstanceUID,
+ SeriesInstanceUID,
+ SOPInstanceUID,
+ SOPClassUID,
+ } = instance;
+
+ const displaySet = {
+ plugin: 'microscopy',
+ Modality: 'SR',
+ altImageText: 'Microscopy SR',
+ displaySetInstanceUID: utils.guid(),
+ SOPInstanceUID,
+ SeriesInstanceUID,
+ StudyInstanceUID,
+ ReferencedFrameOfReferenceUID,
+ SOPClassHandlerId,
+ SOPClassUID,
+ SeriesDescription,
+ // Map the content date/time to the series date/time, these are only used for filtering.
+ SeriesDate: ContentDate,
+ SeriesTime: ContentTime,
+ SeriesNumber,
+ instance,
+ metadata: naturalizedDataset,
+ isDerived: true,
+ isLoading: false,
+ isLoaded: false,
+ loadError: false,
+ };
+
+ displaySet.load = function(referencedDisplaySet) {
+ return loadSR(displaySet, referencedDisplaySet).catch(error => {
+ displaySet.isLoaded = false;
+ displaySet.loadError = true;
+ throw new Error(error);
+ });
+ };
+
+ displaySet.getSourceDisplaySet = function() {
+ let allDisplaySets = [];
+ const studyMetadata = DicomMetadataStore.getStudy(StudyInstanceUID);
+ studyMetadata.series.forEach(series => {
+ const displaySets = displaySetService.getDisplaySetsForSeries(
+ series.SeriesInstanceUID
+ );
+ allDisplaySets = allDisplaySets.concat(displaySets);
+ });
+ return getSourceDisplaySet(allDisplaySets, displaySet);
+ };
+
+ return [displaySet];
+}
+
+export default function getDicomMicroscopySRSopClassHandler({
+ servicesManager,
+ extensionManager,
+}) {
+ const getDisplaySetsFromSeries = instances => {
+ return _getDisplaySetsFromSeries(
+ instances,
+ servicesManager,
+ extensionManager
+ );
+ };
+
+ return {
+ name: 'DicomMicroscopySRSopClassHandler',
+ sopClassUids: [SOP_CLASS_UIDS.COMPREHENSIVE_3D_SR],
+ getDisplaySetsFromSeries,
+ };
+}
diff --git a/extensions/dicom-microscopy/src/DicomMicroscopySopClassHandler.js b/extensions/dicom-microscopy/src/DicomMicroscopySopClassHandler.js
new file mode 100644
index 0000000000..fbfa09075d
--- /dev/null
+++ b/extensions/dicom-microscopy/src/DicomMicroscopySopClassHandler.js
@@ -0,0 +1,112 @@
+import OHIF from '@ohif/core';
+
+const { utils } = OHIF;
+
+const SOP_CLASS_UIDS = {
+ VL_WHOLE_SLIDE_MICROSCOPY_IMAGE_STORAGE: '1.2.840.10008.5.1.4.1.1.77.1.6',
+};
+
+const SOPClassHandlerId =
+ '@ohif/extension-dicom-microscopy.sopClassHandlerModule.DicomMicroscopySopClassHandler';
+
+function _getDisplaySetsFromSeries(
+ instances,
+ servicesManager,
+ extensionManager
+) {
+ // If the series has no instances, stop here
+ if (!instances || !instances.length) {
+ throw new Error('No instances were provided');
+ }
+
+ const instance = instances[0];
+
+ let singleFrameInstance = instance;
+ let currentFrames = +singleFrameInstance.NumberOfFrames || 1;
+ for (const instanceI of instances) {
+ const framesI = +instanceI.NumberOfFrames || 1;
+ if (framesI < currentFrames) {
+ singleFrameInstance = instanceI;
+ currentFrames = framesI;
+ }
+ }
+ let imageIdForThumbnail = null;
+ if (singleFrameInstance) {
+ if (currentFrames == 1) {
+ // Not all DICOM server implementations support thumbnail service,
+ // So if we have a single-frame image, we will prefer it.
+ imageIdForThumbnail = singleFrameInstance.imageId;
+ }
+ if (!imageIdForThumbnail) {
+ // use the thumbnail service provided by DICOM server
+ const dataSource = extensionManager.getActiveDataSource()[0];
+ imageIdForThumbnail = dataSource.getImageIdsForInstance({
+ instance: singleFrameInstance,
+ thumbnail: true,
+ });
+ }
+ }
+
+ const {
+ FrameOfReferenceUID,
+ SeriesDescription,
+ ContentDate,
+ ContentTime,
+ SeriesNumber,
+ StudyInstanceUID,
+ SeriesInstanceUID,
+ SOPInstanceUID,
+ SOPClassUID,
+ } = instance;
+
+ const othersFrameOfReferenceUID = instances
+ .filter(v => v)
+ .map(inst => inst.FrameOfReferenceUID)
+ .filter((value, index, array) => array.indexOf(value) === index);
+
+ const displaySet = {
+ plugin: 'microscopy',
+ Modality: 'SM',
+ altImageText: 'Microscopy',
+ displaySetInstanceUID: utils.guid(),
+ SOPInstanceUID,
+ SeriesInstanceUID,
+ StudyInstanceUID,
+ FrameOfReferenceUID,
+ SOPClassHandlerId,
+ SOPClassUID,
+ SeriesDescription: SeriesDescription || 'Microscopy Data',
+ // Map ContentDate/Time to SeriesTime for series list sorting.
+ SeriesDate: ContentDate,
+ SeriesTime: ContentTime,
+ SeriesNumber,
+ firstInstance: singleFrameInstance, // top level instance in the image Pyramid
+ instance,
+ numImageFrames: 0,
+ numInstances: 1,
+ imageIdForThumbnail, // thumbnail image
+ others: instances, // all other level instances in the image Pyramid
+ othersFrameOfReferenceUID,
+ };
+
+ return [displaySet];
+}
+
+export default function getDicomMicroscopySopClassHandler({
+ servicesManager,
+ extensionManager,
+}) {
+ const getDisplaySetsFromSeries = instances => {
+ return _getDisplaySetsFromSeries(
+ instances,
+ servicesManager,
+ extensionManager
+ );
+ };
+
+ return {
+ name: 'DicomMicroscopySopClassHandler',
+ sopClassUids: [SOP_CLASS_UIDS.VL_WHOLE_SLIDE_MICROSCOPY_IMAGE_STORAGE],
+ getDisplaySetsFromSeries,
+ };
+}
diff --git a/extensions/dicom-microscopy/src/DicomMicroscopyViewport.css b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.css
new file mode 100644
index 0000000000..0317d716d9
--- /dev/null
+++ b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.css
@@ -0,0 +1,9 @@
+.DicomMicroscopyViewer .ol-overviewmap .ol-overviewmap-map {
+ width: 300px !important;
+ height: 200px !important;
+ background: rgba(0,0,0,0.3);
+}
+
+.DicomMicroscopyViewer .ol-tooltip {
+ font-size: 16px !important;
+}
diff --git a/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
new file mode 100644
index 0000000000..0f6403e96b
--- /dev/null
+++ b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
@@ -0,0 +1,181 @@
+import React, { Component, useCallback } from 'react';
+import ReactResizeDetector from 'react-resize-detector';
+import PropTypes from 'prop-types';
+import debounce from 'lodash.debounce';
+
+import microscopyManager from './tools/microscopyManager';
+// import ViewportOverlay from './components/ViewportOverlay';
+import './DicomMicroscopyViewport.css';
+import ViewportOverlay from './components/ViewportOverlay';
+import getDicomWebClient from './utils/dicomWebClient';
+
+class DicomMicroscopyViewport extends Component {
+ state = {
+ error: null as any,
+ };
+
+ viewer: any = null; // dicom-microscopy-viewer instance
+ managedViewer: any = null; // managed wrapper of microscopy-dicom extension
+
+ container = React.createRef();
+ overlayElement = React.createRef();
+ debouncedResize: () => any;
+
+ constructor(props) {
+ super(props);
+
+ this.debouncedResize = debounce(() => {
+ if (this.viewer) this.viewer.resize();
+ }, 100);
+ }
+
+ static propTypes = {
+ viewportData: PropTypes.object,
+ activeViewportIndex: PropTypes.number,
+ setViewportActive: PropTypes.func,
+
+ // props from OHIF Viewport Grid
+ displaySets: PropTypes.array,
+ viewportIndex: PropTypes.number,
+ viewportLabel: PropTypes.string,
+ dataSource: PropTypes.object,
+ viewportOptions: PropTypes.object,
+ displaySetOptions: PropTypes.array,
+
+ // other props from wrapping component
+ servicesManager: PropTypes.object,
+ extensionManager: PropTypes.object,
+ };
+
+ // install the microscopy renderer into the web page.
+ // you should only do this once.
+ installOpenLayersRenderer(container, displaySet) {
+ const loadViewer = async (metadata) => {
+ metadata = metadata.filter((m) => m);
+
+ const { viewer: DicomMicroscopyViewer } = await import(
+ /* webpackChunkName: "dicom-microscopy-viewer" */ 'dicom-microscopy-viewer'
+ );
+ const microscopyViewer = DicomMicroscopyViewer.VolumeImageViewer;
+
+ const client = getDicomWebClient();
+
+ const options = {
+ client,
+ metadata,
+ retrieveRendered: false,
+ controls: ['overview'],
+ };
+
+ this.viewer = new microscopyViewer(options);
+
+ if (
+ this.overlayElement &&
+ this.overlayElement.current &&
+ this.viewer.addViewportOverlay
+ ) {
+ this.viewer.addViewportOverlay({
+ element: this.overlayElement.current,
+ className: 'OpenLayersOverlay',
+ });
+ }
+
+ this.viewer.render({ container });
+
+ const { StudyInstanceUID, SeriesInstanceUID } = displaySet;
+
+ this.managedViewer = microscopyManager.addViewer(
+ this.viewer,
+ this.props.viewportIndex,
+ container,
+ StudyInstanceUID,
+ SeriesInstanceUID
+ );
+ };
+
+ console.log('Loading viewer metadata', displaySet);
+ loadViewer(displaySet.others);
+ }
+
+ componentDidMount() {
+ const { displaySets, viewportIndex } = this.props;
+ const displaySet = displaySets[viewportIndex];
+ this.installOpenLayersRenderer(this.container.current, displaySet);
+ }
+
+ componentDidUpdate(prevProps: Readonly<{}>, prevState: Readonly<{}>, snapshot?: any): void {
+ if (this.managedViewer && prevProps.displaySets !== this.props.displaySets) {
+ const { displaySets } = this.props;
+ const displaySet = displaySets[0];
+
+ microscopyManager.clearAnnotations();
+
+ // loading SR
+ if (displaySet.Modality === 'SR') {
+ const referencedDisplaySet = displaySet.getSourceDisplaySet();
+ displaySet.load(referencedDisplaySet);
+ }
+ }
+ }
+
+ componentWillUnmount() {
+ microscopyManager.removeViewer(this.viewer);
+ }
+
+ setViewportActiveHandler = () => {
+ const { setViewportActive, viewportIndex, activeViewportIndex } =
+ this.props;
+
+ if (viewportIndex !== activeViewportIndex) {
+ setViewportActive(viewportIndex);
+ }
+ };
+
+ render() {
+ const style = { width: '100%', height: '100%' };
+ const displaySet = this.props.displaySets[0];
+ const firstInstance = displaySet.firstInstance || displaySet.instance;
+
+ return (
+
+
+
+
+ {displaySet && firstInstance.imageId && (
+
+ )}
+
+
+
+ {ReactResizeDetector && (
+
+ )}
+ {this.state.error ? (
+
{JSON.stringify(this.state.error)}
+ ) : (
+
+ )}
+
+ );
+ }
+
+ onWindowResize = () => {
+ this.debouncedResize();
+ };
+}
+
+export default DicomMicroscopyViewport;
diff --git a/extensions/dicom-microscopy/src/commandsModule.ts b/extensions/dicom-microscopy/src/commandsModule.ts
new file mode 100644
index 0000000000..d513bc3ac8
--- /dev/null
+++ b/extensions/dicom-microscopy/src/commandsModule.ts
@@ -0,0 +1,346 @@
+import microscopyManager from './tools/microscopyManager';
+import { ContextMenu } from '@ohif/ui';
+
+const commandsModule = ({
+ servicesManager,
+ commandsManager,
+ extensionManager,
+}) => {
+ const {
+ viewportGridService,
+ toolbarService,
+ uiDialogService,
+ cornerstoneViewportService,
+ customizationService,
+ measurementService,
+ } = servicesManager.services;
+
+ const contextMenuController = new ContextMenu.Controller(
+ servicesManager,
+ commandsManager
+ );
+
+ function _showViewerContextMenu(viewerElement, options) {
+ let defaultPointsPosition = [];
+ if (options.nearbyToolData) {
+ defaultPointsPosition = commandsManager.runCommand(
+ 'getToolDataActiveCanvasPoints',
+ { toolData: options.nearbyToolData }
+ );
+ }
+
+ contextMenuController.showContextMenu(
+ options,
+ viewerElement,
+ defaultPointsPosition
+ );
+ }
+
+ const actions = {
+ /** Show the specified context menu */
+ showViewerContextMenu: (providedOptions) => {
+ // const viewerElement = _getActiveEnabledElement();
+ // const options = { ...providedOptions };
+ // const { event: evt } = options;
+ // const { useSelectedAnnotation, nearbyToolData, menuName } = options;
+ // if (menuName) {
+ // Object.assign(
+ // options,
+ // customizationService.getModeCustomization(
+ // menuName,
+ // defaultContextMenu
+ // )
+ // );
+ // }
+ // if (useSelectedAnnotation && !nearbyToolData) {
+ // const firstAnnotationSelected =
+ // getFirstAnnotationSelected(viewerElement);
+ // // filter by allowed selected tools from config property (if there is any)
+ // if (
+ // !options.allowedSelectedTools ||
+ // options.allowedSelectedTools.includes(
+ // firstAnnotationSelected?.metadata?.toolName
+ // )
+ // ) {
+ // options.nearbyToolData = firstAnnotationSelected;
+ // } else {
+ // return;
+ // }
+ // }
+ // // TODO - make the checkProps richer by including the study metadata and display set.
+ // options.checkProps = {
+ // toolName: options.nearbyToolData?.metadata?.toolName,
+ // value: options.nearbyToolData,
+ // uid: options.nearbyToolData?.annotationUID,
+ // nearbyToolData: options.nearbyToolData,
+ // };
+ // _showViewerContextMenu(viewerElement, options);
+ },
+
+ /** Close any viewer context menus currently displayed */
+ closeViewerContextMenu: () => {
+ contextMenuController.closeViewerContextMenu();
+ },
+
+ getNearbyToolData({ nearbyToolData, element, canvasCoordinates }) {
+ // return (
+ // nearbyToolData ??
+ // cstUtils.getAnnotationNearPoint(element, canvasCoordinates)
+ // );
+ },
+
+ // Measurement tool commands:
+ deleteMeasurement: ({ uid }) => {
+ // if (uid) {
+ // // measurementServiceSource.remove(uid);
+ // }
+ },
+
+ setLabel: ({ uid }) => {
+ // const measurement = MeasurementService.getMeasurement(uid);
+ // callInputDialog(
+ // uiDialogService,
+ // measurement,
+ // (label, actionId) => {
+ // if (actionId === 'cancel') {
+ // return;
+ // }
+ // const updatedMeasurement = Object.assign({}, measurement, {
+ // label,
+ // });
+ // MeasurementService.update(
+ // updatedMeasurement.uid,
+ // updatedMeasurement,
+ // true
+ // );
+ // },
+ // false
+ // );
+ },
+
+ updateMeasurement: (props) => {
+ // const { code, uid, measurementKey = 'finding' } = props;
+ // const measurement = MeasurementService.getMeasurement(uid);
+ // const updatedMeasurement = {
+ // ...measurement,
+ // [measurementKey]: code,
+ // label: code.text,
+ // };
+ // MeasurementService.update(
+ // updatedMeasurement.uid,
+ // updatedMeasurement,
+ // true
+ // );
+ },
+
+ // Retrieve value commands
+ setViewportActive: ({ viewportId }) => {
+ // const viewportInfo =
+ // CornerstoneViewportService.getViewportInfo(viewportId);
+ // if (!viewportInfo) {
+ // console.warn('No viewport found for viewportId:', viewportId);
+ // return;
+ // }
+ // const viewportIndex = viewportInfo.getViewportIndex();
+ // viewportGridService.setActiveViewportIndex(viewportIndex);
+ },
+ arrowTextCallback: ({ callback, data }) => {
+ // callInputDialog(uiDialogService, data, callback);
+ },
+ setToolActive: ({ toolName, toolGroupId = 'MICROSCOPY' }) => {
+ if (
+ [
+ 'line',
+ 'box',
+ 'circle',
+ 'point',
+ 'polygon',
+ 'freehandpolygon',
+ 'freehandline',
+ ].indexOf(toolName) >= 0
+ ) {
+ // TODO: read from configuration
+ const styleOptions = {
+ stroke: {
+ color: [0, 255, 0, 1],
+ width: 1.2,
+ },
+ };
+ let options = {
+ geometryType: toolName,
+ vertexEnabled: true,
+ styleOptions,
+ } as any;
+ if ('line' === toolName) {
+ options.minPoints = 2;
+ options.maxPoints = 2;
+ }
+ else if('point' === toolName) {
+ delete options.styleOptions;
+ delete options.vertexEnabled;
+ }
+
+ microscopyManager.activateInteractions([['draw', options]]);
+ } else if (toolName == 'dragPan') {
+ microscopyManager.activateInteractions([['dragPan']]);
+ }
+ },
+ rotateViewport: ({ rotation }) => {},
+ flipViewportHorizontal: () => {},
+ flipViewportVertical: () => {},
+ invertViewport: ({ element }) => {},
+ resetViewport: () => {},
+ scaleViewport: ({ direction }) => {},
+ scroll: ({ direction }) => {},
+ incrementActiveViewport: () => {
+ const { activeViewportIndex, viewports } = viewportGridService.getState();
+ const nextViewportIndex = (activeViewportIndex + 1) % viewports.length;
+ viewportGridService.setActiveViewportIndex(nextViewportIndex);
+ },
+ decrementActiveViewport: () => {
+ const { activeViewportIndex, viewports } = viewportGridService.getState();
+ const nextViewportIndex =
+ (activeViewportIndex - 1 + viewports.length) % viewports.length;
+ viewportGridService.setActiveViewportIndex(nextViewportIndex);
+ },
+
+ toggleOverlays: () => {
+ // overlay
+ const overlays = document.getElementsByClassName('microscopy-viewport-overlay');
+ let onoff = false; // true if this will toggle on
+ for (let i = 0; i < overlays.length; i++) {
+ if (i === 0) onoff = overlays.item(0).classList.contains('hidden');
+ overlays.item(i).classList.toggle('hidden');
+ }
+
+ // overview
+ const { activeViewportIndex, viewports } = viewportGridService.getState();
+ microscopyManager.toggleOverviewMap(activeViewportIndex);
+ },
+ toggleAnnotations: () => {
+ microscopyManager.toggleROIsVisibility();
+ }
+ };
+
+ const definitions = {
+ showViewerContextMenu: {
+ commandFn: actions.showViewerContextMenu,
+ storeContexts: [] as any[],
+ options: {},
+ },
+ closeViewerContextMenu: {
+ commandFn: actions.closeViewerContextMenu,
+ storeContexts: [] as any[],
+ options: {},
+ },
+ getNearbyToolData: {
+ commandFn: actions.getNearbyToolData,
+ storeContexts: [] as any[],
+ options: {},
+ },
+
+ // These should probably all be standard implementations
+ // deleteMeasurement: {
+ // commandFn: actions.deleteMeasurement,
+ // storeContexts: [] as any[],
+ // options: {},
+ // },
+ // setLabel: {
+ // commandFn: actions.setLabel,
+ // storeContexts: [] as any[],
+ // options: {},
+ // },
+ // setFinding: {
+ // commandFn: actions.updateMeasurement,
+ // storeContexts: [] as any[],
+ // options: { measurementKey: 'finding' },
+ // },
+ // setSite: {
+ // commandFn: actions.updateMeasurement,
+ // storeContexts: [] as any[],
+ // options: { measurementKey: 'site' },
+ // },
+
+ setToolActive: {
+ commandFn: actions.setToolActive,
+ storeContexts: [] as any[],
+ options: {},
+ },
+ rotateViewportCW: {
+ commandFn: actions.rotateViewport,
+ storeContexts: [] as any[],
+ options: { rotation: 90 },
+ },
+ rotateViewportCCW: {
+ commandFn: actions.rotateViewport,
+ storeContexts: [] as any[],
+ options: { rotation: -90 },
+ },
+ incrementActiveViewport: {
+ commandFn: actions.incrementActiveViewport,
+ storeContexts: [] as any[],
+ },
+ decrementActiveViewport: {
+ commandFn: actions.decrementActiveViewport,
+ storeContexts: [] as any[],
+ },
+ flipViewportHorizontal: {
+ commandFn: actions.flipViewportHorizontal,
+ storeContexts: [] as any[],
+ options: {},
+ },
+ flipViewportVertical: {
+ commandFn: actions.flipViewportVertical,
+ storeContexts: [] as any[],
+ options: {},
+ },
+ resetViewport: {
+ commandFn: actions.resetViewport,
+ storeContexts: [] as any[],
+ options: {},
+ },
+ scaleUpViewport: {
+ commandFn: actions.scaleViewport,
+ storeContexts: [] as any[],
+ options: { direction: 1 },
+ },
+ scaleDownViewport: {
+ commandFn: actions.scaleViewport,
+ storeContexts: [] as any[],
+ options: { direction: -1 },
+ },
+ fitViewportToWindow: {
+ commandFn: actions.scaleViewport,
+ storeContexts: [] as any[],
+ options: { direction: 0 },
+ },
+ arrowTextCallback: {
+ commandFn: actions.arrowTextCallback,
+ storeContexts: [] as any[],
+ options: {},
+ },
+ setViewportActive: {
+ commandFn: actions.setViewportActive,
+ storeContexts: [] as any[],
+ options: {},
+ },
+ toggleOverlays: {
+ commandFn: actions.toggleOverlays,
+ storeContexts: [] as any[],
+ options: {},
+ },
+ toggleAnnotations: {
+ commandFn: actions.toggleAnnotations,
+ storeContexts: [] as any[],
+ options: {},
+ },
+ };
+
+ return {
+ actions,
+ definitions,
+ defaultContext: 'MICROSCOPY',
+ };
+};
+
+export default commandsModule;
diff --git a/extensions/dicom-microscopy/src/components/MicroscopyDeleteDialog/MicroscopyDeleteDialog.js b/extensions/dicom-microscopy/src/components/MicroscopyDeleteDialog/MicroscopyDeleteDialog.js
new file mode 100644
index 0000000000..0871aaecd0
--- /dev/null
+++ b/extensions/dicom-microscopy/src/components/MicroscopyDeleteDialog/MicroscopyDeleteDialog.js
@@ -0,0 +1,23 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { SimpleDialog } from '@ohif/ui';
+
+import './MicroscopyDeleteDialog.styl';
+
+const MicroscopyDeleteDialog = ({ title, onSubmit, onClose }) => {
+ return (
+
+
+ Are you sure you want to remove this Region of Interest?
+
+
+ );
+};
+
+MicroscopyDeleteDialog.propTypes = {
+ title: PropTypes.string,
+ onSubmit: PropTypes.func,
+ onClose: PropTypes.func,
+};
+
+export default MicroscopyDeleteDialog;
diff --git a/extensions/dicom-microscopy/src/components/MicroscopyDeleteDialog/MicroscopyDeleteDialog.styl b/extensions/dicom-microscopy/src/components/MicroscopyDeleteDialog/MicroscopyDeleteDialog.styl
new file mode 100644
index 0000000000..60291b89ad
--- /dev/null
+++ b/extensions/dicom-microscopy/src/components/MicroscopyDeleteDialog/MicroscopyDeleteDialog.styl
@@ -0,0 +1,2 @@
+.InputDialog.MicroscopyDeleteDialog .content
+ padding-bottom: 16px
diff --git a/extensions/dicom-microscopy/src/components/MicroscopyLabelDialog/MicroscopyLabelDialog.js b/extensions/dicom-microscopy/src/components/MicroscopyLabelDialog/MicroscopyLabelDialog.js
new file mode 100644
index 0000000000..e57b1ca86d
--- /dev/null
+++ b/extensions/dicom-microscopy/src/components/MicroscopyLabelDialog/MicroscopyLabelDialog.js
@@ -0,0 +1,61 @@
+import React, { useState } from 'react';
+import PropTypes from 'prop-types';
+import { SimpleDialog, TextInput, SelectTree } from '@ohif/ui';
+import './MicroscopyLabelDialog.styl';
+import ConfigPoint from 'config-point';
+
+const MicroscopyLabelDialog = ({
+ title,
+ onSubmit,
+ onClose,
+ label,
+ defaultValue = '',
+}) => {
+ const [value, setValue] = useState({ label: defaultValue });
+ const onSubmitHandler = () => onSubmit(value);
+
+ const labellingItemConfig = ConfigPoint.MicroscopyLabellingData;
+ const { computedItems } = labellingItemConfig || {};
+ const selectTreeSelectCallback = (event, item) => {
+ setValue(item);
+ onSubmit(item);
+ };
+
+ return (
+
+
+ setValue({ label: event.target.value })}
+ label={label}
+ autoComplete="off"
+ autoFocus
+ onFocus={e => e.currentTarget.select()}
+ />
+ {computedItems && (
+
+ )}
+
+
+ );
+};
+
+MicroscopyLabelDialog.propTypes = {
+ title: PropTypes.string,
+ onSubmit: PropTypes.func,
+ onClose: PropTypes.func,
+ label: PropTypes.string,
+ defaultValue: PropTypes.string,
+};
+
+export default MicroscopyLabelDialog;
diff --git a/extensions/dicom-microscopy/src/components/MicroscopyLabelDialog/MicroscopyLabelDialog.styl b/extensions/dicom-microscopy/src/components/MicroscopyLabelDialog/MicroscopyLabelDialog.styl
new file mode 100644
index 0000000000..4e0d89f09f
--- /dev/null
+++ b/extensions/dicom-microscopy/src/components/MicroscopyLabelDialog/MicroscopyLabelDialog.styl
@@ -0,0 +1,3 @@
+.InputDialog.MicroscopyLabelDialog .content
+ padding-bottom: 16px
+ flex-direction: column
diff --git a/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx b/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx
new file mode 100644
index 0000000000..001dbc9631
--- /dev/null
+++ b/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx
@@ -0,0 +1,619 @@
+import React, { useState, useEffect } from 'react';
+import PropTypes from 'prop-types';
+import {
+ ServicesManager,
+ ExtensionManager,
+ DicomMetadataStore,
+} from '@ohif/core';
+import { MeasurementTable, Icon, ButtonGroup, Button } from '@ohif/ui';
+import { withTranslation } from 'react-i18next';
+import DEVICE_OBSERVER_UID from '../../utils/DEVICE_OBSERVER_UID';
+import { EVENTS as MicroscopyEvents } from '../../tools/microscopyManager';
+import dcmjs from 'dcmjs';
+import styles from '../../utils/styles';
+import callInputDialog from '../../utils/callInputDialog';
+
+let saving = false;
+const { datasetToBuffer } = dcmjs.data;
+
+const formatArea = (area) => {
+ let mult = 1;
+ let unit = 'mm';
+ if (area > 1000000) {
+ unit = 'm';
+ mult = 1 / 1000000;
+ } else if (area < 1) {
+ unit = 'μm';
+ mult = 1000000;
+ }
+ return `${(area * mult).toFixed(2)} ${unit}²`;
+};
+
+const formatLength = (length, unit) => {
+ let mult = 1;
+ if (unit == 'km' || (!unit && length > 1000000)) {
+ unit = 'km';
+ mult = 1 / 1000000;
+ } else if (unit == 'm' || (!unit && length > 1000)) {
+ unit = 'm';
+ mult = 1 / 1000;
+ } else if (unit == 'μm' || (!unit && length < 1)) {
+ unit = 'μm';
+ mult = 1000;
+ } else if (unit && unit != 'mm') {
+ throw new Error(`Unknown length unit ${unit}`);
+ } else {
+ unit = 'mm';
+ }
+ return `${(length * mult).toFixed(2)} ${unit}`;
+};
+
+/**
+ * Trigger file download from an array buffer
+ * @param buffer
+ * @param filename
+ */
+function saveByteArray(buffer: ArrayBuffer, filename: string) {
+ var blob = new Blob([buffer], { type: 'application/dicom' });
+ var link = document.createElement('a');
+ link.href = window.URL.createObjectURL(blob);
+ link.download = filename;
+ link.click();
+}
+
+/**
+ * Microscopy Measurements Panel Component
+ *
+ * @param props
+ * @returns
+ */
+function MicroscopyPanel(props: {
+ viewports: PropTypes.array;
+ activeViewportIndex: PropTypes.number;
+
+ //
+ microscopyManager: PropTypes.object;
+ onSaveComplete?: PropTypes.func; // callback when successfully saved annotations
+ onRejectComplete?: PropTypes.func; // callback when rejected annotations
+
+ //
+ servicesManager: ServicesManager;
+ extensionManager: ExtensionManager;
+
+ // Translator function (injected by withTranslation() below)
+ t: PropTypes.func;
+}) {
+ const microscopyManager: any = props.microscopyManager;
+
+ const [studyInstanceUID, setStudyInstanceUID] = useState(
+ null as string | null
+ );
+ const [roiAnnotations, setRoiAnnotations] = useState([] as any[]);
+ const [selectedAnnotation, setSelectedAnnotation] = useState(null as any);
+ const { servicesManager, extensionManager } = props;
+
+ const { uiDialogService, displaySetService } = servicesManager.services;
+
+ useEffect(() => {
+ const viewport = props.viewports[props.activeViewportIndex];
+ if (viewport.displaySetInstanceUIDs[0]) {
+ const displaySet = displaySetService.getDisplaySetByUID(
+ viewport.displaySetInstanceUIDs[0]
+ );
+ if (displaySet) {
+ setStudyInstanceUID(displaySet.StudyInstanceUID);
+ }
+ }
+ }, [props.viewports, props.activeViewportIndex]);
+
+ useEffect(() => {
+ const onAnnotationUpdated = () => {
+ const roiAnnotations =
+ microscopyManager.getAnnotationsForStudy(studyInstanceUID);
+ setRoiAnnotations(roiAnnotations);
+ };
+
+ const onAnnotationSelected = () => {
+ const selectedAnnotation = microscopyManager.getSelectedAnnotation();
+ setSelectedAnnotation(selectedAnnotation);
+ };
+
+ microscopyManager.subscribe(
+ MicroscopyEvents.ANNOTATION_UPDATED,
+ onAnnotationUpdated
+ );
+ microscopyManager.subscribe(
+ MicroscopyEvents.ANNOTATION_SELECTED,
+ onAnnotationSelected
+ );
+ onAnnotationUpdated();
+ onAnnotationSelected();
+
+ // on unload unsubscribe from events
+ return () => {
+ microscopyManager.unsubscribe(
+ MicroscopyEvents.ANNOTATION_UPDATED,
+ onAnnotationUpdated
+ );
+ microscopyManager.unsubscribe(
+ MicroscopyEvents.ANNOTATION_SELECTED,
+ onAnnotationSelected
+ );
+ };
+ }, [studyInstanceUID]);
+
+ const getActionButton = (
+ btnLabel: string,
+ onClickCallback: (evt: any) => {}
+ ) => {
+ return (
+
+
+
+
+ {btnLabel}
+
+ );
+ };
+
+ /**
+ * On clicking "Save Annotations" button, prompt an input modal for the
+ * new series' description, and continue to save.
+ *
+ * @returns
+ */
+ const promptSave = () => {
+ const annotations =
+ microscopyManager.getAnnotationsForStudy(studyInstanceUID);
+
+ if (!annotations || saving) {
+ return;
+ }
+
+ callInputDialog({
+ uiDialogService,
+ title: 'Enter description of the Series',
+ defaultValue: '',
+ callback: (value: string, action: string) => {
+ switch (action) {
+ case 'save': {
+ saveFunction(value);
+ }
+ }
+ },
+ });
+ };
+
+ const getAllDisplaySets = (studyMetadata: any) => {
+ let allDisplaySets = [] as any[];
+ studyMetadata.series.forEach((series: any) => {
+ const displaySets = displaySetService.getDisplaySetsForSeries(
+ series.SeriesInstanceUID
+ );
+ allDisplaySets = allDisplaySets.concat(displaySets);
+ });
+ return allDisplaySets;
+ };
+
+ /**
+ * Save annotations as a series
+ *
+ * @param SeriesDescription - series description
+ * @returns
+ */
+ const saveFunction = async (SeriesDescription: string) => {
+ const dataSource = extensionManager.getActiveDataSource()[0];
+ const { onSaveComplete } = props;
+ const imagingMeasurements = [];
+ const annotations =
+ microscopyManager.getAnnotationsForStudy(studyInstanceUID);
+
+ saving = true;
+
+ // There is only one viewer possible for one study,
+ // Since once study contains multiple resolution levels (series) of one whole
+ // Slide image.
+
+ const studyMetadata = DicomMetadataStore.getStudy(studyInstanceUID);
+ const displaySets = getAllDisplaySets(studyMetadata);
+ const smDisplaySet = displaySets.find((ds) => ds.Modality === 'SM');
+
+ // Get the next available series number after 4700.
+
+ const dsWithMetadata = displaySets.filter(
+ (ds) =>
+ ds.metadata &&
+ ds.metadata.SeriesNumber &&
+ typeof ds.metadata.SeriesNumber === 'number'
+ );
+
+ const seriesNumbers = dsWithMetadata.map((ds) => ds.metadata.SeriesNumber);
+ const maxSeriesNumber = Math.max(...seriesNumbers, 4700);
+ const SeriesNumber = maxSeriesNumber + 1;
+
+ const { instance: metadata } = smDisplaySet;
+
+ // Handle malformed data
+ if (!metadata.SpecimenDescriptionSequence) {
+ metadata.SpecimenDescriptionSequence = {
+ SpecimenUID: metadata.SeriesInstanceUID,
+ SpecimenIdentifier: metadata.SeriesDescription,
+ };
+ }
+ const { SpecimenDescriptionSequence } = metadata;
+
+ const observationContext = new dcmjs.sr.templates.ObservationContext({
+ observerPersonContext: new dcmjs.sr.templates.ObserverContext({
+ observerType: new dcmjs.sr.coding.CodedConcept({
+ value: '121006',
+ schemeDesignator: 'DCM',
+ meaning: 'Person',
+ }),
+ observerIdentifyingAttributes:
+ new dcmjs.sr.templates.PersonObserverIdentifyingAttributes({
+ name: '@ohif/extension-dicom-microscopy',
+ }),
+ }),
+ observerDeviceContext: new dcmjs.sr.templates.ObserverContext({
+ observerType: new dcmjs.sr.coding.CodedConcept({
+ value: '121007',
+ schemeDesignator: 'DCM',
+ meaning: 'Device',
+ }),
+ observerIdentifyingAttributes:
+ new dcmjs.sr.templates.DeviceObserverIdentifyingAttributes({
+ uid: DEVICE_OBSERVER_UID,
+ }),
+ }),
+ subjectContext: new dcmjs.sr.templates.SubjectContext({
+ subjectClass: new dcmjs.sr.coding.CodedConcept({
+ value: '121027',
+ schemeDesignator: 'DCM',
+ meaning: 'Specimen',
+ }),
+ subjectClassSpecificContext:
+ new dcmjs.sr.templates.SubjectContextSpecimen({
+ uid: SpecimenDescriptionSequence.SpecimenUID,
+ identifier:
+ SpecimenDescriptionSequence.SpecimenIdentifier ||
+ metadata.SeriesInstanceUID,
+ containerIdentifier:
+ metadata.ContainerIdentifier || metadata.SeriesInstanceUID,
+ }),
+ }),
+ });
+
+ for (let i = 0; i < annotations.length; i++) {
+ const { roiGraphic: roi, label } = annotations[i];
+ let { measurements, evaluations, marker, presentationState } =
+ roi.properties;
+
+ console.debug('[SR] storing marker...', marker);
+ console.debug('[SR] storing measurements...', measurements);
+ console.debug('[SR] storing evaluations...', evaluations);
+ console.debug('[SR] storing presentation state...', presentationState);
+
+ if (presentationState) presentationState.marker = marker;
+
+ /** Avoid incompatibility with dcmjs */
+ measurements = measurements.map((measurement: any) => {
+ const ConceptName = Array.isArray(measurement.ConceptNameCodeSequence)
+ ? measurement.ConceptNameCodeSequence[0]
+ : measurement.ConceptNameCodeSequence;
+
+ const MeasuredValue = Array.isArray(measurement.MeasuredValueSequence)
+ ? measurement.MeasuredValueSequence[0]
+ : measurement.MeasuredValueSequence;
+
+ const MeasuredValueUnits = Array.isArray(
+ MeasuredValue.MeasurementUnitsCodeSequence
+ )
+ ? MeasuredValue.MeasurementUnitsCodeSequence[0]
+ : MeasuredValue.MeasurementUnitsCodeSequence;
+
+ return new dcmjs.sr.valueTypes.NumContentItem({
+ name: new dcmjs.sr.coding.CodedConcept({
+ meaning: ConceptName.CodeMeaning,
+ value: ConceptName.CodeValue,
+ schemeDesignator: ConceptName.CodingSchemeDesignator,
+ }),
+ value: MeasuredValue.NumericValue,
+ unit: new dcmjs.sr.coding.CodedConcept({
+ value: MeasuredValueUnits.CodeValue,
+ meaning: MeasuredValueUnits.CodeMeaning,
+ schemeDesignator: MeasuredValueUnits.CodingSchemeDesignator,
+ }),
+ });
+ });
+
+ /** Avoid incompatibility with dcmjs */
+ evaluations = evaluations.map((evaluation: any) => {
+ const ConceptName = Array.isArray(evaluation.ConceptNameCodeSequence)
+ ? evaluation.ConceptNameCodeSequence[0]
+ : evaluation.ConceptNameCodeSequence;
+
+ return new dcmjs.sr.valueTypes.TextContentItem({
+ name: new dcmjs.sr.coding.CodedConcept({
+ value: ConceptName.CodeValue,
+ meaning: ConceptName.CodeMeaning,
+ schemeDesignator: ConceptName.CodingSchemeDesignator,
+ }),
+ value: evaluation.TextValue,
+ relationshipType: evaluation.RelationshipType,
+ });
+ });
+
+ debugger;
+ const identifier = `ROI #${i + 1}`;
+ const group =
+ new dcmjs.sr.templates.PlanarROIMeasurementsAndQualitativeEvaluations({
+ trackingIdentifier: new dcmjs.sr.templates.TrackingIdentifier({
+ uid: roi.uid,
+ identifier: presentationState
+ ? identifier.concat(`(${JSON.stringify(presentationState)})`)
+ : identifier,
+ }),
+ referencedRegion: new dcmjs.sr.contentItems.ImageRegion3D({
+ graphicType: roi.scoord3d.graphicType,
+ graphicData: roi.scoord3d.graphicData,
+ frameOfReferenceUID: roi.scoord3d.frameOfReferenceUID,
+ }),
+ findingType: new dcmjs.sr.coding.CodedConcept({
+ value: label,
+ schemeDesignator: '@ohif/extension-dicom-microscopy',
+ meaning: 'FREETEXT',
+ }),
+ /** Evaluations will conflict with current tracking identifier */
+ /** qualitativeEvaluations: evaluations, */
+ measurements,
+ });
+ imagingMeasurements.push(...group);
+ }
+
+ const measurementReport = new dcmjs.sr.templates.MeasurementReport({
+ languageOfContentItemAndDescendants:
+ new dcmjs.sr.templates.LanguageOfContentItemAndDescendants({}),
+ observationContext,
+ procedureReported: new dcmjs.sr.coding.CodedConcept({
+ value: '112703',
+ schemeDesignator: 'DCM',
+ meaning: 'Whole Slide Imaging',
+ }),
+ imagingMeasurements,
+ });
+
+ const dataset = new dcmjs.sr.documents.Comprehensive3DSR({
+ content: measurementReport[0],
+ evidence: [metadata],
+ seriesInstanceUID: dcmjs.data.DicomMetaDictionary.uid(),
+ seriesNumber: SeriesNumber,
+ seriesDescription:
+ SeriesDescription || 'Whole slide imaging structured report',
+ sopInstanceUID: dcmjs.data.DicomMetaDictionary.uid(),
+ instanceNumber: 1,
+ manufacturer: 'dcmjs-org',
+ });
+ dataset.SpecificCharacterSet = 'ISO_IR 192';
+ const fileMetaInformationVersionArray = new Uint8Array(2);
+ fileMetaInformationVersionArray[1] = 1;
+
+ dataset._meta = {
+ FileMetaInformationVersion: {
+ Value: [fileMetaInformationVersionArray.buffer], // TODO
+ vr: 'OB',
+ },
+ MediaStorageSOPClassUID: dataset.sopClassUID,
+ MediaStorageSOPInstanceUID: dataset.sopInstanceUID,
+ TransferSyntaxUID: {
+ Value: ['1.2.840.10008.1.2.1'],
+ vr: 'UI',
+ },
+ ImplementationClassUID: {
+ Value: [dcmjs.data.DicomMetaDictionary.uid()],
+ vr: 'UI',
+ },
+ ImplementationVersionName: {
+ Value: ['@ohif/extension-dicom-microscopy'],
+ vr: 'SH',
+ },
+ };
+
+ try {
+ if (dataSource) {
+ if (dataSource.wadoRoot == 'saveDicom') {
+ // download as DICOM file
+ const part10Buffer = datasetToBuffer(dataset);
+ saveByteArray(part10Buffer, `sr-microscopy.dcm`);
+ } else {
+ // Save into Web Data source
+ const { StudyInstanceUID } = dataset;
+ await dataSource.store.dicom(dataset);
+ if (StudyInstanceUID) {
+ dataSource.deleteStudyMetadataPromise(StudyInstanceUID);
+ }
+ }
+ onSaveComplete({
+ title: 'SR Saved',
+ meassage: 'Measurements downloaded successfully',
+ type: 'success',
+ });
+ } else {
+ console.error('Server unspecified');
+ }
+ } catch (error) {
+ onSaveComplete({
+ title: 'SR Save Failed',
+ message: error.message || error.toString(),
+ type: 'error',
+ });
+ } finally {
+ saving = false;
+ }
+ };
+
+ /**
+ * On clicking "Reject annotations" button
+ */
+ const onDeleteCurrentSRHandler = async () => {
+ try {
+ const activeViewport = props.viewports[props.activeViewportIndex];
+ const { StudyInstanceUID } = activeViewport;
+
+ // TODO: studies?
+ const study = DicomMetadataStore.getStudy(StudyInstanceUID);
+
+ const lastDerivedDisplaySet = study.derivedDisplaySets.sort(
+ (ds1: any, ds2: any) => {
+ const dateTime1 = Number(`${ds1.SeriesDate}${ds1.SeriesTime}`);
+ const dateTime2 = Number(`${ds2.SeriesDate}${ds2.SeriesTime}`);
+ return dateTime1 > dateTime2;
+ }
+ )[study.derivedDisplaySets.length - 1];
+
+ // TODO: use dataSource.reject.dicom()
+ // await DICOMSR.rejectMeasurements(
+ // study.wadoRoot,
+ // lastDerivedDisplaySet.StudyInstanceUID,
+ // lastDerivedDisplaySet.SeriesInstanceUID
+ // );
+ props.onRejectComplete({
+ title: 'Report rejected',
+ message: 'Latest report rejected successfully',
+ type: 'success',
+ });
+ } catch (error) {
+ props.onRejectComplete({
+ title: 'Failed to reject report',
+ message: error.message,
+ type: 'error',
+ });
+ }
+ };
+
+ /**
+ * Handler for clicking event of an annotation item.
+ *
+ * @param param0
+ */
+ const onMeasurementItemClickHandler = ({ uid }: { uid: string }) => {
+ const roiAnnotation = microscopyManager.getAnnotation(uid);
+ microscopyManager.selectAnnotation(roiAnnotation);
+ microscopyManager.focusAnnotation(roiAnnotation, props.activeViewportIndex);
+ };
+
+ /**
+ * Handler for "Edit" action of an annotation item
+ * @param param0
+ */
+ const onMeasurementItemEditHandler = ({
+ uid,
+ isActive,
+ }: {
+ uid: string;
+ isActive: boolean;
+ }) => {
+ const roiAnnotation = microscopyManager.getAnnotation(uid);
+
+ callInputDialog({
+ uiDialogService,
+ title: 'Enter your annotation',
+ defaultValue: '',
+ callback: (value: string, action: string) => {
+ switch (action) {
+ case 'save': {
+ roiAnnotation.setLabel(value);
+ microscopyManager.triggerRelabel(roiAnnotation);
+ }
+ }
+ },
+ });
+ };
+
+ // Convert ROI annotations managed by microscopyManager into our
+ // own format for display
+ const data = roiAnnotations.map((roiAnnotation, index) => {
+ const label = roiAnnotation.getDetailedLabel();
+ const area = roiAnnotation.getArea();
+ const length = roiAnnotation.getLength();
+ const shortAxisLength = roiAnnotation.roiGraphic.properties.shortAxisLength;
+ const isSelected: boolean = selectedAnnotation === roiAnnotation;
+
+ // actions
+ const onRelabel = () => microscopyManager.triggerRelabel(roiAnnotation);
+ const onDelete = () => microscopyManager.triggerDelete(roiAnnotation);
+ const onSelect = () => microscopyManager.selectAnnotation(roiAnnotation);
+
+ const rowActions = [];
+ rowActions.push(getActionButton('Relabels', onRelabel));
+ rowActions.push(getActionButton('Delete', onDelete));
+
+ // other events
+ const { uid } = roiAnnotation;
+ const onMouseEnter = () =>
+ microscopyManager.setROIStyle(uid, styles.active);
+ const onMouseLeave = () =>
+ microscopyManager.setROIStyle(uid, styles.default);
+
+ // display text
+ const displayText = [];
+
+ if (area !== undefined) {
+ displayText.push(formatArea(area));
+ } else if (length !== undefined) {
+ displayText.push(
+ shortAxisLength
+ ? `${formatLength(length, 'μm')} x ${formatLength(
+ shortAxisLength,
+ 'μm'
+ )}`
+ : `${formatLength(length, 'μm')}`
+ );
+ }
+
+ // convert to measurementItem format compatible with component
+ return {
+ uid,
+ index,
+ label,
+ isActive: isSelected,
+ displayText,
+ roiAnnotation,
+ };
+ });
+
+ return (
+ <>
+
+
+
+
+ {promptSave && (
+
+ {props.t('Save Measurements')}
+
+ )}
+ {/*
+ {props.t('Reject latest report')}
+ */}
+
+ >
+ );
+}
+
+const connectedMicroscopyPanel = withTranslation(['MicroscopyTable', 'Common'])(
+ MicroscopyPanel
+);
+
+export default connectedMicroscopyPanel;
diff --git a/extensions/dicom-microscopy/src/components/ViewportOverlay/ViewportOverlay.css b/extensions/dicom-microscopy/src/components/ViewportOverlay/ViewportOverlay.css
new file mode 100644
index 0000000000..da46120c5b
--- /dev/null
+++ b/extensions/dicom-microscopy/src/components/ViewportOverlay/ViewportOverlay.css
@@ -0,0 +1,87 @@
+.DicomMicroscopyViewer .OpenLayersOverlay {
+ height: 100%;
+ width: 100%;
+ display: block !important;
+ pointer-events: none !important;
+}
+
+.DicomMicroscopyViewer .text-primary-light {
+ font-size: 14px;
+ color: yellow;
+ font-weight: normal;
+}
+
+.DicomMicroscopyViewer .text-primary-light span {
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ max-width: 300px;
+ /* text-shadow: 0px 1px 1px rgba(225, 225, 225, 0.6),
+ 0px 1px 1px rgba(225, 225, 225, 0.6),
+ 1px 1px 3px rgba(225, 225, 225, 0.9),
+ 1px 1px 3px rgba(225, 225, 225, 0.9),
+ 1px 1px 3px rgba(225, 225, 225, 0.9),
+ 1px 1px 3px rgba(225, 225, 225, 0.9); */
+}
+
+.DicomMicroscopyViewer .absolute {
+ position: absolute;
+}
+
+.DicomMicroscopyViewer .flex {
+ display: flex;
+}
+
+.DicomMicroscopyViewer .flex-row {
+ flex-direction: row;
+}
+
+.DicomMicroscopyViewer .flex-col {
+ flex-direction: column;
+}
+
+.DicomMicroscopyViewer .pointer-events-none {
+ pointer-events: none;
+}
+
+.DicomMicroscopyViewer .left-viewport-scrollbar {
+ left: 0.5rem;
+}
+
+.DicomMicroscopyViewer .right-viewport-scrollbar {
+ right: 1.3rem;
+}
+
+.DicomMicroscopyViewer .top-viewport {
+ top: 0.5rem;
+}
+
+.DicomMicroscopyViewer .bottom-viewport {
+ bottom: 0.5rem;
+}
+
+.DicomMicroscopyViewer .bottom-viewport.left-viewport {
+ bottom: 0.5rem;
+ left: calc(0.5rem + 250px);
+}
+
+.DicomMicroscopyViewer .right-viewport-scrollbar .flex {
+ justify-content: end;
+}
+
+.DicomMicroscopyViewer .microscopy-viewport-overlay {
+ padding: 0.5rem 1rem;
+ background: rgba(0, 0, 0, 0.5);
+ max-width: 40%;
+}
+
+.DicomMicroscopyViewer .microscopy-viewport-overlay .flex {
+ max-width: 100%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.DicomMicroscopyViewer .top-viewport .flex span:not(.font-light) {
+ flex-shrink: 0;
+}
\ No newline at end of file
diff --git a/extensions/dicom-microscopy/src/components/ViewportOverlay/index.tsx b/extensions/dicom-microscopy/src/components/ViewportOverlay/index.tsx
new file mode 100644
index 0000000000..bbe863e711
--- /dev/null
+++ b/extensions/dicom-microscopy/src/components/ViewportOverlay/index.tsx
@@ -0,0 +1,155 @@
+import React from 'react';
+import classnames from 'classnames';
+import ConfigPoint from 'config-point';
+
+import listComponentGenerator from './listComponentGenerator';
+import './ViewportOverlay.css';
+import {
+ formatDICOMDate,
+ formatDICOMTime,
+ formatNumberPrecision,
+ formatPN,
+} from './utils';
+
+interface OverylayItem {
+ id: string;
+ title: string;
+ value?: (props: any) => string;
+ condition?: (props: any) => boolean;
+ contents?: (props: any) => { className: string; value: any };
+ generator?: (props: any) => any;
+}
+
+/**
+ *
+ * @param {*} config is a configuration object that defines four lists of elements,
+ * one topLeft, topRight, bottomLeft, bottomRight contents.
+ * @param {*} extensionManager is used to load the image data.
+ * @returns
+ */
+export const generateFromConfig = ({
+ topLeft,
+ topRight,
+ bottomLeft,
+ bottomRight,
+ itemGenerator,
+}: {
+ topLeft: OverylayItem[];
+ topRight: OverylayItem[];
+ bottomLeft: OverylayItem[];
+ bottomRight: OverylayItem[];
+ itemGenerator: (props: any) => any;
+}) => {
+ return (props: any) => {
+ const topLeftClass = 'top-viewport left-viewport text-primary-light';
+ const topRightClass =
+ 'top-viewport right-viewport-scrollbar text-primary-light';
+ const bottomRightClass =
+ 'bottom-viewport right-viewport-scrollbar text-primary-light';
+ const bottomLeftClass = 'bottom-viewport left-viewport text-primary-light';
+ const overlay = 'absolute pointer-events-none microscopy-viewport-overlay';
+
+ return (
+ <>
+ {topLeft && topLeft.length > 0 && (
+
+ {listComponentGenerator({ ...props, list: topLeft, itemGenerator })}
+
+ )}
+ {topRight && topRight.length > 0 && (
+
+ {listComponentGenerator({
+ ...props,
+ list: topRight,
+ itemGenerator,
+ })}
+
+ )}
+ {bottomRight && bottomRight.length > 0 && (
+
+ {listComponentGenerator({
+ ...props,
+ list: bottomRight,
+ itemGenerator,
+ })}
+
+ )}
+ {bottomLeft && bottomLeft.length > 0 && (
+
+ {listComponentGenerator({
+ ...props,
+ list: bottomLeft,
+ itemGenerator,
+ })}
+
+ )}
+ >
+ );
+ };
+};
+
+const itemGenerator = (props: any) => {
+ const { item } = props;
+ const { title, value: valueFunc, condition, contents } = item;
+ props.image = { ...props.image, ...props.metadata };
+ props.formatDate = formatDICOMDate;
+ props.formatTime = formatDICOMTime;
+ props.formatPN = formatPN;
+ props.formatNumberPrecision = formatNumberPrecision;
+ if (condition && !condition(props)) return null;
+ if (!contents && !valueFunc) return null;
+ const value = valueFunc && valueFunc(props);
+ const contentsValue = (contents && contents(props)) || [
+ { className: 'mr-1', value: title },
+ { classname: 'mr-1 font-light', value },
+ ];
+
+ return (
+
+ {contentsValue.map((content, idx) => (
+
+ {content.value}
+
+ ))}
+
+ );
+};
+
+const { MicroscopyViewportOverlay } = ConfigPoint.register({
+ MicroscopyViewportOverlay: {
+ configBase: {
+ topLeft: [
+ // {
+ // id: 'sm-overlay-patient-name',
+ // title: 'PatientName',
+ // condition: ({ instance }) =>
+ // instance && instance.PatientName && instance.PatientName.Alphabetic,
+ // value: ({ instance }) =>
+ // instance.PatientName && instance.PatientName.Alphabetic,
+ // } as OverylayItem,
+ ],
+ topRight: [] as OverylayItem[],
+ bottomLeft: [] as OverylayItem[],
+ bottomRight: [] as OverylayItem[],
+ itemGenerator,
+ generateFromConfig,
+ },
+ ...(window.config?.MicroscopyViewportOverlay || {}),
+ },
+});
+
+export default MicroscopyViewportOverlay.generateFromConfig(
+ MicroscopyViewportOverlay
+);
diff --git a/extensions/dicom-microscopy/src/components/ViewportOverlay/listComponentGenerator.tsx b/extensions/dicom-microscopy/src/components/ViewportOverlay/listComponentGenerator.tsx
new file mode 100644
index 0000000000..04150625df
--- /dev/null
+++ b/extensions/dicom-microscopy/src/components/ViewportOverlay/listComponentGenerator.tsx
@@ -0,0 +1,12 @@
+const listComponentGenerator = props => {
+ const { list, itemGenerator } = props;
+ if (!list) return;
+ return list.map(item => {
+ if (!item) return;
+ const generator = item.generator || itemGenerator;
+ if (!generator) throw new Error(`No generator for ${item}`);
+ return generator({ ...props, item });
+ });
+};
+
+export default listComponentGenerator;
diff --git a/extensions/dicom-microscopy/src/components/ViewportOverlay/utils.ts b/extensions/dicom-microscopy/src/components/ViewportOverlay/utils.ts
new file mode 100644
index 0000000000..81f56ad05f
--- /dev/null
+++ b/extensions/dicom-microscopy/src/components/ViewportOverlay/utils.ts
@@ -0,0 +1,102 @@
+import moment from 'moment';
+import * as cornerstone from '@cornerstonejs/core';
+
+/**
+ * Checks if value is valid.
+ *
+ * @param {number} value
+ * @returns {boolean} is valid.
+ */
+export function isValidNumber(value) {
+ return typeof value === 'number' && !isNaN(value);
+}
+
+/**
+ * Formats number precision.
+ *
+ * @param {number} number
+ * @param {number} precision
+ * @returns {number} formatted number.
+ */
+export function formatNumberPrecision(number, precision) {
+ if (number !== null) {
+ return parseFloat(number).toFixed(precision);
+ }
+}
+
+/**
+ * Formats DICOM date.
+ *
+ * @param {string} date
+ * @param {string} strFormat
+ * @returns {string} formatted date.
+ */
+export function formatDICOMDate(date, strFormat = 'MMM D, YYYY') {
+ return moment(date, 'YYYYMMDD').format(strFormat);
+}
+
+/**
+ * DICOM Time is stored as HHmmss.SSS, where:
+ * HH 24 hour time:
+ * m mm 0..59 Minutes
+ * s ss 0..59 Seconds
+ * S SS SSS 0..999 Fractional seconds
+ *
+ * Goal: '24:12:12'
+ *
+ * @param {*} time
+ * @param {string} strFormat
+ * @returns {string} formatted name.
+ */
+export function formatDICOMTime(time, strFormat = 'HH:mm:ss') {
+ return moment(time, 'HH:mm:ss').format(strFormat);
+}
+
+/**
+ * Formats a patient name for display purposes
+ *
+ * @param {string} name
+ * @returns {string} formatted name.
+ */
+export function formatPN(name) {
+ if (!name) {
+ return;
+ }
+
+ // Convert the first ^ to a ', '. String.replace() only affects
+ // the first appearance of the character.
+ const commaBetweenFirstAndLast = name.replace('^', ', ');
+
+ // Replace any remaining '^' characters with spaces
+ const cleaned = commaBetweenFirstAndLast.replace(/\^/g, ' ');
+
+ // Trim any extraneous whitespace
+ return cleaned.trim();
+}
+
+/**
+ * Gets compression type
+ *
+ * @param {number} imageId
+ * @returns {string} comrpession type.
+ */
+export function getCompression(imageId) {
+ const generalImageModule =
+ cornerstone.metaData.get('generalImageModule', imageId) || {};
+ const {
+ lossyImageCompression,
+ lossyImageCompressionRatio,
+ lossyImageCompressionMethod,
+ } = generalImageModule;
+
+ if (lossyImageCompression === '01' && lossyImageCompressionRatio !== '') {
+ const compressionMethod = lossyImageCompressionMethod || 'Lossy: ';
+ const compressionRatio = formatNumberPrecision(
+ lossyImageCompressionRatio,
+ 2
+ );
+ return compressionMethod + compressionRatio + ' : 1';
+ }
+
+ return 'Lossless / Uncompressed';
+}
diff --git a/extensions/dicom-microscopy/src/getPanelModule.tsx b/extensions/dicom-microscopy/src/getPanelModule.tsx
new file mode 100644
index 0000000000..76f0004459
--- /dev/null
+++ b/extensions/dicom-microscopy/src/getPanelModule.tsx
@@ -0,0 +1,46 @@
+import React from 'react';
+import { useViewportGrid } from '@ohif/ui';
+import MicroscopyPanel from './components/MicroscopyPanel/MicroscopyPanel';
+import microscopyManager from './tools/microscopyManager';
+
+// TODO:
+// - No loading UI exists yet
+// - cancel promises when component is destroyed
+// - show errors in UI for thumbnails if promise fails
+
+export default function getPanelModule({
+ commandsManager,
+ extensionManager,
+ servicesManager,
+}) {
+ const wrappedMeasurementPanel = () => {
+ const [
+ { activeViewportIndex, viewports },
+ viewportGridService,
+ ] = useViewportGrid();
+
+ return (
+ {}}
+ onRejectComplete={() => {}}
+ commandsManager={commandsManager}
+ servicesManager={servicesManager}
+ extensionManager={extensionManager}
+ />
+ );
+ };
+
+ return [
+ {
+ name: 'measure',
+ iconName: 'tab-linear',
+ iconLabel: 'Measure',
+ label: 'Measurements',
+ secondaryLabel: 'Measurements',
+ component: wrappedMeasurementPanel,
+ },
+ ];
+}
diff --git a/extensions/dicom-microscopy/src/helpers/formatDICOMDate.js b/extensions/dicom-microscopy/src/helpers/formatDICOMDate.js
new file mode 100644
index 0000000000..c048df4727
--- /dev/null
+++ b/extensions/dicom-microscopy/src/helpers/formatDICOMDate.js
@@ -0,0 +1,11 @@
+import moment from 'moment';
+
+/**
+ * Formats DICOM date.
+ *
+ * @param {string} date
+ * @param {string} strFormat
+ */
+export default function formatDICOMDate(date, strFormat = 'MMM D, YYYY') {
+ return moment(date, 'YYYYMMDD').format(strFormat);
+}
diff --git a/extensions/dicom-microscopy/src/helpers/formatDICOMDate.test.js b/extensions/dicom-microscopy/src/helpers/formatDICOMDate.test.js
new file mode 100644
index 0000000000..abb9b2c1fd
--- /dev/null
+++ b/extensions/dicom-microscopy/src/helpers/formatDICOMDate.test.js
@@ -0,0 +1,9 @@
+import formatDICOMDate from './formatDICOMDate';
+
+describe('formatDICOMDate', () => {
+ it('should format DICOM date string', () => {
+ const date = '20180916';
+ const formattedDate = formatDICOMDate(date);
+ expect(formattedDate).toEqual('Sep 16, 2018');
+ });
+});
diff --git a/extensions/dicom-microscopy/src/helpers/formatDICOMPatientName.js b/extensions/dicom-microscopy/src/helpers/formatDICOMPatientName.js
new file mode 100644
index 0000000000..fd62d94c9d
--- /dev/null
+++ b/extensions/dicom-microscopy/src/helpers/formatDICOMPatientName.js
@@ -0,0 +1,21 @@
+/**
+ * Formats a patient name for display purposes.
+ *
+ * @param {string} name DICOM patient name string
+ * @returns {string} formatted name
+ */
+export default function formatDICOMPatientName(name) {
+ if (typeof name !== 'string') return;
+
+ /**
+ * Convert the first ^ to a ', '. String.replace() only affects
+ * the first appearance of the character.
+ */
+ const commaBetweenFirstAndLast = name.replace('^', ', ');
+
+ /** Replace any remaining '^' characters with spaces */
+ const cleaned = commaBetweenFirstAndLast.replace(/\^/g, ' ');
+
+ /** Trim any extraneous whitespace */
+ return cleaned.trim();
+}
diff --git a/extensions/dicom-microscopy/src/helpers/formatDICOMPatientName.test.js b/extensions/dicom-microscopy/src/helpers/formatDICOMPatientName.test.js
new file mode 100644
index 0000000000..efda1ed026
--- /dev/null
+++ b/extensions/dicom-microscopy/src/helpers/formatDICOMPatientName.test.js
@@ -0,0 +1,17 @@
+import formatDICOMPatientName from './formatDICOMPatientName';
+
+describe('formatDICOMPatientName', () => {
+ it('should format DICOM patient name correctly', () => {
+ const patientName = 'Blackford^Test';
+ const formattedPatientName = formatDICOMPatientName(patientName);
+ expect(formattedPatientName).toEqual('Blackford, Test');
+ });
+
+ it('should return undefined it input is not a string', () => {
+ expect(formatDICOMPatientName(123)).toEqual(undefined);
+ expect(formatDICOMPatientName(null)).toEqual(undefined);
+ expect(formatDICOMPatientName(undefined)).toEqual(undefined);
+ expect(formatDICOMPatientName(false)).toEqual(undefined);
+ expect(formatDICOMPatientName([])).toEqual(undefined);
+ });
+});
diff --git a/extensions/dicom-microscopy/src/helpers/formatDICOMTime.js b/extensions/dicom-microscopy/src/helpers/formatDICOMTime.js
new file mode 100644
index 0000000000..ea2deb7dfd
--- /dev/null
+++ b/extensions/dicom-microscopy/src/helpers/formatDICOMTime.js
@@ -0,0 +1,17 @@
+import moment from 'moment';
+
+/**
+ * DICOM Time is stored as HHmmss.SSS, where:
+ * HH 24 hour time:
+ * m mm 0..59 Minutes
+ * s ss 0..59 Seconds
+ * S SS SSS 0..999 Fractional seconds
+ *
+ * Goal: '24:12:12'
+ *
+ * @param {*} time
+ * @param {string} strFormat
+ */
+export default function formatDICOMTime(time, strFormat = 'HH:mm:ss') {
+ return moment(time, 'HH:mm:ss').format(strFormat);
+}
diff --git a/extensions/dicom-microscopy/src/helpers/formatDICOMTime.test.js b/extensions/dicom-microscopy/src/helpers/formatDICOMTime.test.js
new file mode 100644
index 0000000000..a94d9bc83b
--- /dev/null
+++ b/extensions/dicom-microscopy/src/helpers/formatDICOMTime.test.js
@@ -0,0 +1,9 @@
+import formatDICOMTime from './formatDICOMTime';
+
+describe('formatDICOMTime', () => {
+ it('should format DICOM time string', () => {
+ const time = '101300.000';
+ const formattedTime = formatDICOMTime(time);
+ expect(formattedTime).toEqual('10:13:00');
+ });
+});
diff --git a/extensions/dicom-microscopy/src/helpers/formatNumberPrecision.js b/extensions/dicom-microscopy/src/helpers/formatNumberPrecision.js
new file mode 100644
index 0000000000..9aa152fe13
--- /dev/null
+++ b/extensions/dicom-microscopy/src/helpers/formatNumberPrecision.js
@@ -0,0 +1,9 @@
+/**
+ * Formats a number to a fixed precision.
+ *
+ * @param {number} number
+ * @param {number} precision
+ */
+export default function formatNumberPrecision(number, precision) {
+ return Number(parseFloat(number).toFixed(precision));
+}
diff --git a/extensions/dicom-microscopy/src/helpers/formatNumberPrecision.test.js b/extensions/dicom-microscopy/src/helpers/formatNumberPrecision.test.js
new file mode 100644
index 0000000000..0ccce4d9ef
--- /dev/null
+++ b/extensions/dicom-microscopy/src/helpers/formatNumberPrecision.test.js
@@ -0,0 +1,9 @@
+import formatNumberPrecision from './formatNumberPrecision';
+
+describe('formatNumberPrecision', () => {
+ it('should format number precision', () => {
+ const number = 0.229387;
+ const formattedNumber = formatNumberPrecision(number, 2);
+ expect(formattedNumber).toEqual(0.23);
+ });
+});
diff --git a/extensions/dicom-microscopy/src/helpers/index.js b/extensions/dicom-microscopy/src/helpers/index.js
new file mode 100644
index 0000000000..59468982c5
--- /dev/null
+++ b/extensions/dicom-microscopy/src/helpers/index.js
@@ -0,0 +1,15 @@
+import formatDICOMPatientName from './formatDICOMPatientName';
+import formatDICOMDate from './formatDICOMDate';
+import formatDICOMTime from './formatDICOMTime';
+import formatNumberPrecision from './formatNumberPrecision';
+import isValidNumber from './isValidNumber';
+
+const helpers = {
+ formatDICOMPatientName,
+ formatDICOMDate,
+ formatDICOMTime,
+ formatNumberPrecision,
+ isValidNumber,
+};
+
+export default helpers;
diff --git a/extensions/dicom-microscopy/src/helpers/isValidNumber.js b/extensions/dicom-microscopy/src/helpers/isValidNumber.js
new file mode 100644
index 0000000000..086b1c08ae
--- /dev/null
+++ b/extensions/dicom-microscopy/src/helpers/isValidNumber.js
@@ -0,0 +1,3 @@
+export default function isValidNumber(value) {
+ return typeof value === 'number' && !isNaN(value);
+}
diff --git a/extensions/dicom-microscopy/src/tools/microscopyManager.js b/extensions/dicom-microscopy/src/tools/microscopyManager.js
new file mode 100644
index 0000000000..082e4bf6eb
--- /dev/null
+++ b/extensions/dicom-microscopy/src/tools/microscopyManager.js
@@ -0,0 +1,575 @@
+import Publisher from '../utils/Publisher';
+import ViewerManager, { EVENTS as ViewerEvents } from './viewerManager';
+import RoiAnnotation, {
+ EVENTS as AnnotationEvents,
+} from '../utils/RoiAnnotation';
+import { DicomMetadataStore } from '@ohif/core';
+
+const EVENTS = {
+ ANNOTATION_UPDATED: 'annotationUpdated',
+ ANNOTATION_SELECTED: 'annotationSelected',
+ RELABEL: 'relabel',
+ DELETE: 'delete',
+};
+
+/**
+ * MicroscopyManager is responsible to manage multiple third-party API's
+ * microscopy viewers expose methods to manage the interaction with these
+ * viewers and handle their ROI graphics to create, remove and modify the
+ * ROI annotations relevant to the application
+ */
+class MicroscopyManager extends Publisher {
+ managedViewers = new Set();
+ roiUids = new Set();
+ annotations = {};
+ selectedAnnotation = null;
+ pendingFocus = false;
+
+ constructor() {
+ super();
+ this._onRoiAdded = this._onRoiAdded.bind(this);
+ this._onRoiModified = this._onRoiModified.bind(this);
+ this._onRoiRemoved = this._onRoiRemoved.bind(this);
+ this._onRoiUpdated = this._onRoiUpdated.bind(this);
+ this.isROIsVisible = true;
+ }
+
+ /**
+ * Cleares all the annotations and managed viewers, setting the manager state
+ * to its initial state
+ */
+ clear() {
+ this.managedViewers.forEach((managedViewer) => managedViewer.destroy());
+ this.managedViewers.clear();
+ for (var key in this.annotations) {
+ delete this.annotations[key];
+ }
+
+ this.roiUids.clear();
+ this.selectedAnnotation = null;
+ this.pendingFocus = false;
+ }
+
+ clearAnnotations() {
+ Object.keys(this.annotations).forEach((uid) => {
+ this.removeAnnotation(this.annotations[uid]);
+ });
+ }
+
+ /**
+ * Observes when a ROI graphic is added, creating the correspondent annotation
+ * with the current graphic and view state.
+ * Creates a subscription for label updating for the created annotation and
+ * publishes an ANNOTATION_UPDATED event when it happens.
+ * Also triggers the relabel process after the graphic is placed.
+ *
+ * @param {Object} data The published data
+ * @param {Object} data.roiGraphic The added ROI graphic object
+ * @param {ViewerManager} data.managedViewer The origin viewer for the event
+ */
+ _onRoiAdded(data) {
+ const { roiGraphic, managedViewer, label } = data;
+ const { studyInstanceUID, seriesInstanceUID } = managedViewer;
+ const viewState = managedViewer.getViewState();
+
+ const roiAnnotation = new RoiAnnotation(
+ roiGraphic,
+ studyInstanceUID,
+ seriesInstanceUID,
+ '',
+ viewState
+ );
+
+ this.roiUids.add(roiGraphic.uid);
+ this.annotations[roiGraphic.uid] = roiAnnotation;
+
+ roiAnnotation.subscribe(AnnotationEvents.LABEL_UPDATED, () => {
+ this.publish(EVENTS.ANNOTATION_UPDATED, roiAnnotation);
+ });
+
+ if (label !== undefined) {
+ roiAnnotation.setLabel(label);
+ } else {
+ const onRelabel = (item) =>
+ managedViewer.updateROIProperties({
+ uid: roiGraphic.uid,
+ properties: { label: item.label, finding: item.finding },
+ });
+ this.triggerRelabel(roiAnnotation, true, onRelabel);
+ }
+ }
+
+ /**
+ * Observes when a ROI graphic is modified, updating the correspondent
+ * annotation with the current graphic and view state.
+ *
+ * @param {Object} data The published data
+ * @param {Object} data.roiGraphic The modified ROI graphic object
+ */
+ _onRoiModified(data) {
+ const { roiGraphic, managedViewer } = data;
+ const roiAnnotation = this.getAnnotation(roiGraphic.uid);
+ if (!roiAnnotation) return;
+ roiAnnotation.setRoiGraphic(roiGraphic);
+ roiAnnotation.setViewState(managedViewer.getViewState());
+ }
+
+ /**
+ * Observes when a ROI graphic is removed, reflecting the removal in the
+ * annotations' state.
+ *
+ * @param {Object} data The published data
+ * @param {Object} data.roiGraphic The removed ROI graphic object
+ */
+ _onRoiRemoved(data) {
+ const { roiGraphic } = data;
+ this.roiUids.delete(roiGraphic.uid);
+ this.annotations[roiGraphic.uid].destroy();
+ delete this.annotations[roiGraphic.uid];
+ }
+
+ /**
+ * Observes any changes on ROI graphics and synchronize all the managed
+ * viewers to reflect those changes.
+ * Also publishes an ANNOTATION_UPDATED event to notify the subscribers.
+ *
+ * @param {Object} data The published data
+ * @param {Object} data.roiGraphic The added ROI graphic object
+ * @param {ViewerManager} data.managedViewer The origin viewer for the event
+ */
+ _onRoiUpdated(data) {
+ const { roiGraphic, managedViewer } = data;
+ this.synchronizeViewers(managedViewer);
+ this.publish(EVENTS.ANNOTATION_UPDATED, this.getAnnotation(roiGraphic.uid));
+ }
+
+ /**
+ * Creates the subscriptions for the managed viewer being added
+ *
+ * @param {ViewerManager} managedViewer The viewer being added
+ */
+ _addManagedViewerSubscriptions(managedViewer) {
+ managedViewer.subscribe(ViewerEvents.ADDED, this._onRoiAdded);
+ managedViewer.subscribe(ViewerEvents.MODIFIED, this._onRoiModified);
+ managedViewer.subscribe(ViewerEvents.REMOVED, this._onRoiRemoved);
+ managedViewer.subscribe(ViewerEvents.UPDATED, this._onRoiUpdated);
+ }
+
+ /**
+ * Removes the subscriptions for the managed viewer being removed
+ *
+ * @param {ViewerManager} managedViewer The viewer being removed
+ */
+ _removeManagedViewerSubscriptions(managedViewer) {
+ managedViewer.unsubscribe(ViewerEvents.ADDED, this._onRoiAdded);
+ managedViewer.unsubscribe(ViewerEvents.MODIFIED, this._onRoiModified);
+ managedViewer.unsubscribe(ViewerEvents.REMOVED, this._onRoiRemoved);
+ managedViewer.unsubscribe(ViewerEvents.UPDATED, this._onRoiUpdated);
+ }
+
+ /**
+ * Returns the managed viewers that are displaying the image with the given
+ * study and series UIDs
+ *
+ * @param {String} studyInstanceUID UID for the study
+ * @param {String} seriesInstanceUID UID for the series
+ *
+ * @returns {Array} The managed viewers for the given series UID
+ */
+ _getManagedViewersForSeries(studyInstanceUID, seriesInstanceUID) {
+ const filter = (managedViewer) =>
+ managedViewer.studyInstanceUID === studyInstanceUID &&
+ managedViewer.seriesInstanceUID === seriesInstanceUID;
+ return Array.from(this.managedViewers).filter(filter);
+ }
+
+ /**
+ * Returns the managed viewers that are displaying the image with the given
+ * study UID
+ *
+ * @param {String} studyInstanceUID UID for the study
+ *
+ * @returns {Array} The managed viewers for the given series UID
+ */
+ getManagedViewersForStudy(studyInstanceUID) {
+ const filter = (managedViewer) =>
+ managedViewer.studyInstanceUID === studyInstanceUID;
+ return Array.from(this.managedViewers).filter(filter);
+ }
+
+ /**
+ * Restores the created annotations for the viewer being added
+ *
+ * @param {ViewerManager} managedViewer The viewer being added
+ */
+ _restoreAnnotations(managedViewer) {
+ const { studyInstanceUID, seriesInstanceUID } = managedViewer;
+ const annotations = this.getAnnotationsForSeries(
+ studyInstanceUID,
+ seriesInstanceUID
+ );
+ annotations.forEach((roiAnnotation) => {
+ managedViewer.addRoiGraphic(roiAnnotation.roiGraphic);
+ });
+ }
+
+ /**
+ * Creates a managed viewer instance for the given thrid-party API's viewer.
+ * Restores existing annotations for the given study/series.
+ * Adds event subscriptions for the viewer being added.
+ * Focuses the selected annotation when the viewer is being loaded into the
+ * active viewport.
+ *
+ * @param {Object} viewer Third-party viewer API's object to be managed
+ * @param {Number} viewportIndex The index of the viewport to load the viewer
+ * @param {HTMLElement} container The DOM element where it will be renderd
+ * @param {String} studyInstanceUID The study UID of the loaded image
+ * @param {String} seriesInstanceUID The series UID of the loaded image
+ * @param {Array} displaySets All displaySets related to the same StudyInstanceUID
+ *
+ * @returns {ViewerManager} managed viewer
+ */
+ addViewer(
+ viewer,
+ viewportIndex,
+ container,
+ studyInstanceUID,
+ seriesInstanceUID,
+ ) {
+ const managedViewer = new ViewerManager(
+ viewer,
+ viewportIndex,
+ container,
+ studyInstanceUID,
+ seriesInstanceUID
+ );
+
+ this._restoreAnnotations(managedViewer);
+ viewer._manager = managedViewer;
+ this.managedViewers.add(managedViewer);
+
+ // this._potentiallyLoadSR(studyInstanceUID, displaySets);
+ this._addManagedViewerSubscriptions(managedViewer);
+
+ if (this.pendingFocus) {
+ this.pendingFocus = false;
+ this.focusAnnotation(this.selectedAnnotation, viewportIndex);
+ }
+
+ return managedViewer;
+ }
+
+ _potentiallyLoadSR(StudyInstanceUID, displaySets) {
+ const studyMetadata = DicomMetadataStore.getStudy(StudyInstanceUID);
+ const smDisplaySet = displaySets.find((ds) => ds.Modality === 'SM');
+
+ const { FrameOfReferenceUID, othersFrameOfReferenceUID } = smDisplaySet;
+
+ if (!studyMetadata) {
+ return;
+ }
+
+ let derivedDisplaySets = FrameOfReferenceUID
+ ? displaySets.filter(
+ (ds) =>
+ ds.ReferencedFrameOfReferenceUID === FrameOfReferenceUID ||
+ // sometimes each depth instance has the different FrameOfReferenceID
+ othersFrameOfReferenceUID.includes(ds.ReferencedFrameOfReferenceUID)
+ )
+ : [];
+
+ if (!derivedDisplaySets.length) {
+ return;
+ }
+
+ derivedDisplaySets = derivedDisplaySets.filter(
+ (ds) => ds.Modality === 'SR'
+ );
+
+ if (derivedDisplaySets.some((ds) => ds.isLoaded === true)) {
+ // Don't auto load
+ return;
+ }
+
+ // find most recent and load it.
+ let recentDateTime = 0;
+ let recentDisplaySet = derivedDisplaySets[0];
+
+ derivedDisplaySets.forEach((ds) => {
+ const dateTime = Number(`${ds.SeriesDate}${ds.SeriesTime}`);
+ if (dateTime > recentDateTime) {
+ recentDateTime = dateTime;
+ recentDisplaySet = ds;
+ }
+ });
+
+ recentDisplaySet.isLoading = true;
+
+ recentDisplaySet.load(smDisplaySet);
+ }
+
+ /**
+ * Removes the given third-party viewer API's object from the managed viewers
+ * and cleares all its event subscriptions
+ *
+ * @param {Object} viewer Third-party viewer API's object to be removed
+ */
+ removeViewer(viewer) {
+ const managedViewer = viewer._manager;
+
+ this._removeManagedViewerSubscriptions(managedViewer);
+ managedViewer.destroy();
+ this.managedViewers.delete(managedViewer);
+ }
+
+ /**
+ * Toggle ROIs visibility
+ */
+ toggleROIsVisibility() {
+ if (!this.isROIsVisible) {
+ this.showROIs();
+ } else {
+ this.hideROIs();
+ }
+ this.isROIsVisible = !this.isROIsVisible;
+ }
+
+ /**
+ * Hide all ROIs
+ */
+ hideROIs() {
+ this.managedViewers.forEach((mv) => mv.hideROIs());
+ }
+
+ /** Show all ROIs */
+ showROIs() {
+ this.managedViewers.forEach((mv) => mv.showROIs());
+ }
+
+ /**
+ * Returns a RoiAnnotation instance for the given ROI UID
+ *
+ * @param {String} uid UID of the annotation
+ *
+ * @returns {RoiAnnotation} The RoiAnnotation instance found for the given UID
+ */
+ getAnnotation(uid) {
+ return this.annotations[uid];
+ }
+
+ /**
+ * Returns all the RoiAnnotation instances being managed
+ *
+ * @returns {Array} All RoiAnnotation instances
+ */
+ getAnnotations() {
+ const annotations = [];
+ Object.keys(this.annotations).forEach((uid) => {
+ annotations.push(this.getAnnotation(uid));
+ });
+ return annotations;
+ }
+
+ /**
+ * Returns the RoiAnnotation instances registered with the given study UID
+ *
+ * @param {String} studyInstanceUID UID for the study
+ */
+ getAnnotationsForStudy(studyInstanceUID) {
+ const filter = (a) => a.studyInstanceUID === studyInstanceUID;
+ return this.getAnnotations().filter(filter);
+ }
+
+ /**
+ * Returns the RoiAnnotation instances registered with the given study and
+ * series UIDs
+ *
+ * @param {String} studyInstanceUID UID for the study
+ * @param {String} seriesInstanceUID UID for the series
+ */
+ getAnnotationsForSeries(studyInstanceUID, seriesInstanceUID) {
+ const filter = (annotation) =>
+ annotation.studyInstanceUID === studyInstanceUID &&
+ annotation.seriesInstanceUID === seriesInstanceUID;
+ return this.getAnnotations().filter(filter);
+ }
+
+ /**
+ * Returns the selected RoiAnnotation instance or null if none is selected
+ *
+ * @returns {RoiAnnotation} The selected RoiAnnotation instance
+ */
+ getSelectedAnnotation() {
+ return this.selectedAnnotation;
+ }
+
+ /**
+ * Selects the given RoiAnnotation instance, publishing an ANNOTATION_SELECTED
+ * event to notify all the subscribers
+ *
+ * @param {RoiAnnotation} roiAnnotation The instance to be selected
+ */
+ selectAnnotation(roiAnnotation) {
+ this.selectedAnnotation = roiAnnotation;
+ this.publish(EVENTS.ANNOTATION_SELECTED, roiAnnotation);
+ }
+
+ /**
+ * Toggles overview map
+ *
+ * @param viewportIndex The active viewport index
+ * @returns {void}
+ */
+ toggleOverviewMap(viewportIndex) {
+ const managedViewers = Array.from(this.managedViewers);
+ const managedViewer = managedViewers.find(
+ (mv) => mv.viewportIndex === viewportIndex
+ );
+ if (managedViewer) {
+ managedViewer.toggleOverviewMap();
+ }
+ }
+
+ /**
+ * Removes a RoiAnnotation instance from the managed annotations and reflects
+ * its removal on all third-party viewers being managed
+ *
+ * @param {RoiAnnotation} roiAnnotation The instance to be removed
+ */
+ removeAnnotation(roiAnnotation) {
+ const { uid, studyInstanceUID, seriesInstanceUID } = roiAnnotation;
+ const filter = (managedViewer) =>
+ managedViewer.studyInstanceUID === studyInstanceUID &&
+ managedViewer.seriesInstanceUID === seriesInstanceUID;
+
+ const managedViewers = Array.from(this.managedViewers).filter(filter);
+
+ managedViewers.forEach((managedViewer) =>
+ managedViewer.removeRoiGraphic(uid)
+ );
+ }
+
+ /**
+ * Focus the given RoiAnnotation instance by changing the OpenLayers' Map view
+ * state of the managed viewer with the given viewport index.
+ * If the image for the given annotation is not yet loaded into the viewport,
+ * it will set a pendingFocus flag to true in order to perform the focus when
+ * the managed viewer instance is created.
+ *
+ * @param {RoiAnnotation} roiAnnotation RoiAnnotation instance to be focused
+ * @param {Number} viewportIndex Index of the viewport to focus
+ */
+ focusAnnotation(roiAnnotation, viewportIndex) {
+ const filter = (mv) => mv.viewportIndex === viewportIndex;
+ const managedViewer = Array.from(this.managedViewers).find(filter);
+ if (managedViewer) {
+ managedViewer.setViewStateByExtent(roiAnnotation);
+ } else {
+ this.pendingFocus = true;
+ }
+ }
+
+ /**
+ * Synchronize the ROI graphics for all the managed viewers that has the same
+ * series UID of the given managed viewer
+ *
+ * @param {ViewerManager} baseManagedViewer Reference managed viewer
+ */
+ synchronizeViewers(baseManagedViewer) {
+ const { studyInstanceUID, seriesInstanceUID } = baseManagedViewer;
+ const managedViewers = this._getManagedViewersForSeries(
+ studyInstanceUID,
+ seriesInstanceUID
+ );
+
+ // Prevent infinite loops arrising from updates.
+ managedViewers.forEach((managedViewer) =>
+ this._removeManagedViewerSubscriptions(managedViewer)
+ );
+
+ managedViewers.forEach((managedViewer) => {
+ if (managedViewer === baseManagedViewer) {
+ return;
+ }
+
+ const annotations = this.getAnnotationsForSeries(
+ studyInstanceUID,
+ seriesInstanceUID
+ );
+ managedViewer.clearRoiGraphics();
+ annotations.forEach((roiAnnotation) => {
+ managedViewer.addRoiGraphic(roiAnnotation.roiGraphic);
+ });
+ });
+
+ managedViewers.forEach((managedViewer) =>
+ this._addManagedViewerSubscriptions(managedViewer)
+ );
+ }
+
+ /**
+ * Activates interactions across all the viewers being managed
+ *
+ * @param {Array} interactions interactions
+ */
+ activateInteractions(interactions) {
+ this.managedViewers.forEach((mv) => mv.activateInteractions(interactions));
+ this.activeInteractions = interactions;
+ }
+
+ /**
+ * Triggers the relabelling process for the given RoiAnnotation instance, by
+ * publishing the RELABEL event to notify the subscribers
+ *
+ * @param {RoiAnnotation} roiAnnotation The instance to be relabelled
+ * @param {boolean} newAnnotation Whether the annotation is newly drawn (so it deletes on cancel).
+ */
+ triggerRelabel(roiAnnotation, newAnnotation = false, onRelabel) {
+ if (!onRelabel) {
+ onRelabel = ({ label }) =>
+ this.managedViewers.forEach((mv) =>
+ mv.updateROIProperties({
+ uid: roiAnnotation.uid,
+ properties: { label },
+ })
+ );
+ }
+
+ this.publish(EVENTS.RELABEL, {
+ roiAnnotation,
+ deleteCallback: () => this.removeAnnotation(roiAnnotation),
+ successCallback: onRelabel,
+ newAnnotation,
+ });
+ }
+
+ /**
+ * Triggers the deletion process for the given RoiAnnotation instance, by
+ * publishing the DELETE event to notify the subscribers
+ *
+ * @param {RoiAnnotation} roiAnnotation The instance to be deleted
+ */
+ triggerDelete(roiAnnotation) {
+ this.publish(EVENTS.DELETE, roiAnnotation);
+ }
+
+ /**
+ * Set ROI style for all managed viewers
+ *
+ * @param {string} uid The ROI uid that will be styled
+ * @param {object} styleOptions - Style options
+ * @param {object*} styleOptions.stroke - Style options for the outline of the geometry
+ * @param {number[]} styleOptions.stroke.color - RGBA color of the outline
+ * @param {number} styleOptions.stroke.width - Width of the outline
+ * @param {object*} styleOptions.fill - Style options for body the geometry
+ * @param {number[]} styleOptions.fill.color - RGBA color of the body
+ * @param {object*} styleOptions.image - Style options for image
+ */
+ setROIStyle(uid, styleOptions) {
+ this.managedViewers.forEach((mv) => mv.setROIStyle(uid, styleOptions));
+ }
+}
+
+export { EVENTS };
+
+export default new MicroscopyManager();
diff --git a/extensions/dicom-microscopy/src/tools/viewerManager.js b/extensions/dicom-microscopy/src/tools/viewerManager.js
new file mode 100644
index 0000000000..410abc9939
--- /dev/null
+++ b/extensions/dicom-microscopy/src/tools/viewerManager.js
@@ -0,0 +1,427 @@
+import Publisher from '../utils/Publisher';
+import coordinateFormatScoord3d2Geometry from '../utils/coordinateFormatScoord3d2Geometry';
+import styles from '../utils/styles';
+
+// Events from the third-party viewer
+const ApiEvents = {
+ ROI_ADDED: 'dicommicroscopyviewer_roi_added',
+ ROI_MODIFIED: 'dicommicroscopyviewer_roi_modified',
+ ROI_REMOVED: 'dicommicroscopyviewer_roi_removed',
+};
+
+const EVENTS = {
+ ADDED: 'added',
+ MODIFIED: 'modified',
+ REMOVED: 'removed',
+ UPDATED: 'updated',
+};
+
+/**
+ * ViewerManager encapsulates the complexity of the third-party viewer and
+ * expose only the features/behaviors that are relevant to the application
+ */
+class ViewerManager extends Publisher {
+ constructor(
+ viewer,
+ viewportIndex,
+ container,
+ studyInstanceUID,
+ seriesInstanceUID
+ ) {
+ super();
+ this.viewer = viewer;
+ this.viewportIndex = viewportIndex;
+ this.container = container;
+ this.studyInstanceUID = studyInstanceUID;
+ this.seriesInstanceUID = seriesInstanceUID;
+
+ this.onRoiAdded = this.roiAddedHandler.bind(this);
+ this.onRoiModified = this.roiModifiedHandler.bind(this);
+ this.onRoiRemoved = this.roiRemovedHandler.bind(this);
+
+ this.registerEvents();
+ this.activateDefaultInteractions();
+ }
+
+ /**
+ * Destroys this managed viewer instance, clearing all the event handlers
+ */
+ destroy() {
+ this.unregisterEvents();
+ }
+
+ /**
+ * Overrides the publish method and always send the ROI graphic object and
+ * this managed viewer instance
+ *
+ * @param {String} key key Subscription key
+ * @param {Object} roiGraphic ROI graphic object created by the third-party API
+ */
+ publish(key, roiGraphic) {
+ super.publish(key, {
+ roiGraphic,
+ managedViewer: this,
+ });
+ }
+
+ /**
+ * Registers all the relevant event handlers for the third-party API
+ */
+ registerEvents() {
+ this.container.addEventListener(ApiEvents.ROI_ADDED, this.onRoiAdded);
+ this.container.addEventListener(ApiEvents.ROI_MODIFIED, this.onRoiModified);
+ this.container.addEventListener(ApiEvents.ROI_REMOVED, this.onRoiRemoved);
+ }
+
+ /**
+ * Cleares all the relevant event handlers for the third-party API
+ */
+ unregisterEvents() {
+ this.container.removeEventListener(ApiEvents.ROI_ADDED, this.onRoiAdded);
+ this.container.removeEventListener(
+ ApiEvents.ROI_MODIFIED,
+ this.onRoiModified
+ );
+ this.container.removeEventListener(
+ ApiEvents.ROI_REMOVED,
+ this.onRoiRemoved
+ );
+ }
+
+ /**
+ * Handles the ROI_ADDED event triggered by the third-party API
+ *
+ * @param {Event} event Event triggered by the third-party API
+ */
+ roiAddedHandler(event) {
+ const roiGraphic = event.detail.payload;
+ this.publish(EVENTS.ADDED, roiGraphic);
+ this.publish(EVENTS.UPDATED, roiGraphic);
+ }
+
+ /**
+ * Handles the ROI_MODIFIED event triggered by the third-party API
+ *
+ * @param {Event} event Event triggered by the third-party API
+ */
+ roiModifiedHandler(event) {
+ const roiGraphic = event.detail.payload;
+ this.publish(EVENTS.MODIFIED, roiGraphic);
+ this.publish(EVENTS.UPDATED, roiGraphic);
+ }
+
+ /**
+ * Handles the ROI_REMOVED event triggered by the third-party API
+ *
+ * @param {Event} event Event triggered by the third-party API
+ */
+ roiRemovedHandler(event) {
+ const roiGraphic = event.detail.payload;
+ this.publish(EVENTS.REMOVED, roiGraphic);
+ this.publish(EVENTS.UPDATED, roiGraphic);
+ }
+
+ /**
+ * Run the given callback operation without triggering any events for this
+ * instance, so subscribers will not be affected
+ *
+ * @param {Function} callback Callback that will run sinlently
+ */
+ runSilently(callback) {
+ this.unregisterEvents();
+ callback();
+ this.registerEvents();
+ }
+
+ /**
+ * Removes all the ROI graphics from the third-party API
+ */
+ clearRoiGraphics() {
+ this.runSilently(() => this.viewer.removeAllROIs());
+ }
+
+ showROIs() {
+ this.viewer.showROIs();
+ }
+
+ hideROIs() {
+ this.viewer.hideROIs();
+ }
+
+ /**
+ * Adds the given ROI graphic into the third-party API
+ *
+ * @param {Object} roiGraphic ROI graphic object to be added
+ */
+ addRoiGraphic(roiGraphic) {
+ this.runSilently(() => this.viewer.addROI(roiGraphic, styles.default));
+ }
+
+ /**
+ * Adds the given ROI graphic into the third-party API, and also add a label.
+ * Used for importing from SR.
+ *
+ * @param {Object} roiGraphic ROI graphic object to be added.
+ * @param {String} label The label of the annotation.
+ */
+ addRoiGraphicWithLabel(roiGraphic, label) {
+ if (label) {
+ if (!roiGraphic.properties) roiGraphic.properties = {};
+ roiGraphic.properties.label = label;
+ }
+ this.runSilently(() => this.viewer.addROI(roiGraphic, styles.default));
+
+ super.publish(EVENTS.ADDED, {
+ roiGraphic,
+ managedViewer: this,
+ label,
+ });
+ }
+
+ /**
+ * Sets ROI style
+ *
+ * @param {String} uid ROI graphic UID to be styled
+ * @param {object} styleOptions - Style options
+ * @param {object} styleOptions.stroke - Style options for the outline of the geometry
+ * @param {number[]} styleOptions.stroke.color - RGBA color of the outline
+ * @param {number} styleOptions.stroke.width - Width of the outline
+ * @param {object} styleOptions.fill - Style options for body the geometry
+ * @param {number[]} styleOptions.fill.color - RGBA color of the body
+ * @param {object} styleOptions.image - Style options for image
+ */
+ setROIStyle(uid, styleOptions) {
+ this.viewer.setROIStyle(uid, styleOptions);
+ }
+
+ /**
+ * Removes the ROI graphic with the given UID from the third-party API
+ *
+ * @param {String} uid ROI graphic UID to be removed
+ */
+ removeRoiGraphic(uid) {
+ this.viewer.removeROI(uid);
+ }
+
+ /**
+ * Update properties of regions of interest.
+ *
+ * @param {object} roi - ROI to be updated
+ * @param {string} roi.uid - Unique identifier of the region of interest
+ * @param {object} roi.properties - ROI properties
+ * @returns {void}
+ */
+ updateROIProperties({ uid, properties }) {
+ this.viewer.updateROI({ uid, properties });
+ }
+
+ /**
+ * Toggles overview map
+ *
+ * @returns {void}
+ */
+ toggleOverviewMap() {
+ this.viewer.toggleOverviewMap();
+ }
+
+ /**
+ * Activates the viewer default interactions
+ * @returns {void}
+ */
+ activateDefaultInteractions() {
+ /** Disable browser's native context menu inside the canvas */
+ document
+ .querySelector('.DicomMicroscopyViewer')
+ .addEventListener(
+ 'contextmenu',
+ (event) => event.preventDefault(),
+ false
+ );
+ const defaultInteractions = [
+ [
+ 'dragPan',
+ {
+ bindings: {
+ mouseButtons: ['middle'],
+ },
+ },
+ ],
+ [
+ 'dragZoom',
+ {
+ bindings: {
+ mouseButtons: ['right'],
+ },
+ },
+ ],
+ ['modify', {}],
+ ];
+ this.activateInteractions(defaultInteractions);
+ }
+
+ /**
+ * Activates interactions
+ * @param {Array} interactions Interactions to be activated
+ * @returns {void}
+ */
+ activateInteractions(interactions) {
+ const interactionsMap = {
+ draw: (activate) =>
+ activate ? 'activateDrawInteraction' : 'deactivateDrawInteraction',
+ modify: (activate) =>
+ activate ? 'activateModifyInteraction' : 'deactivateModifyInteraction',
+ translate: (activate) =>
+ activate
+ ? 'activateTranslateInteraction'
+ : 'deactivateTranslateInteraction',
+ snap: (activate) =>
+ activate ? 'activateSnapInteraction' : 'deactivateSnapInteraction',
+ dragPan: (activate) =>
+ activate
+ ? 'activateDragPanInteraction'
+ : 'deactivateDragPanInteraction',
+ dragZoom: (activate) =>
+ activate
+ ? 'activateDragZoomInteraction'
+ : 'deactivateDragZoomInteraction',
+ };
+
+ const availableInteractionsName = Object.keys(interactionsMap);
+ availableInteractionsName.forEach((availableInteractionName) => {
+ const interaction = interactions.find(
+ (interaction) => interaction[0] === availableInteractionName
+ );
+ if (!interaction) {
+ const deactivateInteractionMethod =
+ interactionsMap[availableInteractionName](false);
+ this.viewer[deactivateInteractionMethod]();
+ } else {
+ const [name, config] = interaction;
+ const activateInteractionMethod = interactionsMap[name](true);
+ this.viewer[activateInteractionMethod](config);
+ }
+ });
+ }
+
+ /**
+ * Accesses the internals of third-party API and returns the OpenLayers Map
+ *
+ * @returns {Object} OpenLayers Map component instance
+ */
+ _getMapView() {
+ const map = this._getMap();
+ return map.getView();
+ }
+
+ _getMap() {
+ const symbols = Object.getOwnPropertySymbols(this.viewer);
+ const _map = symbols.find((s) => String(s) === 'Symbol(map)');
+ window['map'] = this.viewer[_map];
+ return this.viewer[_map];
+ }
+
+ /**
+ * Returns the current state for the OpenLayers View
+ *
+ * @returns {Object} Current view state
+ */
+ getViewState() {
+ const view = this._getMapView();
+ return {
+ center: view.getCenter(),
+ resolution: view.getResolution(),
+ zoom: view.getZoom(),
+ };
+ }
+
+ /**
+ * Sets the current state for the OpenLayers View
+ *
+ * @param {Object} viewState View state to be applied
+ */
+ setViewState(viewState) {
+ const view = this._getMapView();
+
+ view.setZoom(viewState.zoom);
+ view.setResolution(viewState.resolution);
+ view.setCenter(viewState.center);
+ }
+
+ setViewStateByExtent(roiAnnotation) {
+ const coordinates = roiAnnotation.getCoordinates();
+
+ if (Array.isArray(coordinates[0]) && !coordinates[2]) {
+ this._jumpToPolyline(coordinates);
+ } else if (Array.isArray(coordinates[0])) {
+ this._jumpToPolygonOrEllipse(coordinates);
+ } else {
+ this._jumpToPoint(coordinates);
+ }
+ }
+
+ _jumpToPoint(coord) {
+ const pyramid = this.viewer.imageMetadata;
+
+ const mappedCoord = coordinateFormatScoord3d2Geometry(coord, pyramid);
+ const view = this._getMapView();
+
+ view.setCenter(mappedCoord);
+ }
+
+ _jumpToPolyline(coord) {
+ const pyramid = this.viewer.imageMetadata;
+
+ const mappedCoord = coordinateFormatScoord3d2Geometry(coord, pyramid);
+ const view = this._getMapView();
+
+ const x = mappedCoord[0];
+ const y = mappedCoord[1];
+
+ const xab = (x[0] + y[0]) / 2;
+ const yab = (x[1] + y[1]) / 2;
+ const midpoint = [xab, yab];
+
+ view.setCenter(midpoint);
+ }
+
+ _jumpToPolygonOrEllipse(coordinates) {
+ const pyramid = this.viewer.imageMetadata;
+
+ let minX = Infinity;
+ let maxX = -Infinity;
+ let minY = Infinity;
+ let maxY = -Infinity;
+
+ coordinates.forEach((coord) => {
+ let mappedCoord = coordinateFormatScoord3d2Geometry(coord, pyramid);
+
+ const [x, y] = mappedCoord;
+ if (x < minX) {
+ minX = x;
+ } else if (x > maxX) {
+ maxX = x;
+ }
+
+ if (y < minY) {
+ minY = y;
+ } else if (y > maxY) {
+ maxY = y;
+ }
+ });
+
+ const width = maxX - minX;
+ const height = maxY - minY;
+
+ minX -= 0.5 * width;
+ maxX += 0.5 * width;
+ minY -= 0.5 * height;
+ maxY += 0.5 * height;
+
+ const map = this._getMap();
+ map.getView().fit([minX, minY, maxX, maxY], map.getSize());
+ }
+}
+
+export { EVENTS };
+
+export default ViewerManager;
diff --git a/extensions/dicom-microscopy/src/utils/DEVICE_OBSERVER_UID.js b/extensions/dicom-microscopy/src/utils/DEVICE_OBSERVER_UID.js
new file mode 100644
index 0000000000..cb35858705
--- /dev/null
+++ b/extensions/dicom-microscopy/src/utils/DEVICE_OBSERVER_UID.js
@@ -0,0 +1,5 @@
+// We need to define a UID for this extension as a device, and it should be the same for all saves:
+
+const uid = '2.25.285241207697168520771311899641885187923';
+
+export default uid;
diff --git a/extensions/dicom-microscopy/src/utils/Publisher.js b/extensions/dicom-microscopy/src/utils/Publisher.js
new file mode 100644
index 0000000000..e7a2d1c5fe
--- /dev/null
+++ b/extensions/dicom-microscopy/src/utils/Publisher.js
@@ -0,0 +1,53 @@
+/**
+ * Publisher allows applying pub-sub by extending it in other classes definition
+ */
+export default class Publisher {
+ constructor() {
+ this.subscriptions = new Map();
+ }
+
+ /**
+ * Triggers a publication for the given subscription key.
+ * It will run the callbacks for all subscribers with the provided value.
+ * The subscribers' callbacks will be called in the order they were defined.
+ *
+ * @param {String} key Subscription key
+ * @param {any} value Value that will be send to all subscribers
+ */
+ publish(key, value) {
+ const subscriptions = this.subscriptions.get(key);
+ if (subscriptions) {
+ subscriptions.forEach(callback => callback(value));
+ }
+ }
+
+ /**
+ * Subscribe to a specific key, providing a callback that will run when the
+ * publish occurs.
+ *
+ * @param {String} key Subscription key
+ * @param {Function} callback Callback that will be registered for the key
+ */
+ subscribe(key, callback) {
+ if (!this.subscriptions.get(key)) {
+ this.subscriptions.set(key, [callback]);
+ } else {
+ this.subscriptions.get(key).push(callback);
+ }
+ }
+
+ /**
+ * Remove the subscription for the given key and callback, so the callback
+ * will no longer run if a publish to key occurs.
+ *
+ * @param {String} key Subscription key
+ * @param {Function} callback Callback that was registered for the key
+ */
+ unsubscribe(key, callback) {
+ const subscriptions = this.subscriptions.get(key);
+ const index = subscriptions.indexOf(callback);
+ if (index > -1) {
+ subscriptions.splice(index, 1);
+ }
+ }
+}
diff --git a/extensions/dicom-microscopy/src/utils/RoiAnnotation.js b/extensions/dicom-microscopy/src/utils/RoiAnnotation.js
new file mode 100644
index 0000000000..34f9905f2b
--- /dev/null
+++ b/extensions/dicom-microscopy/src/utils/RoiAnnotation.js
@@ -0,0 +1,195 @@
+import Publisher from './Publisher';
+import areaOfPolygon from './areaOfPolygon';
+
+const EVENTS = {
+ LABEL_UPDATED: 'labelUpdated',
+ GRAPHIC_UPDATED: 'graphicUpdated',
+ VIEW_UPDATED: 'viewUpdated',
+ REMOVED: 'removed',
+};
+
+/**
+ * Represents a single annotation for the Microscopy Viewer
+ */
+class RoiAnnotation extends Publisher {
+ constructor(
+ roiGraphic,
+ studyInstanceUID,
+ seriesInstanceUID,
+ label = '',
+ viewState = null
+ ) {
+ super();
+ this.uid = roiGraphic.uid;
+ this.roiGraphic = roiGraphic;
+ this.studyInstanceUID = studyInstanceUID;
+ this.seriesInstanceUID = seriesInstanceUID;
+ this.label = label;
+ this.viewState = viewState;
+ this.setMeasurements(roiGraphic);
+ }
+
+ getScoord3d() {
+ const roiGraphic = this.roiGraphic;
+
+ const roiGraphicSymbols = Object.getOwnPropertySymbols(roiGraphic);
+ const _scoord3d = roiGraphicSymbols.find(
+ s => String(s) === 'Symbol(scoord3d)'
+ );
+
+ return roiGraphic[_scoord3d];
+ }
+
+ getCoordinates() {
+ const scoord3d = this.getScoord3d();
+ const scoord3dSymbols = Object.getOwnPropertySymbols(scoord3d);
+
+ const _coordinates = scoord3dSymbols.find(
+ s => String(s) === 'Symbol(coordinates)'
+ );
+
+ const coordinates = scoord3d[_coordinates];
+ return coordinates;
+ }
+
+ /**
+ * When called will trigger the REMOVED event
+ */
+ destroy() {
+ this.publish(EVENTS.REMOVED, this);
+ }
+
+ /**
+ * Updates the ROI graphic for the annotation and triggers the GRAPHIC_UPDATED
+ * event
+ *
+ * @param {Object} roiGraphic
+ */
+ setRoiGraphic(roiGraphic) {
+ this.roiGraphic = roiGraphic;
+ this.setMeasurements();
+ this.publish(EVENTS.GRAPHIC_UPDATED, this);
+ }
+
+ /**
+ * Update ROI measurement values based on its scoord3d coordinates.
+ *
+ * @returns {void}
+ */
+ setMeasurements() {
+ const type = this.roiGraphic.scoord3d.graphicType;
+ const coordinates = this.roiGraphic.scoord3d.graphicData;
+
+ switch (type) {
+ case 'ELLIPSE':
+ // This is a circle so only need one side
+ const point1 = coordinates[0];
+ const point2 = coordinates[1];
+
+ let xLength2 = point2[0] - point1[0];
+ let yLength2 = point2[1] - point1[1];
+
+ xLength2 *= xLength2;
+ yLength2 *= yLength2;
+
+ const length = Math.sqrt(xLength2 + yLength2);
+ const radius = length / 2;
+
+ const areaEllipse = Math.PI * radius * radius;
+ this._area = areaEllipse;
+ this._length = undefined;
+ break;
+
+ case 'POLYGON':
+ const areaPolygon = areaOfPolygon(coordinates);
+ this._area = areaPolygon;
+ this._length = undefined;
+ break;
+
+ case 'POINT':
+ this._area = undefined;
+ this._length = undefined;
+ break;
+
+ case 'POLYLINE':
+ let len = 0;
+ for (let i=1; i {}
+}) {
+ const dialogId = 'microscopy-input-dialog';
+
+ const onSubmitHandler = ({ action, value }) => {
+ switch (action.id) {
+ case 'save':
+ callback(value.value, action.id);
+ break;
+ case 'cancel':
+ callback('', action.id);
+ break;
+ }
+ uiDialogService.dismiss({ id: dialogId });
+ };
+
+ if (uiDialogService) {
+ uiDialogService.create({
+ id: dialogId,
+ centralize: true,
+ isDraggable: false,
+ showOverlay: true,
+ content: Dialog,
+ contentProps: {
+ title: title,
+ value: { value: defaultValue },
+ noCloseButton: true,
+ onClose: () => uiDialogService.dismiss({ id: dialogId }),
+ actions: [
+ { id: 'cancel', text: 'Cancel', type: 'primary' },
+ { id: 'save', text: 'Save', type: 'secondary' },
+ ],
+ onSubmit: onSubmitHandler,
+ body: ({ value, setValue }) => {
+ return (
+
+ {
+ event.persist();
+ setValue(value => ({ ...value, value: event.target.value }));
+ }}
+ onKeyPress={event => {
+ if (event.key === 'Enter') {
+ onSubmitHandler({ value, action: { id: 'save' } });
+ }
+ }}
+ />
+
+ );
+ },
+ },
+ });
+ }
+}
\ No newline at end of file
diff --git a/extensions/dicom-microscopy/src/utils/coordinateFormatScoord3d2Geometry.js b/extensions/dicom-microscopy/src/utils/coordinateFormatScoord3d2Geometry.js
new file mode 100644
index 0000000000..70b6e42d54
--- /dev/null
+++ b/extensions/dicom-microscopy/src/utils/coordinateFormatScoord3d2Geometry.js
@@ -0,0 +1,110 @@
+import { inv, multiply } from 'mathjs';
+
+// TODO -> This is pulled out of some internal logic from Dicom Microscopy Viewer,
+// We should likely just expose this there.
+
+export default function coordinateFormatScoord3d2Geometry(
+ coordinates,
+ pyramid
+) {
+ let transform = false;
+ if (!Array.isArray(coordinates[0])) {
+ coordinates = [coordinates];
+ transform = true;
+ }
+ const metadata = pyramid[pyramid.length - 1];
+ const orientation = metadata.ImageOrientationSlide;
+ const spacing = _getPixelSpacing(metadata);
+ const origin = metadata.TotalPixelMatrixOriginSequence[0];
+ const offset = [
+ Number(origin.XOffsetInSlideCoordinateSystem),
+ Number(origin.YOffsetInSlideCoordinateSystem),
+ ];
+
+ coordinates = coordinates.map(c => {
+ const slideCoord = [c[0], c[1]];
+ const pixelCoord = mapSlideCoord2PixelCoord({
+ offset,
+ orientation,
+ spacing,
+ point: slideCoord,
+ });
+ return [pixelCoord[0], -(pixelCoord[1] + 1), 0];
+ });
+ if (transform) {
+ return coordinates[0];
+ }
+ return coordinates;
+}
+
+function _getPixelSpacing(metadata) {
+ if (metadata.PixelSpacing) return metadata.PixelSpacing;
+ const functionalGroup = metadata.SharedFunctionalGroupsSequence[0];
+ const pixelMeasures = functionalGroup.PixelMeasuresSequence[0];
+ return pixelMeasures.PixelSpacing;
+}
+
+function mapSlideCoord2PixelCoord(options) {
+ // X and Y Offset in Slide Coordinate System
+ if (!('offset' in options)) {
+ throw new Error('Option "offset" is required.');
+ }
+ if (!Array.isArray(options.offset)) {
+ throw new Error('Option "offset" must be an array.');
+ }
+ if (options.offset.length !== 2) {
+ throw new Error('Option "offset" must be an array with 2 elements.');
+ }
+ const offset = options.offset;
+
+ // Image Orientation Slide with direction cosines for Row and Column direction
+ if (!('orientation' in options)) {
+ throw new Error('Option "orientation" is required.');
+ }
+ if (!Array.isArray(options.orientation)) {
+ throw new Error('Option "orientation" must be an array.');
+ }
+ if (options.orientation.length !== 6) {
+ throw new Error('Option "orientation" must be an array with 6 elements.');
+ }
+ const orientation = options.orientation;
+
+ // Pixel Spacing along the Row and Column direction
+ if (!('spacing' in options)) {
+ throw new Error('Option "spacing" is required.');
+ }
+ if (!Array.isArray(options.spacing)) {
+ throw new Error('Option "spacing" must be an array.');
+ }
+ if (options.spacing.length !== 2) {
+ throw new Error('Option "spacing" must be an array with 2 elements.');
+ }
+ const spacing = options.spacing;
+
+ // X and Y coordinate in the Slide Coordinate System
+ if (!('point' in options)) {
+ throw new Error('Option "point" is required.');
+ }
+ if (!Array.isArray(options.point)) {
+ throw new Error('Option "point" must be an array.');
+ }
+ if (options.point.length !== 2) {
+ throw new Error('Option "point" must be an array with 2 elements.');
+ }
+ const point = options.point;
+
+ const m = [
+ [orientation[0] * spacing[1], orientation[3] * spacing[0], offset[0]],
+ [orientation[1] * spacing[1], orientation[4] * spacing[0], offset[1]],
+ [0, 0, 1],
+ ];
+ const mInverted = inv(m);
+
+ const vSlide = [[point[0]], [point[1]], [1]];
+
+ const vImage = multiply(mInverted, vSlide);
+
+ const row = Number(vImage[1][0].toFixed(4));
+ const col = Number(vImage[0][0].toFixed(4));
+ return [col, row];
+}
diff --git a/extensions/dicom-microscopy/src/utils/dcmCodeValues.js b/extensions/dicom-microscopy/src/utils/dcmCodeValues.js
new file mode 100644
index 0000000000..82d489919b
--- /dev/null
+++ b/extensions/dicom-microscopy/src/utils/dcmCodeValues.js
@@ -0,0 +1,14 @@
+const DCM_CODE_VALUES = {
+ IMAGING_MEASUREMENTS: '126010',
+ MEASUREMENT_GROUP: '125007',
+ IMAGE_REGION: '111030',
+ FINDING: '121071',
+ TRACKING_UNIQUE_IDENTIFIER: '112039',
+ LENGTH: '410668003',
+ AREA: '42798000',
+ SHORT_AXIS: 'G-A186',
+ LONG_AXIS: 'G-A185',
+ ELLIPSE_AREA: 'G-D7FE', // TODO: Remove this
+};
+
+export default DCM_CODE_VALUES;
diff --git a/extensions/dicom-microscopy/src/utils/dicomWebClient.ts b/extensions/dicom-microscopy/src/utils/dicomWebClient.ts
new file mode 100644
index 0000000000..3ecc15b0c3
--- /dev/null
+++ b/extensions/dicom-microscopy/src/utils/dicomWebClient.ts
@@ -0,0 +1,8 @@
+import * as DICOMwebClient from 'dicomweb-client';
+
+export default function getDicomWebClient() {
+ const url = window.config.dataSources[0].configuration.wadoRoot;
+ const client = new DICOMwebClient.api.DICOMwebClient({ url });
+ client.wadoURL = url;
+ return client;
+}
diff --git a/extensions/dicom-microscopy/src/utils/getSourceDisplaySet.js b/extensions/dicom-microscopy/src/utils/getSourceDisplaySet.js
new file mode 100644
index 0000000000..207dc1dd7c
--- /dev/null
+++ b/extensions/dicom-microscopy/src/utils/getSourceDisplaySet.js
@@ -0,0 +1,31 @@
+/**
+ * Get referenced SM displaySet from SR displaySet
+ *
+ * @param {*} allDisplaySets
+ * @param {*} microscopySRDisplaySet
+ * @returns
+ */
+export default function getSourceDisplaySet(allDisplaySets, microscopySRDisplaySet) {
+ const { ReferencedFrameOfReferenceUID } = microscopySRDisplaySet;
+
+ const otherDisplaySets = allDisplaySets.filter(
+ ds =>
+ ds.displaySetInstanceUID !== microscopySRDisplaySet.displaySetInstanceUID
+ );
+ const referencedDisplaySet = otherDisplaySets.find(
+ displaySet =>
+ displaySet.Modality === 'SM' &&
+ (displaySet.FrameOfReferenceUID === ReferencedFrameOfReferenceUID
+ // sometimes each depth instance has the different FrameOfReferenceID
+ || displaySet.othersFrameOfReferenceUID.includes(ReferencedFrameOfReferenceUID))
+ );
+
+ if (!referencedDisplaySet && otherDisplaySets.length == 1) {
+ console.log('No display set with FrameOfReferenceUID',
+ ReferencedFrameOfReferenceUID,
+ 'single series, assuming data error, defaulting to only series.')
+ return otherDisplaySets[0];
+ }
+
+ return referencedDisplaySet;
+};
diff --git a/extensions/dicom-microscopy/src/utils/loadSR.js b/extensions/dicom-microscopy/src/utils/loadSR.js
new file mode 100644
index 0000000000..0e66bab45e
--- /dev/null
+++ b/extensions/dicom-microscopy/src/utils/loadSR.js
@@ -0,0 +1,200 @@
+import dcmjs from 'dcmjs';
+
+import microscopyManager from '../tools/microscopyManager';
+import DCM_CODE_VALUES from './dcmCodeValues';
+import toArray from './toArray';
+
+const MeasurementReport =
+ dcmjs.adapters.DICOMMicroscopyViewer.MeasurementReport;
+
+// Define as async so that it returns a promise, expected by the ViewportGrid
+export default async function loadSR(
+ microscopySRDisplaySet,
+ referencedDisplaySet
+) {
+ console.debug(
+ 'Microscopy - loadSR',
+ microscopySRDisplaySet.StudyInstanceUID,
+ microscopySRDisplaySet.SeriesInstanceUID
+ );
+ const naturalizedDataset = microscopySRDisplaySet.metadata;
+
+ const { StudyInstanceUID, FrameOfReferenceUID } = referencedDisplaySet;
+
+ const managedViewers = microscopyManager.getManagedViewersForStudy(
+ StudyInstanceUID
+ );
+
+ if (!managedViewers || !managedViewers.length) {
+ return;
+ }
+
+ microscopySRDisplaySet.isLoaded = true;
+
+ const { rois, labels } = await _getROIsFromToolState(
+ naturalizedDataset,
+ FrameOfReferenceUID
+ );
+
+ const managedViewer = managedViewers[0];
+
+ for (let i = 0; i < rois.length; i++) {
+ managedViewer.addRoiGraphicWithLabel(rois[i], labels[i]);
+ }
+}
+
+async function _getROIsFromToolState(naturalizedDataset, FrameOfReferenceUID) {
+ const toolState = MeasurementReport.generateToolState(naturalizedDataset);
+ const tools = Object.getOwnPropertyNames(toolState);
+
+ const DICOMMicroscopyViewer = await import(
+ /* webpackChunkName: "dicom-microscopy-viewer" */ 'dicom-microscopy-viewer'
+ );
+
+ const measurementGroupContentItems = _getMeasurementGroups(
+ naturalizedDataset
+ );
+
+ const rois = [];
+ const labels = [];
+
+ tools.forEach(t => {
+ const toolSpecificToolState = toolState[t];
+ let scoord3d;
+
+ const capsToolType = t.toUpperCase();
+
+ const measurementGroupContentItemsForTool = measurementGroupContentItems.filter(
+ mg => {
+ const imageRegionContentItem = toArray(mg.ContentSequence).find(
+ ci =>
+ ci.ConceptNameCodeSequence.CodeValue ===
+ DCM_CODE_VALUES.IMAGE_REGION
+ );
+
+ return imageRegionContentItem.GraphicType === capsToolType;
+ }
+ );
+
+ toolSpecificToolState.forEach((coordinates, index) => {
+ const properties = {};
+
+ const options = {
+ coordinates,
+ frameOfReferenceUID: FrameOfReferenceUID,
+ };
+
+ if (t === 'Polygon') {
+ scoord3d = new DICOMMicroscopyViewer.scoord3d.Polygon(options);
+ } else if (t === 'Polyline') {
+ scoord3d = new DICOMMicroscopyViewer.scoord3d.Polyline(options);
+ } else if (t === 'Point') {
+ scoord3d = new DICOMMicroscopyViewer.scoord3d.Point(options);
+ } else if (t === 'Ellipse') {
+ scoord3d = new DICOMMicroscopyViewer.scoord3d.Ellipse(options);
+ } else {
+ throw new Error('Unsupported tool type');
+ }
+
+ const measurementGroup = measurementGroupContentItemsForTool[index];
+ const findingGroup = toArray(measurementGroup.ContentSequence).find(
+ ci => ci.ConceptNameCodeSequence.CodeValue === DCM_CODE_VALUES.FINDING
+ );
+
+ const trackingGroup = toArray(measurementGroup.ContentSequence).find(
+ ci =>
+ ci.ConceptNameCodeSequence.CodeValue ===
+ DCM_CODE_VALUES.TRACKING_UNIQUE_IDENTIFIER
+ );
+
+ /**
+ * Extract presentation state from tracking identifier.
+ * Currently is stored in SR but should be stored in its tags.
+ */
+ if (trackingGroup) {
+ const regExp = /\(([^)]+)\)/;
+ const matches = regExp.exec(trackingGroup.TextValue);
+ if (matches && matches[1]) {
+ properties.presentationState = JSON.parse(matches[1]);
+ properties.marker = properties.presentationState.marker;
+ }
+ }
+
+ let measurements = toArray(measurementGroup.ContentSequence).filter(ci =>
+ [
+ DCM_CODE_VALUES.LENGTH,
+ DCM_CODE_VALUES.AREA,
+ DCM_CODE_VALUES.SHORT_AXIS,
+ DCM_CODE_VALUES.LONG_AXIS,
+ DCM_CODE_VALUES.ELLIPSE_AREA,
+ ].includes(ci.ConceptNameCodeSequence.CodeValue)
+ );
+
+ let evaluations = toArray(measurementGroup.ContentSequence).filter(ci =>
+ [DCM_CODE_VALUES.TRACKING_UNIQUE_IDENTIFIER].includes(
+ ci.ConceptNameCodeSequence.CodeValue
+ )
+ );
+
+ /**
+ * TODO: Resolve bug in DCMJS.
+ * ConceptNameCodeSequence should be a sequence with only one item.
+ */
+ evaluations = evaluations.map(evaluation => {
+ const e = { ...evaluation };
+ e.ConceptNameCodeSequence = toArray(e.ConceptNameCodeSequence);
+ return e;
+ });
+
+ /**
+ * TODO: Resolve bug in DCMJS.
+ * ConceptNameCodeSequence should be a sequence with only one item.
+ */
+ measurements = measurements.map(measurement => {
+ const m = { ...measurement };
+ m.ConceptNameCodeSequence = toArray(m.ConceptNameCodeSequence);
+ return m;
+ });
+
+ if (measurements && measurements.length) {
+ properties.measurements = measurements;
+ console.debug('[SR] retrieving measurements...', measurements);
+ }
+
+ if (evaluations && evaluations.length) {
+ properties.evaluations = evaluations;
+ console.debug('[SR] retrieving evaluations...', evaluations);
+ }
+
+ const roi = new DICOMMicroscopyViewer.roi.ROI({ scoord3d, properties });
+ rois.push(roi);
+
+ if (findingGroup) {
+ labels.push(findingGroup.ConceptCodeSequence.CodeValue);
+ } else {
+ labels.push('');
+ }
+ });
+ });
+
+ return { rois, labels };
+}
+
+function _getMeasurementGroups(naturalizedDataset) {
+ const { ContentSequence } = naturalizedDataset;
+
+ const imagingMeasurementsContentItem = ContentSequence.find(
+ ci =>
+ ci.ConceptNameCodeSequence.CodeValue ===
+ DCM_CODE_VALUES.IMAGING_MEASUREMENTS
+ );
+
+ const measurementGroupContentItems = toArray(
+ imagingMeasurementsContentItem.ContentSequence
+ ).filter(
+ ci =>
+ ci.ConceptNameCodeSequence.CodeValue === DCM_CODE_VALUES.MEASUREMENT_GROUP
+ );
+
+ return measurementGroupContentItems;
+}
diff --git a/extensions/dicom-microscopy/src/utils/styles.js b/extensions/dicom-microscopy/src/utils/styles.js
new file mode 100644
index 0000000000..348455087c
--- /dev/null
+++ b/extensions/dicom-microscopy/src/utils/styles.js
@@ -0,0 +1,48 @@
+const defaultFill = {
+ color: 'rgba(255,255,255,0.4)',
+};
+
+const emptyFill = {
+ color: 'rgba(255,255,255,0.0)',
+};
+
+const defaultStroke = {
+ color: 'black',
+ width: 3,
+};
+
+const activeStroke = {
+ color: 'white',
+ width: 3,
+};
+
+const defaultStyle = {
+ image: {
+ circle: {
+ fill: defaultFill,
+ stroke: activeStroke,
+ radius: 5,
+ },
+ },
+ fill: defaultFill,
+ stroke: activeStroke,
+};
+
+const emptyStyle = {
+ image: {
+ circle: {
+ fill: emptyFill,
+ stroke: defaultStroke,
+ radius: 5,
+ },
+ },
+ fill: emptyFill,
+ stroke: defaultStroke,
+};
+
+const styles = {
+ active: defaultStyle,
+ default: emptyStyle,
+};
+
+export default styles;
diff --git a/extensions/dicom-microscopy/src/utils/toArray.js b/extensions/dicom-microscopy/src/utils/toArray.js
new file mode 100644
index 0000000000..2800085fa7
--- /dev/null
+++ b/extensions/dicom-microscopy/src/utils/toArray.js
@@ -0,0 +1,3 @@
+export default function toArray(item) {
+ return Array.isArray(item) ? item : [item];
+}
diff --git a/extensions/dicom-microscopy/tsconfig.json b/extensions/dicom-microscopy/tsconfig.json
new file mode 100644
index 0000000000..e8977ec34b
--- /dev/null
+++ b/extensions/dicom-microscopy/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "compilerOptions": {
+ "outDir": "./dist/",
+ "noImplicitAny": true,
+ "module": "es6",
+ "target": "es5",
+ "jsx": "react",
+ "allowJs": true,
+ "moduleResolution": "node"
+ }
+}
From ca66b82a319c739df5029219b7afe52ee5842710 Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Fri, 17 Mar 2023 15:00:32 -0400
Subject: [PATCH 04/51] [feat] ported microscopy mode from private repo
---
modes/microscopy/README.md | 2 +-
modes/microscopy/package.json | 5 +-
.../public/assets/icons/tool-circle.svg | 3 +
.../assets/icons/tool-freehand-polygon.svg | 37 ++++
.../public/assets/icons/tool-freehand.svg | 34 ++++
.../public/assets/icons/tool-polygon.svg | 3 +
modes/microscopy/src/index.tsx | 167 ++++++++++------
modes/microscopy/src/toolbarButtons.js | 185 ++++++++++++++++++
8 files changed, 377 insertions(+), 59 deletions(-)
create mode 100644 modes/microscopy/public/assets/icons/tool-circle.svg
create mode 100644 modes/microscopy/public/assets/icons/tool-freehand-polygon.svg
create mode 100644 modes/microscopy/public/assets/icons/tool-freehand.svg
create mode 100644 modes/microscopy/public/assets/icons/tool-polygon.svg
create mode 100644 modes/microscopy/src/toolbarButtons.js
diff --git a/modes/microscopy/README.md b/modes/microscopy/README.md
index 679ea76216..1ad98b7a7a 100644
--- a/modes/microscopy/README.md
+++ b/modes/microscopy/README.md
@@ -4,7 +4,7 @@
OHIF mode for DICOM microscopy
## Author
-OHIF, md-prog
+Bill Wallace, md-prog, Radical Imaging
## License
MIT
diff --git a/modes/microscopy/package.json b/modes/microscopy/package.json
index d7016be014..dfc2f20c5c 100644
--- a/modes/microscopy/package.json
+++ b/modes/microscopy/package.json
@@ -2,7 +2,7 @@
"name": "@ohif/mode-microscopy",
"version": "3.0.0",
"description": "OHIF mode for DICOM microscopy",
- "author": "OHIF, md-prog",
+ "author": "Bill Wallace, md-prog, Radical Imaging",
"license": "MIT",
"main": "dist/index.umd.js",
"files": [
@@ -30,7 +30,8 @@
"test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests"
},
"peerDependencies": {
- "@ohif/core": "^3.0.0"
+ "@ohif/core": "^3.0.0",
+ "@ohif/extension-dicom-microscopy": "^3.0.0"
},
"dependencies": {
"@babel/runtime": "^7.20.13"
diff --git a/modes/microscopy/public/assets/icons/tool-circle.svg b/modes/microscopy/public/assets/icons/tool-circle.svg
new file mode 100644
index 0000000000..e94375564e
--- /dev/null
+++ b/modes/microscopy/public/assets/icons/tool-circle.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/modes/microscopy/public/assets/icons/tool-freehand-polygon.svg b/modes/microscopy/public/assets/icons/tool-freehand-polygon.svg
new file mode 100644
index 0000000000..bfc1b0dcd5
--- /dev/null
+++ b/modes/microscopy/public/assets/icons/tool-freehand-polygon.svg
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modes/microscopy/public/assets/icons/tool-freehand.svg b/modes/microscopy/public/assets/icons/tool-freehand.svg
new file mode 100644
index 0000000000..2159f91505
--- /dev/null
+++ b/modes/microscopy/public/assets/icons/tool-freehand.svg
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modes/microscopy/public/assets/icons/tool-polygon.svg b/modes/microscopy/public/assets/icons/tool-polygon.svg
new file mode 100644
index 0000000000..ff2f9e0299
--- /dev/null
+++ b/modes/microscopy/public/assets/icons/tool-polygon.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/modes/microscopy/src/index.tsx b/modes/microscopy/src/index.tsx
index a9c28dd939..7fead82204 100644
--- a/modes/microscopy/src/index.tsx
+++ b/modes/microscopy/src/index.tsx
@@ -1,84 +1,130 @@
+import { hotkeys } from '@ohif/core';
+import { addIcon } from '@ohif/ui';
+import ConfigPoint from "config-point";
+
import { id } from './id';
+import toolbarButtons from './toolbarButtons';
+
+import toolCircle from '../public/assets/icons/tool-circle.svg';
+import toolFreehand from '../public/assets/icons/tool-freehand.svg';
+import toolFreehandPolygon from '../public/assets/icons/tool-freehand-polygon.svg';
+import toolPolygon from '../public/assets/icons/tool-polygon.svg';
const ohif = {
layout: '@ohif/extension-default.layoutTemplateModule.viewerLayout',
sopClassHandler: '@ohif/extension-default.sopClassHandlerModule.stack',
- hangingProtocol: '@ohif/extension-default.hangingProtocolModule.default',
+ hangingProtocols: '@ohif/extension-default.hangingProtocolModule.default',
leftPanel: '@ohif/extension-default.panelModule.seriesList',
rightPanel: '@ohif/extension-default.panelModule.measure',
};
-const cornerstone = {
+export const cornerstone = {
viewport: '@ohif/extension-cornerstone.viewportModule.cornerstone',
};
-/**
- * Just two dependencies to be able to render a viewport with panels in order
- * to make sure that the mode is working.
- */
+const dicomsr = {
+ sopClassHandler:
+ '@ohif/extension-cornerstone-dicom-sr.sopClassHandlerModule.dicom-sr',
+ viewport: '@ohif/extension-cornerstone-dicom-sr.viewportModule.dicom-sr',
+};
+
+const dicomvideo = {
+ sopClassHandler:
+ '@ohif/extension-dicom-video.sopClassHandlerModule.dicom-video',
+ viewport: '@ohif/extension-dicom-video.viewportModule.dicom-video',
+};
+
+const dicompdf = {
+ sopClassHandler: '@ohif/extension-dicom-pdf.sopClassHandlerModule.dicom-pdf',
+ viewport: '@ohif/extension-dicom-pdf.viewportModule.dicom-pdf',
+};
+
const extensionDependencies = {
+ // Can derive the versions at least process.env.from npm_package_version
'@ohif/extension-default': '^3.0.0',
'@ohif/extension-cornerstone': '^3.0.0',
+ '@ohif/extension-cornerstone-dicom-sr': '^3.0.0',
+ '@ohif/extension-dicom-pdf': '^3.0.1',
+ '@ohif/extension-dicom-video': '^3.0.1',
+ '@ohif/extension-dicom-microscopy': '^3.0.0',
};
-function modeFactory({ modeConfiguration }) {
+function modeFactory() {
return {
- /**
- * Mode ID, which should be unique among modes used by the viewer. This ID
- * is used to identify the mode in the viewer's state.
- */
+ // TODO: We're using this as a route segment
+ // We should not be.
id,
- routeName: 'template',
- /**
- * Mode name, which is displayed in the viewer's UI in the workList, for the
- * user to select the mode.
- */
- displayName: 'Template Mode',
- /**
- * Runs when the Mode Route is mounted to the DOM. Usually used to initialize
- * Services and other resources.
- */
- onModeEnter: ({ servicesManager, extensionManager }) => {},
+ routeName: 'microscopy',
+ displayName: 'Microscopy',
+
/**
- * Runs when the Mode Route is unmounted from the DOM. Usually used to clean
- * up resources and states
+ * Lifecycle hooks
*/
- onModeExit: () => {},
- /** */
+ onModeEnter: ({ servicesManager, extensionManager, commandsManager }) => {
+ const { ToolBarService } = servicesManager.services;
+
+ addIcon('tool-point', toolCircle)
+ addIcon('tool-circle', toolCircle)
+ addIcon('tool-freehand-line', toolFreehand)
+ addIcon('tool-freehand-polygon', toolFreehandPolygon)
+ addIcon('tool-polygon', toolPolygon)
+
+ ToolBarService.init(extensionManager);
+ ToolBarService.addButtons(toolbarButtons);
+ ToolBarService.createButtonSection('primary', [
+ 'MeasurementTools',
+ 'dragPan',
+ ]);
+ },
+
+ onModeExit: ({ servicesManager }) => {
+ const {
+ MeasurementService,
+ ToolBarService,
+ } = servicesManager.services;
+
+ ToolBarService.reset();
+ },
+
validationTags: {
study: [],
series: [],
},
- /**
- * A boolean return value that indicates whether the mode is valid for the
- * modalities of the selected studies. For instance a PET/CT mode should be
- */
- isValidMode: ({ modalities }) => true,
- /**
- * Mode Routes are used to define the mode's behavior. A list of Mode Route
- * that includes the mode's path and the layout to be used. The layout will
- * include the components that are used in the layout. For instance, if the
- * default layoutTemplate is used (id: '@ohif/extension-default.layoutTemplateModule.viewerLayout')
- * it will include the leftPanels, rightPanels, and viewports. However, if
- * you define another layoutTemplate that includes a Footer for instance,
- * you should provide the Footer component here too. Note: We use Strings
- * to reference the component's ID as they are registered in the internal
- * ExtensionManager. The template for the string is:
- * `${extensionId}.{moduleType}.${componentId}`.
- */
+
+ isValidMode: ({ modalities }) => {
+ const modalities_list = modalities.split('\\');
+
+ // Slide Microscopy and ECG modality not supported by basic mode yet
+ return modalities_list.includes('SM');
+ },
+
routes: [
{
- path: 'template',
+ path: 'microscopy',
+ /*init: ({ servicesManager, extensionManager }) => {
+ //defaultViewerRouteInit
+ },*/
layoutTemplate: ({ location, servicesManager }) => {
return {
id: ohif.layout,
props: {
leftPanels: [ohif.leftPanel],
- rightPanels: [ohif.rightPanel],
+ rightPanels: ['@ohif/extension-dicom-microscopy.panelModule.measure'],
viewports: [
{
- namespace: cornerstone.viewport,
- displaySetsToDisplay: [ohif.sopClassHandler],
+ namespace: "@ohif/extension-dicom-microscopy.viewportModule.microscopy-dicom",
+ displaySetsToDisplay: [
+ "@ohif/extension-dicom-microscopy.sopClassHandlerModule.DicomMicroscopySopClassHandler",
+ "@ohif/extension-dicom-microscopy.sopClassHandlerModule.DicomMicroscopySRSopClassHandler",
+ ],
+ },
+ {
+ namespace: dicomvideo.viewport,
+ displaySetsToDisplay: [dicomvideo.sopClassHandler],
+ },
+ {
+ namespace: dicompdf.viewport,
+ displaySetsToDisplay: [dicompdf.sopClassHandler],
},
],
},
@@ -86,21 +132,30 @@ function modeFactory({ modeConfiguration }) {
},
},
],
- /** List of extensions that are used by the mode */
extensions: extensionDependencies,
- /** HangingProtocol used by the mode */
- // hangingProtocol: [''],
- /** SopClassHandlers used by the mode */
- sopClassHandlers: [ohif.sopClassHandler],
- /** hotkeys for mode */
- hotkeys: [''],
+ hangingProtocols: [ohif.hangingProtocols],
+ hangingProtocol: ['default'],
+
+ // Order is important in sop class handlers when two handlers both use
+ // the same sop class under different situations. In that case, the more
+ // general handler needs to come last. For this case, the dicomvideo must
+ // come first to remove video transfer syntax before ohif uses images
+ sopClassHandlers: [
+ "@ohif/extension-dicom-microscopy.sopClassHandlerModule.DicomMicroscopySopClassHandler",
+ "@ohif/extension-dicom-microscopy.sopClassHandlerModule.DicomMicroscopySRSopClassHandler",
+ dicomvideo.sopClassHandler,
+ dicompdf.sopClassHandler,
+ ],
+ hotkeys: [...hotkeys.defaults.hotkeyBindings],
};
}
-const mode = {
+const mode = ConfigPoint.createConfiguration("microscopyMode", {
id,
modeFactory,
extensionDependencies,
-};
+});
+
+console.log('microscopy-mode=', mode)
export default mode;
diff --git a/modes/microscopy/src/toolbarButtons.js b/modes/microscopy/src/toolbarButtons.js
new file mode 100644
index 0000000000..73fb430f94
--- /dev/null
+++ b/modes/microscopy/src/toolbarButtons.js
@@ -0,0 +1,185 @@
+// TODO: torn, can either bake this here; or have to create a whole new button type
+/**
+ *
+ * @param {*} type - 'tool' | 'action' | 'toggle'
+ * @param {*} id
+ * @param {*} icon
+ * @param {*} label
+ */
+function _createButton(type, id, icon, label, commands, tooltip) {
+ return {
+ id,
+ icon,
+ label,
+ type,
+ commands,
+ tooltip,
+ };
+}
+
+const _createActionButton = _createButton.bind(null, 'action');
+const _createToggleButton = _createButton.bind(null, 'toggle');
+const _createToolButton = _createButton.bind(null, 'tool');
+
+
+const toolbarButtons = [
+ // Measurement
+ {
+ id: 'MeasurementTools',
+ type: 'ohif.splitButton',
+ props: {
+ groupId: 'MeasurementTools',
+ isRadio: true, // ?
+ // Switch?
+ primary: _createToolButton(
+ 'line',
+ 'tool-length',
+ 'Line',
+ [
+ {
+ commandName: 'setToolActive',
+ commandOptions: {
+ toolName: 'line',
+ },
+ context: 'MICROSCOPY',
+ },
+ ],
+ 'Line'
+ ),
+ secondary: {
+ icon: 'chevron-down',
+ label: '',
+ isActive: true,
+ tooltip: 'More Measure Tools',
+ },
+ items: [
+ _createToolButton(
+ 'line',
+ 'tool-length',
+ 'Line',
+ [
+ {
+ commandName: 'setToolActive',
+ commandOptions: {
+ toolName: 'line',
+ },
+ context: 'MICROSCOPY',
+ },
+ ],
+ 'Line Tool'
+ ),
+ _createToolButton(
+ 'point',
+ 'tool-point',
+ 'Point',
+ [
+ {
+ commandName: 'setToolActive',
+ commandOptions: {
+ toolName: 'point',
+ },
+ context: 'MICROSCOPY',
+ },
+ ],
+ 'Point Tool'
+ ),
+ _createToolButton(
+ 'polygon',
+ 'tool-polygon',
+ 'Polygon',
+ [
+ {
+ commandName: 'setToolActive',
+ commandOptions: {
+ toolName: 'polygon',
+ },
+ context: 'MICROSCOPY',
+ },
+ ],
+ 'Polygon Tool'
+ ),
+ _createToolButton(
+ 'circle',
+ 'tool-circle',
+ 'Circle',
+ [
+ {
+ commandName: 'setToolActive',
+ commandOptions: {
+ toolName: 'circle',
+ },
+ context: 'MICROSCOPY',
+ },
+ ],
+ 'Circle Tool'
+ ),
+ _createToolButton(
+ 'box',
+ 'tool-rectangle',
+ 'Box',
+ [
+ {
+ commandName: 'setToolActive',
+ commandOptions: {
+ toolName: 'box',
+ },
+ context: 'MICROSCOPY',
+ },
+ ],
+ 'Box Tool'
+ ),
+ _createToolButton(
+ 'freehandpolygon',
+ 'tool-freehand-polygon',
+ 'Freehand Polygon',
+ [
+ {
+ commandName: 'setToolActive',
+ commandOptions: {
+ toolName: 'freehandpolygon',
+ },
+ context: 'MICROSCOPY',
+ },
+ ],
+ 'Freehand Polygon Tool'
+ ),
+ _createToolButton(
+ 'freehandline',
+ 'tool-freehand-line',
+ 'Freehand Line',
+ [
+ {
+ commandName: 'setToolActive',
+ commandOptions: {
+ toolName: 'freehandline',
+ },
+ context: 'MICROSCOPY',
+ },
+ ],
+ 'Freehand Line Tool'
+ ),
+ ],
+ },
+ },
+ // Pan...
+ {
+ id: 'dragPan',
+ type: 'ohif.radioGroup',
+ props: {
+ type: 'tool',
+ icon: 'tool-move',
+ label: 'Pan',
+ commands: [
+ {
+ commandName: 'setToolActive',
+ commandOptions: {
+ toolName: 'dragPan',
+ },
+ context: 'MICROSCOPY',
+ },
+ ],
+ },
+ },
+];
+
+export default toolbarButtons;
From c3338eabfce23737ad6eed389ba71bd377f8c535 Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Fri, 17 Mar 2023 15:00:51 -0400
Subject: [PATCH 05/51] added new definitions to the package.json
---
platform/viewer/package.json | 2 ++
1 file changed, 2 insertions(+)
diff --git a/platform/viewer/package.json b/platform/viewer/package.json
index b7fe69b1ee..63f594077d 100644
--- a/platform/viewer/package.json
+++ b/platform/viewer/package.json
@@ -54,11 +54,13 @@
"@ohif/extension-default": "^3.0.0",
"@ohif/extension-dicom-pdf": "^3.0.1",
"@ohif/extension-dicom-video": "^3.0.1",
+ "@ohif/extension-dicom-microscopy": "^3.0.0",
"@ohif/extension-test": "0.0.1",
"@ohif/i18n": "^1.0.0",
"@ohif/mode-basic-dev-mode": "^3.0.0",
"@ohif/mode-test": "^0.0.1",
"@ohif/mode-longitudinal": "^3.0.0",
+ "@ohif/mode-microscopy": "^3.0.0",
"@ohif/ui": "^2.0.0",
"@types/react": "^17.0.38",
"classnames": "^2.3.2",
From 5c7df1e068740ad1ad70122a54104c9df6e47252 Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Fri, 17 Mar 2023 16:58:31 -0400
Subject: [PATCH 06/51] webpack configuration update for microscopy extension
---
.webpack/rules/transpileJavaScript.js | 2 +-
.webpack/webpack.base.js | 10 ++++++++++
extensions/dicom-microscopy/.webpack/webpack.prod.js | 2 +-
extensions/dicom-microscopy/package.json | 11 ++++++++---
modes/microscopy/.webpack/webpack.prod.js | 2 +-
modes/microscopy/package.json | 2 +-
6 files changed, 22 insertions(+), 7 deletions(-)
diff --git a/.webpack/rules/transpileJavaScript.js b/.webpack/rules/transpileJavaScript.js
index d576e58393..ca7f00c7a5 100644
--- a/.webpack/rules/transpileJavaScript.js
+++ b/.webpack/rules/transpileJavaScript.js
@@ -25,7 +25,7 @@ function transpileJavaScript(mode) {
// These are packages that are not transpiled to our lowest supported
// JS version (currently ES5). Most of these leverage ES6+ features,
// that we need to transpile to a different syntax.
- exclude,
+ exclude: [/(codecs)/, /(dicomicc)/, exclude],
loader: 'babel-loader',
options: {
// Find babel.config.js in monorepo root
diff --git a/.webpack/webpack.base.js b/.webpack/webpack.base.js
index 700e9e1b34..306fee5e76 100644
--- a/.webpack/webpack.base.js
+++ b/.webpack/webpack.base.js
@@ -78,6 +78,7 @@ module.exports = (env, argv, { SRC_DIR, DIST_DIR }) => {
},
},
module: {
+ noParse: [/(codec)/, /(dicomicc)/],
rules: [
transpileJavaScriptRule(mode),
loadWebWorkersRule,
@@ -89,6 +90,10 @@ module.exports = (env, argv, { SRC_DIR, DIST_DIR }) => {
},
},
cssToJavaScript,
+ {
+ test: /\.wasm/,
+ type: 'asset/resource',
+ },
], //.concat(vtkRules),
},
resolve: {
@@ -148,6 +153,11 @@ module.exports = (env, argv, { SRC_DIR, DIST_DIR }) => {
// Uncomment to generate bundle analyzer
// new BundleAnalyzerPlugin(),
],
+ // enable experimental features, like webAssembly
+ experiments: {
+ asyncWebAssembly: true,
+ syncWebAssembly: true,
+ },
};
if (isProdBuild) {
diff --git a/extensions/dicom-microscopy/.webpack/webpack.prod.js b/extensions/dicom-microscopy/.webpack/webpack.prod.js
index e4e19f234a..070a723e38 100644
--- a/extensions/dicom-microscopy/.webpack/webpack.prod.js
+++ b/extensions/dicom-microscopy/.webpack/webpack.prod.js
@@ -3,7 +3,7 @@ const pkg = require('../package.json');
const outputFile = 'index.umd.js';
const rootDir = path.resolve(__dirname, '../');
-const outputFolder = path.join(__dirname, `../dist/`);
+const outputFolder = path.join(__dirname, `../dist/umd/${pkg.name}/`);
// Todo: add ESM build for the extension in addition to umd build
diff --git a/extensions/dicom-microscopy/package.json b/extensions/dicom-microscopy/package.json
index 648d7dcc4a..c06a70065f 100644
--- a/extensions/dicom-microscopy/package.json
+++ b/extensions/dicom-microscopy/package.json
@@ -4,7 +4,7 @@
"description": "OHIF extension for DICOM microscopy",
"author": "Bill Wallace, md-prog, Radical Imaging",
"license": "MIT",
- "main": "dist/index.umd.js",
+ "main": "dist/umd/@ohif/extension-dicom-microscopy/index.umd.js",
"files": [
"dist/**",
"public/**",
@@ -29,9 +29,9 @@
},
"peerDependencies": {
"@ohif/core": "^3.0.0",
- "@ohif/extension-cornerstone": "^3.0.0",
"@ohif/extension-default": "^3.0.0",
"@ohif/i18n": "^1.0.0",
+ "@ohif/ui": "^2.0.0",
"prop-types": "^15.6.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
@@ -43,7 +43,12 @@
},
"dependencies": {
"@babel/runtime": "^7.20.13",
- "dicom-microscopy-viewer": "^0.44.0"
+ "@cornerstonejs/codec-charls": "^0.1.1",
+ "@cornerstonejs/codec-libjpeg-turbo-8bit": "^0.0.7",
+ "@cornerstonejs/codec-openjpeg": "^0.1.1",
+ "colormap": "^2.3",
+ "dicom-microscopy-viewer": "^0.44.0",
+ "dicomicc": "^0.1"
},
"devDependencies": {
"@babel/core": "^7.17.8",
diff --git a/modes/microscopy/.webpack/webpack.prod.js b/modes/microscopy/.webpack/webpack.prod.js
index c872977954..163392a699 100644
--- a/modes/microscopy/.webpack/webpack.prod.js
+++ b/modes/microscopy/.webpack/webpack.prod.js
@@ -3,7 +3,7 @@ const pkg = require('../package.json');
const outputFile = 'index.umd.js';
const rootDir = path.resolve(__dirname, '../');
-const outputFolder = path.join(__dirname, `../dist/`);
+const outputFolder = path.join(__dirname, `../dist/umd/${pkg.name}/`);
// Todo: add ESM build for the mode in addition to umd build
const config = {
diff --git a/modes/microscopy/package.json b/modes/microscopy/package.json
index dfc2f20c5c..f1593ae985 100644
--- a/modes/microscopy/package.json
+++ b/modes/microscopy/package.json
@@ -4,7 +4,7 @@
"description": "OHIF mode for DICOM microscopy",
"author": "Bill Wallace, md-prog, Radical Imaging",
"license": "MIT",
- "main": "dist/index.umd.js",
+ "main": "dist/umd/@ohif/mode-microscopy/index.umd.js",
"files": [
"dist/**",
"public/**",
From 55df9d22144e5c8f77da92cbd2068dbf29bd4469 Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Fri, 17 Mar 2023 16:58:57 -0400
Subject: [PATCH 07/51] register new icons for microscopy tools
---
platform/ui/src/assets/icons/tool-circle.svg | 3 ++
.../assets/icons/tool-freehand-polygon.svg | 37 +++++++++++++++++++
.../ui/src/assets/icons/tool-freehand.svg | 34 +++++++++++++++++
platform/ui/src/assets/icons/tool-polygon.svg | 3 ++
platform/ui/src/components/Icon/getIcon.js | 9 +++++
5 files changed, 86 insertions(+)
create mode 100644 platform/ui/src/assets/icons/tool-circle.svg
create mode 100644 platform/ui/src/assets/icons/tool-freehand-polygon.svg
create mode 100644 platform/ui/src/assets/icons/tool-freehand.svg
create mode 100644 platform/ui/src/assets/icons/tool-polygon.svg
diff --git a/platform/ui/src/assets/icons/tool-circle.svg b/platform/ui/src/assets/icons/tool-circle.svg
new file mode 100644
index 0000000000..e94375564e
--- /dev/null
+++ b/platform/ui/src/assets/icons/tool-circle.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/platform/ui/src/assets/icons/tool-freehand-polygon.svg b/platform/ui/src/assets/icons/tool-freehand-polygon.svg
new file mode 100644
index 0000000000..bfc1b0dcd5
--- /dev/null
+++ b/platform/ui/src/assets/icons/tool-freehand-polygon.svg
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/platform/ui/src/assets/icons/tool-freehand.svg b/platform/ui/src/assets/icons/tool-freehand.svg
new file mode 100644
index 0000000000..2159f91505
--- /dev/null
+++ b/platform/ui/src/assets/icons/tool-freehand.svg
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/platform/ui/src/assets/icons/tool-polygon.svg b/platform/ui/src/assets/icons/tool-polygon.svg
new file mode 100644
index 0000000000..ff2f9e0299
--- /dev/null
+++ b/platform/ui/src/assets/icons/tool-polygon.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/platform/ui/src/components/Icon/getIcon.js b/platform/ui/src/components/Icon/getIcon.js
index b4ebdf5629..e3e528bb6f 100644
--- a/platform/ui/src/components/Icon/getIcon.js
+++ b/platform/ui/src/components/Icon/getIcon.js
@@ -95,6 +95,10 @@ import toolRectangle from './../../assets/icons/tool-rectangle.svg';
import toolFusionColor from './../../assets/icons/tool-fusion-color.svg';
import toolCreateThreshold from './../../assets/icons/tool-create-threshold.svg';
import toolCalibration from './../../assets/icons/tool-calibration.svg';
+import toolCircle from './../../assets/icons/tool-circle.svg';
+import toolFreehand from './../../assets/icons/tool-freehand.svg';
+import toolFreehandPolygon from './../../assets/icons/tool-freehand-polygon.svg';
+import toolPolygon from './../../assets/icons/tool-polygon.svg';
import editPatient from './../../assets/icons/edit-patient.svg';
import panelGroupMore from './../../assets/icons/panel-group-more.svg';
import panelGroupOpenClose from './../../assets/icons/panel-group-open-close.svg';
@@ -212,6 +216,11 @@ const ICONS = {
'tool-fusion-color': toolFusionColor,
'tool-create-threshold': toolCreateThreshold,
'tool-calibration': toolCalibration,
+ 'tool-point': toolCircle,
+ 'tool-circle': toolCircle,
+ 'tool-freehand-line': toolFreehand,
+ 'tool-freehand-polygon': toolFreehandPolygon,
+ 'tool-polygon': toolPolygon,
'edit-patient': editPatient,
'icon-mpr': iconMPR,
'icon-next-inactive': iconNextInactive,
From dc3d3270ddf203d637ac33639cefdfe8c9f58454 Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Fri, 17 Mar 2023 17:23:08 -0400
Subject: [PATCH 08/51] fixes to the microscopy extension and mode
---
.../dicom-microscopy/src/commandsModule.ts | 346 ------------------
.../MicroscopyPanel/MicroscopyPanel.tsx | 22 +-
.../dicom-microscopy/src/getCommandsModule.ts | 169 +++++++++
.../dicom-microscopy/src/getPanelModule.tsx | 5 +
extensions/dicom-microscopy/src/index.tsx | 172 ++++-----
modes/microscopy/src/index.tsx | 38 +-
6 files changed, 267 insertions(+), 485 deletions(-)
delete mode 100644 extensions/dicom-microscopy/src/commandsModule.ts
create mode 100644 extensions/dicom-microscopy/src/getCommandsModule.ts
diff --git a/extensions/dicom-microscopy/src/commandsModule.ts b/extensions/dicom-microscopy/src/commandsModule.ts
deleted file mode 100644
index d513bc3ac8..0000000000
--- a/extensions/dicom-microscopy/src/commandsModule.ts
+++ /dev/null
@@ -1,346 +0,0 @@
-import microscopyManager from './tools/microscopyManager';
-import { ContextMenu } from '@ohif/ui';
-
-const commandsModule = ({
- servicesManager,
- commandsManager,
- extensionManager,
-}) => {
- const {
- viewportGridService,
- toolbarService,
- uiDialogService,
- cornerstoneViewportService,
- customizationService,
- measurementService,
- } = servicesManager.services;
-
- const contextMenuController = new ContextMenu.Controller(
- servicesManager,
- commandsManager
- );
-
- function _showViewerContextMenu(viewerElement, options) {
- let defaultPointsPosition = [];
- if (options.nearbyToolData) {
- defaultPointsPosition = commandsManager.runCommand(
- 'getToolDataActiveCanvasPoints',
- { toolData: options.nearbyToolData }
- );
- }
-
- contextMenuController.showContextMenu(
- options,
- viewerElement,
- defaultPointsPosition
- );
- }
-
- const actions = {
- /** Show the specified context menu */
- showViewerContextMenu: (providedOptions) => {
- // const viewerElement = _getActiveEnabledElement();
- // const options = { ...providedOptions };
- // const { event: evt } = options;
- // const { useSelectedAnnotation, nearbyToolData, menuName } = options;
- // if (menuName) {
- // Object.assign(
- // options,
- // customizationService.getModeCustomization(
- // menuName,
- // defaultContextMenu
- // )
- // );
- // }
- // if (useSelectedAnnotation && !nearbyToolData) {
- // const firstAnnotationSelected =
- // getFirstAnnotationSelected(viewerElement);
- // // filter by allowed selected tools from config property (if there is any)
- // if (
- // !options.allowedSelectedTools ||
- // options.allowedSelectedTools.includes(
- // firstAnnotationSelected?.metadata?.toolName
- // )
- // ) {
- // options.nearbyToolData = firstAnnotationSelected;
- // } else {
- // return;
- // }
- // }
- // // TODO - make the checkProps richer by including the study metadata and display set.
- // options.checkProps = {
- // toolName: options.nearbyToolData?.metadata?.toolName,
- // value: options.nearbyToolData,
- // uid: options.nearbyToolData?.annotationUID,
- // nearbyToolData: options.nearbyToolData,
- // };
- // _showViewerContextMenu(viewerElement, options);
- },
-
- /** Close any viewer context menus currently displayed */
- closeViewerContextMenu: () => {
- contextMenuController.closeViewerContextMenu();
- },
-
- getNearbyToolData({ nearbyToolData, element, canvasCoordinates }) {
- // return (
- // nearbyToolData ??
- // cstUtils.getAnnotationNearPoint(element, canvasCoordinates)
- // );
- },
-
- // Measurement tool commands:
- deleteMeasurement: ({ uid }) => {
- // if (uid) {
- // // measurementServiceSource.remove(uid);
- // }
- },
-
- setLabel: ({ uid }) => {
- // const measurement = MeasurementService.getMeasurement(uid);
- // callInputDialog(
- // uiDialogService,
- // measurement,
- // (label, actionId) => {
- // if (actionId === 'cancel') {
- // return;
- // }
- // const updatedMeasurement = Object.assign({}, measurement, {
- // label,
- // });
- // MeasurementService.update(
- // updatedMeasurement.uid,
- // updatedMeasurement,
- // true
- // );
- // },
- // false
- // );
- },
-
- updateMeasurement: (props) => {
- // const { code, uid, measurementKey = 'finding' } = props;
- // const measurement = MeasurementService.getMeasurement(uid);
- // const updatedMeasurement = {
- // ...measurement,
- // [measurementKey]: code,
- // label: code.text,
- // };
- // MeasurementService.update(
- // updatedMeasurement.uid,
- // updatedMeasurement,
- // true
- // );
- },
-
- // Retrieve value commands
- setViewportActive: ({ viewportId }) => {
- // const viewportInfo =
- // CornerstoneViewportService.getViewportInfo(viewportId);
- // if (!viewportInfo) {
- // console.warn('No viewport found for viewportId:', viewportId);
- // return;
- // }
- // const viewportIndex = viewportInfo.getViewportIndex();
- // viewportGridService.setActiveViewportIndex(viewportIndex);
- },
- arrowTextCallback: ({ callback, data }) => {
- // callInputDialog(uiDialogService, data, callback);
- },
- setToolActive: ({ toolName, toolGroupId = 'MICROSCOPY' }) => {
- if (
- [
- 'line',
- 'box',
- 'circle',
- 'point',
- 'polygon',
- 'freehandpolygon',
- 'freehandline',
- ].indexOf(toolName) >= 0
- ) {
- // TODO: read from configuration
- const styleOptions = {
- stroke: {
- color: [0, 255, 0, 1],
- width: 1.2,
- },
- };
- let options = {
- geometryType: toolName,
- vertexEnabled: true,
- styleOptions,
- } as any;
- if ('line' === toolName) {
- options.minPoints = 2;
- options.maxPoints = 2;
- }
- else if('point' === toolName) {
- delete options.styleOptions;
- delete options.vertexEnabled;
- }
-
- microscopyManager.activateInteractions([['draw', options]]);
- } else if (toolName == 'dragPan') {
- microscopyManager.activateInteractions([['dragPan']]);
- }
- },
- rotateViewport: ({ rotation }) => {},
- flipViewportHorizontal: () => {},
- flipViewportVertical: () => {},
- invertViewport: ({ element }) => {},
- resetViewport: () => {},
- scaleViewport: ({ direction }) => {},
- scroll: ({ direction }) => {},
- incrementActiveViewport: () => {
- const { activeViewportIndex, viewports } = viewportGridService.getState();
- const nextViewportIndex = (activeViewportIndex + 1) % viewports.length;
- viewportGridService.setActiveViewportIndex(nextViewportIndex);
- },
- decrementActiveViewport: () => {
- const { activeViewportIndex, viewports } = viewportGridService.getState();
- const nextViewportIndex =
- (activeViewportIndex - 1 + viewports.length) % viewports.length;
- viewportGridService.setActiveViewportIndex(nextViewportIndex);
- },
-
- toggleOverlays: () => {
- // overlay
- const overlays = document.getElementsByClassName('microscopy-viewport-overlay');
- let onoff = false; // true if this will toggle on
- for (let i = 0; i < overlays.length; i++) {
- if (i === 0) onoff = overlays.item(0).classList.contains('hidden');
- overlays.item(i).classList.toggle('hidden');
- }
-
- // overview
- const { activeViewportIndex, viewports } = viewportGridService.getState();
- microscopyManager.toggleOverviewMap(activeViewportIndex);
- },
- toggleAnnotations: () => {
- microscopyManager.toggleROIsVisibility();
- }
- };
-
- const definitions = {
- showViewerContextMenu: {
- commandFn: actions.showViewerContextMenu,
- storeContexts: [] as any[],
- options: {},
- },
- closeViewerContextMenu: {
- commandFn: actions.closeViewerContextMenu,
- storeContexts: [] as any[],
- options: {},
- },
- getNearbyToolData: {
- commandFn: actions.getNearbyToolData,
- storeContexts: [] as any[],
- options: {},
- },
-
- // These should probably all be standard implementations
- // deleteMeasurement: {
- // commandFn: actions.deleteMeasurement,
- // storeContexts: [] as any[],
- // options: {},
- // },
- // setLabel: {
- // commandFn: actions.setLabel,
- // storeContexts: [] as any[],
- // options: {},
- // },
- // setFinding: {
- // commandFn: actions.updateMeasurement,
- // storeContexts: [] as any[],
- // options: { measurementKey: 'finding' },
- // },
- // setSite: {
- // commandFn: actions.updateMeasurement,
- // storeContexts: [] as any[],
- // options: { measurementKey: 'site' },
- // },
-
- setToolActive: {
- commandFn: actions.setToolActive,
- storeContexts: [] as any[],
- options: {},
- },
- rotateViewportCW: {
- commandFn: actions.rotateViewport,
- storeContexts: [] as any[],
- options: { rotation: 90 },
- },
- rotateViewportCCW: {
- commandFn: actions.rotateViewport,
- storeContexts: [] as any[],
- options: { rotation: -90 },
- },
- incrementActiveViewport: {
- commandFn: actions.incrementActiveViewport,
- storeContexts: [] as any[],
- },
- decrementActiveViewport: {
- commandFn: actions.decrementActiveViewport,
- storeContexts: [] as any[],
- },
- flipViewportHorizontal: {
- commandFn: actions.flipViewportHorizontal,
- storeContexts: [] as any[],
- options: {},
- },
- flipViewportVertical: {
- commandFn: actions.flipViewportVertical,
- storeContexts: [] as any[],
- options: {},
- },
- resetViewport: {
- commandFn: actions.resetViewport,
- storeContexts: [] as any[],
- options: {},
- },
- scaleUpViewport: {
- commandFn: actions.scaleViewport,
- storeContexts: [] as any[],
- options: { direction: 1 },
- },
- scaleDownViewport: {
- commandFn: actions.scaleViewport,
- storeContexts: [] as any[],
- options: { direction: -1 },
- },
- fitViewportToWindow: {
- commandFn: actions.scaleViewport,
- storeContexts: [] as any[],
- options: { direction: 0 },
- },
- arrowTextCallback: {
- commandFn: actions.arrowTextCallback,
- storeContexts: [] as any[],
- options: {},
- },
- setViewportActive: {
- commandFn: actions.setViewportActive,
- storeContexts: [] as any[],
- options: {},
- },
- toggleOverlays: {
- commandFn: actions.toggleOverlays,
- storeContexts: [] as any[],
- options: {},
- },
- toggleAnnotations: {
- commandFn: actions.toggleAnnotations,
- storeContexts: [] as any[],
- options: {},
- },
- };
-
- return {
- actions,
- definitions,
- defaultContext: 'MICROSCOPY',
- };
-};
-
-export default commandsModule;
diff --git a/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx b/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx
index 001dbc9631..03b67432b7 100644
--- a/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx
+++ b/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx
@@ -6,7 +6,7 @@ import {
DicomMetadataStore,
} from '@ohif/core';
import { MeasurementTable, Icon, ButtonGroup, Button } from '@ohif/ui';
-import { withTranslation } from 'react-i18next';
+import { withTranslation, WithTranslation } from 'react-i18next';
import DEVICE_OBSERVER_UID from '../../utils/DEVICE_OBSERVER_UID';
import { EVENTS as MicroscopyEvents } from '../../tools/microscopyManager';
import dcmjs from 'dcmjs';
@@ -61,13 +61,8 @@ function saveByteArray(buffer: ArrayBuffer, filename: string) {
link.click();
}
-/**
- * Microscopy Measurements Panel Component
- *
- * @param props
- * @returns
- */
-function MicroscopyPanel(props: {
+
+interface IMicroscopyPanelProps extends WithTranslation {
viewports: PropTypes.array;
activeViewportIndex: PropTypes.number;
@@ -79,10 +74,15 @@ function MicroscopyPanel(props: {
//
servicesManager: ServicesManager;
extensionManager: ExtensionManager;
+}
- // Translator function (injected by withTranslation() below)
- t: PropTypes.func;
-}) {
+/**
+ * Microscopy Measurements Panel Component
+ *
+ * @param props
+ * @returns
+ */
+function MicroscopyPanel(props: IMicroscopyPanelProps) {
const microscopyManager: any = props.microscopyManager;
const [studyInstanceUID, setStudyInstanceUID] = useState(
diff --git a/extensions/dicom-microscopy/src/getCommandsModule.ts b/extensions/dicom-microscopy/src/getCommandsModule.ts
new file mode 100644
index 0000000000..9e5d281438
--- /dev/null
+++ b/extensions/dicom-microscopy/src/getCommandsModule.ts
@@ -0,0 +1,169 @@
+import microscopyManager from './tools/microscopyManager';
+import { ServicesManager, CommandsManager, ExtensionManager } from '@ohif/core';
+
+export default function getCommandsModule({
+ servicesManager,
+ commandsManager,
+ extensionManager,
+} : {
+ servicesManager: ServicesManager,
+ commandsManager: CommandsManager,
+ extensionManager: ExtensionManager,
+}) {
+ const {
+ viewportGridService,
+ toolbarService,
+ uiDialogService,
+ cornerstoneViewportService,
+ customizationService,
+ measurementService,
+ } = servicesManager.services;
+
+ const actions = {
+ setToolActive: ({ toolName, toolGroupId = 'MICROSCOPY' }) => {
+ if (
+ [
+ 'line',
+ 'box',
+ 'circle',
+ 'point',
+ 'polygon',
+ 'freehandpolygon',
+ 'freehandline',
+ ].indexOf(toolName) >= 0
+ ) {
+ // TODO: read from configuration
+ const styleOptions = {
+ stroke: {
+ color: [0, 255, 0, 1],
+ width: 1.2,
+ },
+ };
+ let options = {
+ geometryType: toolName,
+ vertexEnabled: true,
+ styleOptions,
+ } as any;
+ if ('line' === toolName) {
+ options.minPoints = 2;
+ options.maxPoints = 2;
+ }
+ else if('point' === toolName) {
+ delete options.styleOptions;
+ delete options.vertexEnabled;
+ }
+
+ microscopyManager.activateInteractions([['draw', options]]);
+ } else if (toolName == 'dragPan') {
+ microscopyManager.activateInteractions([['dragPan']]);
+ }
+ },
+ rotateViewport: ({ rotation }) => {},
+ flipViewportHorizontal: () => {},
+ flipViewportVertical: () => {},
+ invertViewport: ({ element }) => {},
+ resetViewport: () => {},
+ scaleViewport: ({ direction }) => {},
+ scroll: ({ direction }) => {},
+ incrementActiveViewport: () => {
+ const { activeViewportIndex, viewports } = viewportGridService.getState();
+ const nextViewportIndex = (activeViewportIndex + 1) % viewports.length;
+ viewportGridService.setActiveViewportIndex(nextViewportIndex);
+ },
+ decrementActiveViewport: () => {
+ const { activeViewportIndex, viewports } = viewportGridService.getState();
+ const nextViewportIndex =
+ (activeViewportIndex - 1 + viewports.length) % viewports.length;
+ viewportGridService.setActiveViewportIndex(nextViewportIndex);
+ },
+
+ toggleOverlays: () => {
+ // overlay
+ const overlays = document.getElementsByClassName('microscopy-viewport-overlay');
+ let onoff = false; // true if this will toggle on
+ for (let i = 0; i < overlays.length; i++) {
+ if (i === 0) onoff = overlays.item(0).classList.contains('hidden');
+ overlays.item(i).classList.toggle('hidden');
+ }
+
+ // overview
+ const { activeViewportIndex, viewports } = viewportGridService.getState();
+ microscopyManager.toggleOverviewMap(activeViewportIndex);
+ },
+ toggleAnnotations: () => {
+ microscopyManager.toggleROIsVisibility();
+ }
+ };
+
+ const definitions = {
+ setToolActive: {
+ commandFn: actions.setToolActive,
+ storeContexts: [] as any[],
+ options: {},
+ },
+ rotateViewportCW: {
+ commandFn: actions.rotateViewport,
+ storeContexts: [] as any[],
+ options: { rotation: 90 },
+ },
+ rotateViewportCCW: {
+ commandFn: actions.rotateViewport,
+ storeContexts: [] as any[],
+ options: { rotation: -90 },
+ },
+ incrementActiveViewport: {
+ commandFn: actions.incrementActiveViewport,
+ storeContexts: [] as any[],
+ },
+ decrementActiveViewport: {
+ commandFn: actions.decrementActiveViewport,
+ storeContexts: [] as any[],
+ },
+ flipViewportHorizontal: {
+ commandFn: actions.flipViewportHorizontal,
+ storeContexts: [] as any[],
+ options: {},
+ },
+ flipViewportVertical: {
+ commandFn: actions.flipViewportVertical,
+ storeContexts: [] as any[],
+ options: {},
+ },
+ resetViewport: {
+ commandFn: actions.resetViewport,
+ storeContexts: [] as any[],
+ options: {},
+ },
+ scaleUpViewport: {
+ commandFn: actions.scaleViewport,
+ storeContexts: [] as any[],
+ options: { direction: 1 },
+ },
+ scaleDownViewport: {
+ commandFn: actions.scaleViewport,
+ storeContexts: [] as any[],
+ options: { direction: -1 },
+ },
+ fitViewportToWindow: {
+ commandFn: actions.scaleViewport,
+ storeContexts: [] as any[],
+ options: { direction: 0 },
+ },
+ toggleOverlays: {
+ commandFn: actions.toggleOverlays,
+ storeContexts: [] as any[],
+ options: {},
+ },
+ toggleAnnotations: {
+ commandFn: actions.toggleAnnotations,
+ storeContexts: [] as any[],
+ options: {},
+ },
+ };
+
+ return {
+ actions,
+ definitions,
+ defaultContext: 'MICROSCOPY',
+ };
+};
diff --git a/extensions/dicom-microscopy/src/getPanelModule.tsx b/extensions/dicom-microscopy/src/getPanelModule.tsx
index 76f0004459..4393b0f130 100644
--- a/extensions/dicom-microscopy/src/getPanelModule.tsx
+++ b/extensions/dicom-microscopy/src/getPanelModule.tsx
@@ -1,4 +1,5 @@
import React from 'react';
+import { ServicesManager, CommandsManager, ExtensionManager } from '@ohif/core';
import { useViewportGrid } from '@ohif/ui';
import MicroscopyPanel from './components/MicroscopyPanel/MicroscopyPanel';
import microscopyManager from './tools/microscopyManager';
@@ -12,6 +13,10 @@ export default function getPanelModule({
commandsManager,
extensionManager,
servicesManager,
+} : {
+ servicesManager: ServicesManager,
+ commandsManager: CommandsManager,
+ extensionManager: ExtensionManager,
}) {
const wrappedMeasurementPanel = () => {
const [
diff --git a/extensions/dicom-microscopy/src/index.tsx b/extensions/dicom-microscopy/src/index.tsx
index fa659bced1..f9c4136657 100644
--- a/extensions/dicom-microscopy/src/index.tsx
+++ b/extensions/dicom-microscopy/src/index.tsx
@@ -1,4 +1,23 @@
import { id } from './id';
+import React, { Suspense } from 'react';
+import getPanelModule from './getPanelModule';
+import getCommandsModule from './getCommandsModule';
+
+import { useViewportGrid } from '@ohif/ui';
+import getDicomMicroscopySopClassHandler from './DicomMicroscopySopClassHandler';
+import getDicomMicroscopySRSopClassHandler from './DicomMicroscopySRSopClassHandler';
+
+const Component = React.lazy(() => {
+ return import('./DicomMicroscopyViewport');
+});
+
+const MicroscopyViewport = (props) => {
+ return (
+ Loading...}>
+
+
+ );
+};
/**
* You can remove any of the following modules if you don't need them.
@@ -10,117 +29,74 @@ export default {
*/
id,
- /**
- * Perform any pre-registration tasks here. This is called before the extension
- * is registered. Usually we run tasks such as: configuring the libraries
- * (e.g. cornerstone, cornerstoneTools, ...) or registering any services that
- * this extension is providing.
- */
- preRegistration: ({
- servicesManager,
- commandsManager,
- configuration = {},
- }) => {},
- /**
- * PanelModule should provide a list of panels that will be available in OHIF
- * for Modes to consume and render. Each panel is defined by a {name,
- * iconName, iconLabel, label, component} object. Example of a panel module
- * is the StudyBrowserPanel that is provided by the default extension in OHIF.
- */
- getPanelModule: ({
- servicesManager,
- commandsManager,
- extensionManager,
- }) => {},
/**
* ViewportModule should provide a list of viewports that will be available in OHIF
* for Modes to consume and use in the viewports. Each viewport is defined by
* {name, component} object. Example of a viewport module is the CornerstoneViewport
* that is provided by the Cornerstone extension in OHIF.
*/
- getViewportModule: ({
- servicesManager,
- commandsManager,
- extensionManager,
- }) => {},
- /**
- * ToolbarModule should provide a list of tool buttons that will be available in OHIF
- * for Modes to consume and use in the toolbar. Each tool button is defined by
- * {name, defaultComponent, clickHandler }. Examples include radioGroupIcons and
- * splitButton toolButton that the default extension is providing.
- */
- getToolbarModule: ({
- servicesManager,
- commandsManager,
- extensionManager,
- }) => {},
- /**
- * LayoutTemplateMOdule should provide a list of layout templates that will be
- * available in OHIF for Modes to consume and use to layout the viewer.
- * Each layout template is defined by a { name, id, component}. Examples include
- * the default layout template provided by the default extension which renders
- * a Header, left and right sidebars, and a viewport section in the middle
- * of the viewer.
- */
- getLayoutTemplateModule: ({
- servicesManager,
- commandsManager,
- extensionManager,
- }) => {},
+ getViewportModule({ servicesManager, extensionManager }) {
+
+ const ExtendedMicroscopyViewport = (props) => {
+ console.log('Creating an extended microscopy viewport');
+ const {
+ displaySets,
+ viewportIndex,
+ viewportLabel,
+ dataSource,
+ viewportOptions,
+ displaySetOptions,
+ } = props;
+
+ const [viewportGrid, viewportGridService] = useViewportGrid();
+ const { viewports, activeViewportIndex } = viewportGrid;
+
+ return (
+ {
+ viewportGridService.setActiveViewportIndex(viewportIndex);
+ }}
+ viewportData={viewportOptions}
+ {...props}
+ />
+ );
+ };
+
+ return [
+ {
+ name: 'microscopy-dicom',
+ component: ExtendedMicroscopyViewport
+ },
+ ];
+ },
+
/**
* SopClassHandlerModule should provide a list of sop class handlers that will be
* available in OHIF for Modes to consume and use to create displaySets from Series.
* Each sop class handler is defined by a { name, sopClassUids, getDisplaySetsFromSeries}.
* Examples include the default sop class handler provided by the default extension
*/
- getSopClassHandlerModule: ({
- servicesManager,
- commandsManager,
- extensionManager,
- }) => {},
- /**
- * HangingProtocolModule should provide a list of hanging protocols that will be
- * available in OHIF for Modes to use to decide on the structure of the viewports
- * and also the series that hung in the viewports. Each hanging protocol is defined by
- * { name, protocols}. Examples include the default hanging protocol provided by
- * the default extension that shows 2x2 viewports.
- */
- getHangingProtocolModule: ({
- servicesManager,
- commandsManager,
- extensionManager,
- }) => {},
- /**
- * CommandsModule should provide a list of commands that will be available in OHIF
- * for Modes to consume and use in the viewports. Each command is defined by
- * an object of { actions, definitions, defaultContext } where actions is an
- * object of functions, definitions is an object of available commands, their
- * options, and defaultContext is the default context for the command to run against.
- */
- getCommandsModule: ({
+ getSopClassHandlerModule({
servicesManager,
commandsManager,
extensionManager,
- }) => {},
- /**
- * ContextModule should provide a list of context that will be available in OHIF
- * and will be provided to the Modes. A context is a state that is shared OHIF.
- * Context is defined by an object of { name, context, provider }. Examples include
- * the measurementTracking context provided by the measurementTracking extension.
- */
- getContextModule: ({
- servicesManager,
- commandsManager,
- extensionManager,
- }) => {},
- /**
- * DataSourceModule should provide a list of data sources to be used in OHIF.
- * DataSources can be used to map the external data formats to the OHIF's
- * native format. DataSources are defined by an object of { name, type, createDataSource }.
- */
- getDataSourcesModule: ({
- servicesManager,
- commandsManager,
- extensionManager,
- }) => {},
+ }) {
+ return [
+ getDicomMicroscopySopClassHandler({
+ servicesManager,
+ extensionManager
+ }),
+ getDicomMicroscopySRSopClassHandler({
+ servicesManager,
+ extensionManager,
+ }),
+ ];
+ },
+
+ getPanelModule,
+
+ getCommandsModule,
};
diff --git a/modes/microscopy/src/index.tsx b/modes/microscopy/src/index.tsx
index 7fead82204..749e5ac040 100644
--- a/modes/microscopy/src/index.tsx
+++ b/modes/microscopy/src/index.tsx
@@ -1,15 +1,8 @@
import { hotkeys } from '@ohif/core';
-import { addIcon } from '@ohif/ui';
-import ConfigPoint from "config-point";
import { id } from './id';
import toolbarButtons from './toolbarButtons';
-import toolCircle from '../public/assets/icons/tool-circle.svg';
-import toolFreehand from '../public/assets/icons/tool-freehand.svg';
-import toolFreehandPolygon from '../public/assets/icons/tool-freehand-polygon.svg';
-import toolPolygon from '../public/assets/icons/tool-polygon.svg';
-
const ohif = {
layout: '@ohif/extension-default.layoutTemplateModule.viewerLayout',
sopClassHandler: '@ohif/extension-default.sopClassHandlerModule.stack',
@@ -22,12 +15,6 @@ export const cornerstone = {
viewport: '@ohif/extension-cornerstone.viewportModule.cornerstone',
};
-const dicomsr = {
- sopClassHandler:
- '@ohif/extension-cornerstone-dicom-sr.sopClassHandlerModule.dicom-sr',
- viewport: '@ohif/extension-cornerstone-dicom-sr.viewportModule.dicom-sr',
-};
-
const dicomvideo = {
sopClassHandler:
'@ohif/extension-dicom-video.sopClassHandlerModule.dicom-video',
@@ -61,17 +48,11 @@ function modeFactory() {
* Lifecycle hooks
*/
onModeEnter: ({ servicesManager, extensionManager, commandsManager }) => {
- const { ToolBarService } = servicesManager.services;
+ const { toolbarService } = servicesManager.services;
- addIcon('tool-point', toolCircle)
- addIcon('tool-circle', toolCircle)
- addIcon('tool-freehand-line', toolFreehand)
- addIcon('tool-freehand-polygon', toolFreehandPolygon)
- addIcon('tool-polygon', toolPolygon)
-
- ToolBarService.init(extensionManager);
- ToolBarService.addButtons(toolbarButtons);
- ToolBarService.createButtonSection('primary', [
+ toolbarService.init(extensionManager);
+ toolbarService.addButtons(toolbarButtons);
+ toolbarService.createButtonSection('primary', [
'MeasurementTools',
'dragPan',
]);
@@ -79,11 +60,10 @@ function modeFactory() {
onModeExit: ({ servicesManager }) => {
const {
- MeasurementService,
- ToolBarService,
+ toolbarService,
} = servicesManager.services;
- ToolBarService.reset();
+ toolbarService.reset();
},
validationTags: {
@@ -150,12 +130,10 @@ function modeFactory() {
};
}
-const mode = ConfigPoint.createConfiguration("microscopyMode", {
+const mode = {
id,
modeFactory,
extensionDependencies,
-});
-
-console.log('microscopy-mode=', mode)
+};
export default mode;
From 526045e86f366b102c95225de0df0c661addfbc1 Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Fri, 17 Mar 2023 17:23:24 -0400
Subject: [PATCH 09/51] trivial error fix - typescript type import error
---
extensions/cornerstone/src/index.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/extensions/cornerstone/src/index.tsx b/extensions/cornerstone/src/index.tsx
index 20b8695c61..2dda11b29c 100644
--- a/extensions/cornerstone/src/index.tsx
+++ b/extensions/cornerstone/src/index.tsx
@@ -26,7 +26,7 @@ import { registerColormap } from './utils/colormap/transferFunctionHelpers';
import { id } from './id';
import * as csWADOImageLoader from './initWADOImageLoader.js';
import { measurementMappingUtils } from './utils/measurementServiceMappings';
-import { PublicViewportOptions } from './services/ViewportService/Viewport';
+import type { PublicViewportOptions } from './services/ViewportService/Viewport';
const Component = React.lazy(() => {
return import(
From 0cd64e83eec4f4a7952d1d3f0a0dc74c77eff33c Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Fri, 17 Mar 2023 17:23:55 -0400
Subject: [PATCH 10/51] link microscopy extension with default OHIF app plugin
config
---
platform/viewer/pluginConfig.json | 10 +-
yarn.lock | 330 +++++++++++++++++++++++++++++-
2 files changed, 330 insertions(+), 10 deletions(-)
diff --git a/platform/viewer/pluginConfig.json b/platform/viewer/pluginConfig.json
index f69de27704..0db271d7a9 100644
--- a/platform/viewer/pluginConfig.json
+++ b/platform/viewer/pluginConfig.json
@@ -16,6 +16,10 @@
"packageName": "@ohif/extension-default",
"version": "3.0.0"
},
+ {
+ "packageName": "@ohif/extension-dicom-microscopy",
+ "version": "3.0.0"
+ },
{
"packageName": "@ohif/extension-dicom-pdf",
"version": "3.0.1"
@@ -38,6 +42,10 @@
"packageName": "@ohif/mode-longitudinal",
"version": "3.0.0"
},
+ {
+ "packageName": "@ohif/mode-microscopy",
+ "version": "3.0.0"
+ },
{
"packageName": "@ohif/mode-tmtv",
"version": "3.0.0"
@@ -45,4 +53,4 @@
],
"modesFactory": [],
"umd": []
-}
\ No newline at end of file
+}
diff --git a/yarn.lock b/yarn.lock
index 8d7ec3330d..13a8ddb493 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1308,7 +1308,7 @@
core-js-pure "^3.25.1"
regenerator-runtime "^0.13.11"
-"@babel/runtime@7.17.9", "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.6", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.6", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.7", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
+"@babel/runtime@7.17.9", "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.6", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.6", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
version "7.20.13"
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz#7055ab8a7cff2b8f6058bf6ae45ff84ad2aded4b"
integrity sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==
@@ -1423,19 +1423,34 @@
resolved "https://registry.npmjs.org/@cornerstonejs/calculate-suv/-/calculate-suv-1.0.3.tgz#6d99a72032c0f90cebf44dc6f0b12a5f1102e884"
integrity sha512-2SwVJKzC1DzyxdxJtCht9dhTND2GFjLwhhkDyyC7vJq5tIgbhxgPk1CSwovO1pxmoybAXzjOxnaubllxLgoT+w==
+"@cornerstonejs/codec-charls@^0.1.1":
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/@cornerstonejs/codec-charls/-/codec-charls-0.1.1.tgz#e55d4aa908732d0cc902888b7f3856c5a996df7f"
+ integrity sha512-Y250DGVzmownJ7WgpHxNqWvfTnv4/malaKm/tWm0xE1FxhQE8iErMWFpKxpNDk3MdfXO4/98piVsUwmJMiWoDQ==
+
"@cornerstonejs/codec-charls@^1.2.3":
version "1.2.3"
- resolved "https://registry.npmjs.org/@cornerstonejs/codec-charls/-/codec-charls-1.2.3.tgz#6952c420486822ac8404409ae0ed5a559aff6e25"
+ resolved "https://registry.yarnpkg.com/@cornerstonejs/codec-charls/-/codec-charls-1.2.3.tgz#6952c420486822ac8404409ae0ed5a559aff6e25"
integrity sha512-qKUe6DN0dnGzhhfZLYhH9UZacMcudjxcaLXCrpxJImT/M/PQvZCT2rllu6VGJbWKJWG+dMVV2zmmleZcdJ7/cA==
+"@cornerstonejs/codec-libjpeg-turbo-8bit@^0.0.7":
+ version "0.0.7"
+ resolved "https://registry.yarnpkg.com/@cornerstonejs/codec-libjpeg-turbo-8bit/-/codec-libjpeg-turbo-8bit-0.0.7.tgz#2ea9b575eed19e6e7e3701b7a50a4ae0ffbef0c4"
+ integrity sha512-qgm6BuVAy5mNP8SJ+A6+VbmPnqgj8jPvJrw4HbUoAzndmf9/VHjTYwawn3kmZWya5ErFAsXQ6c0U0noB1LKAiA==
+
"@cornerstonejs/codec-libjpeg-turbo-8bit@^1.2.2":
version "1.2.2"
- resolved "https://registry.npmjs.org/@cornerstonejs/codec-libjpeg-turbo-8bit/-/codec-libjpeg-turbo-8bit-1.2.2.tgz#ae384b149d6655e3dd6e18b9891fab479ab5e144"
+ resolved "https://registry.yarnpkg.com/@cornerstonejs/codec-libjpeg-turbo-8bit/-/codec-libjpeg-turbo-8bit-1.2.2.tgz#ae384b149d6655e3dd6e18b9891fab479ab5e144"
integrity sha512-aAUMK2958YNpOb/7G6e2/aG7hExTiFTASlMt/v90XA0pRHdWiNg5ny4S5SAju0FbIw4zcMnR0qfY+yW3VG2ivg==
+"@cornerstonejs/codec-openjpeg@^0.1.1":
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/@cornerstonejs/codec-openjpeg/-/codec-openjpeg-0.1.1.tgz#5bd1c52a33a425299299e970312731fa0cc2711b"
+ integrity sha512-HOMMOLV6xy8O/agNGGvrl0a8DwShpBvWxAzEzv2pqq12d3r5z/3MyIgNA3Oj/8bIBVvvVXxh9RX7rMDRHJdowg==
+
"@cornerstonejs/codec-openjpeg@^1.2.2":
version "1.2.2"
- resolved "https://registry.npmjs.org/@cornerstonejs/codec-openjpeg/-/codec-openjpeg-1.2.2.tgz#f0b524235b5551426b46db197a37b06f8ac805d7"
+ resolved "https://registry.yarnpkg.com/@cornerstonejs/codec-openjpeg/-/codec-openjpeg-1.2.2.tgz#f0b524235b5551426b46db197a37b06f8ac805d7"
integrity sha512-b1O7lZacKXelgeV9n8XWZ7pTw3i4Bq4qQ26G5ahBjWoOw4QNcCrb5hPxWBxNB/I8AoNbJxAe+lyLtyQGfdrTbw==
"@cornerstonejs/codec-openjph@^2.4.2":
@@ -3346,6 +3361,35 @@
npmlog "^4.1.2"
write-file-atomic "^2.3.0"
+"@mapbox/jsonlint-lines-primitives@~2.0.2":
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz#ce56e539f83552b58d10d672ea4d6fc9adc7b234"
+ integrity sha512-rY0o9A5ECsTQRVhv7tL/OyDpGAoUB4tTvLiW1DSzQGq4bvTPhNw1VpSNjDJc5GFZ2XuyOtSWSVN05qOtcD71qQ==
+
+"@mapbox/mapbox-gl-style-spec@^13.23.1":
+ version "13.28.0"
+ resolved "https://registry.yarnpkg.com/@mapbox/mapbox-gl-style-spec/-/mapbox-gl-style-spec-13.28.0.tgz#2ec226320a0f77856046e000df9b419303a56458"
+ integrity sha512-B8xM7Fp1nh5kejfIl4SWeY0gtIeewbuRencqO3cJDrCHZpaPg7uY+V8abuR+esMeuOjRl5cLhVTP40v+1ywxbg==
+ dependencies:
+ "@mapbox/jsonlint-lines-primitives" "~2.0.2"
+ "@mapbox/point-geometry" "^0.1.0"
+ "@mapbox/unitbezier" "^0.0.0"
+ csscolorparser "~1.0.2"
+ json-stringify-pretty-compact "^2.0.0"
+ minimist "^1.2.6"
+ rw "^1.3.3"
+ sort-object "^0.3.2"
+
+"@mapbox/point-geometry@^0.1.0":
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz#8a83f9335c7860effa2eeeca254332aa0aeed8f2"
+ integrity sha512-6j56HdLTwWGO0fJPlrZtdU/B13q8Uwmo18Ck2GnGgN9PCFyKTZ3UbXeEdRFh18i9XQ92eH2VdtpJHpBD3aripQ==
+
+"@mapbox/unitbezier@^0.0.0":
+ version "0.0.0"
+ resolved "https://registry.yarnpkg.com/@mapbox/unitbezier/-/unitbezier-0.0.0.tgz#15651bd553a67b8581fb398810c98ad86a34524e"
+ integrity sha512-HPnRdYO0WjFjRTSwO3frz1wKaU649OBFPX3Zo/2WZvuRi6zMiRGui8SnPQiQABgqCf8YikDe5t3HViTVw1WUzA==
+
"@mdx-js/mdx@^1.6.22":
version "1.6.22"
resolved "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-1.6.22.tgz#8a723157bf90e78f17dc0f27995398e6c731f1ba"
@@ -3606,6 +3650,11 @@
resolved "https://registry.npmjs.org/@percy/sdk-utils/-/sdk-utils-1.18.0.tgz#3c0c7faaf2668eb679546554418e96a53c81db4a"
integrity sha512-c+Avhw5rNa+xyAFIpuQ4jxksw7/5DWyPGaNncTMY4Dbc1fVxT6nu6V1Mytc9A1zxl0+ficQHTZeSugM4aL81hQ==
+"@petamoriken/float16@^3.4.7":
+ version "3.7.1"
+ resolved "https://registry.yarnpkg.com/@petamoriken/float16/-/float16-3.7.1.tgz#4a0cc0854a3a101cc2d697272f120e1a05975ce5"
+ integrity sha512-oXZOc+aePd0FnhTWk15pyqK+Do87n0TyLV1nxdEougE95X/WXWDqmQobfhgnSY7QsWn5euZUWuDVeTQvoQ5VNw==
+
"@philpl/buble@^0.19.7":
version "0.19.7"
resolved "https://registry.npmjs.org/@philpl/buble/-/buble-0.19.7.tgz#27231e6391393793b64bc1c982fc7b593198b893"
@@ -6446,6 +6495,11 @@ array-union@^2.1.0:
resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
+array-union@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/array-union/-/array-union-3.0.1.tgz#da52630d327f8b88cfbfb57728e2af5cd9b6b975"
+ integrity sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==
+
array-uniq@^1.0.1:
version "1.0.3"
resolved "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6"
@@ -8101,6 +8155,13 @@ colorette@^2.0.10, colorette@^2.0.14, colorette@^2.0.16:
resolved "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798"
integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==
+colormap@^2.3:
+ version "2.3.2"
+ resolved "https://registry.yarnpkg.com/colormap/-/colormap-2.3.2.tgz#4422c1178ce563806e265b96782737be85815abf"
+ integrity sha512-jDOjaoEEmA9AgA11B/jCSAvYE95r3wRoAyTf3LEHGiUVlNHJaL1mRkf5AyLSpQBVGfTEPwGEqCIzL+kgr2WgNA==
+ dependencies:
+ lerp "^1.0.3"
+
colors@~1.1.2:
version "1.1.2"
resolved "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63"
@@ -8189,6 +8250,11 @@ compare-func@^2.0.0:
array-ify "^1.0.0"
dot-prop "^5.1.0"
+complex.js@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/complex.js/-/complex.js-2.1.1.tgz#0675dac8e464ec431fb2ab7d30f41d889fb25c31"
+ integrity sha512-8njCHOTtFFLtegk6zQo0kkVX1rngygb/KQI6z1qZxlFI3scluC+LVTCFbrkWjBv4vvLlbQ9t88IPMC6k95VTTg==
+
component-emitter@^1.2.1:
version "1.3.0"
resolved "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
@@ -8466,6 +8532,18 @@ copy-text-to-clipboard@^3.0.1:
resolved "https://registry.npmjs.org/copy-text-to-clipboard/-/copy-text-to-clipboard-3.0.1.tgz#8cbf8f90e0a47f12e4a24743736265d157bce69c"
integrity sha512-rvVsHrpFcL4F2P8ihsoLdFHmd404+CMg71S756oRSeQgqk51U3kicGdnvfkrxva0xXH92SjGS62B0XIJsbh+9Q==
+copy-webpack-plugin@^10.2.0:
+ version "10.2.4"
+ resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-10.2.4.tgz#6c854be3fdaae22025da34b9112ccf81c63308fe"
+ integrity sha512-xFVltahqlsRcyyJqQbDY6EYTtyQZF9rf+JPjwHObLdPFMEISqkFkr7mFoVOC6BfYS/dNThyoQKvziugm+OnwBg==
+ dependencies:
+ fast-glob "^3.2.7"
+ glob-parent "^6.0.1"
+ globby "^12.0.2"
+ normalize-path "^3.0.0"
+ schema-utils "^4.0.0"
+ serialize-javascript "^6.0.0"
+
copy-webpack-plugin@^11.0.0:
version "11.0.0"
resolved "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz#96d4dbdb5f73d02dd72d0528d1958721ab72e04a"
@@ -8915,6 +8993,11 @@ css-what@^6.0.1, css-what@^6.1.0:
resolved "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4"
integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==
+csscolorparser@~1.0.2:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/csscolorparser/-/csscolorparser-1.0.3.tgz#b34f391eea4da8f3e98231e2ccd8df9c041f171b"
+ integrity sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w==
+
cssdb@^7.1.0:
version "7.4.1"
resolved "https://registry.npmjs.org/cssdb/-/cssdb-7.4.1.tgz#61d55c0173126689922a219e15e131e4b5caf422"
@@ -9263,6 +9346,17 @@ dayjs@^1.10.4:
resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz#4b296922642f70999544d1144a2c25730fce63e2"
integrity sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==
+dcmjs@^0.27:
+ version "0.27.0"
+ resolved "https://registry.yarnpkg.com/dcmjs/-/dcmjs-0.27.0.tgz#2662818c8b20494e366583e6dd3577c20d04d6ff"
+ integrity sha512-26wtatOLh+0b0aFy9iOg7PdOLG9EHevn9nEOn7Aoo5l7P9aFAMZ3XAa9Q+NULzLE2Q7DcIf2TQvfyVtdzhQzeg==
+ dependencies:
+ "@babel/runtime-corejs2" "^7.17.8"
+ gl-matrix "^3.1.0"
+ lodash.clonedeep "^4.5.0"
+ loglevelnext "^3.0.1"
+ ndarray "^1.0.19"
+
dcmjs@^0.29.4:
version "0.29.4"
resolved "https://registry.npmjs.org/dcmjs/-/dcmjs-0.29.4.tgz#3fb13945611979f756bc91b3f199fd87b4eabdb4"
@@ -9326,6 +9420,11 @@ decamelize@^1.1.0, decamelize@^1.1.2, decamelize@^1.2.0:
resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==
+decimal.js@^10.4.3:
+ version "10.4.3"
+ resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23"
+ integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==
+
decode-uri-component@^0.2.0:
version "0.2.2"
resolved "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9"
@@ -9643,12 +9742,34 @@ dezalgo@^1.0.0:
asap "^2.0.0"
wrappy "1"
+dicom-microscopy-viewer@^0.44.0:
+ version "0.44.0"
+ resolved "https://registry.yarnpkg.com/dicom-microscopy-viewer/-/dicom-microscopy-viewer-0.44.0.tgz#d4a9e985acb23c5b82a9aedbe379b39198b8fd55"
+ integrity sha512-7rcm8bXTcOLsXrhBPwWe5gf4Sj1Rz1lRh+ECcaznOEr/uvX6BTAYLqNJNmeAUMoKcadboEeDrmLihCwCteRSJQ==
+ dependencies:
+ "@cornerstonejs/codec-charls" "^0.1.1"
+ "@cornerstonejs/codec-libjpeg-turbo-8bit" "^0.0.7"
+ "@cornerstonejs/codec-openjpeg" "^0.1.1"
+ colormap "^2.3"
+ dcmjs "^0.27"
+ dicomicc "^0.1"
+ dicomweb-client "^0.8"
+ image-type "^4.1"
+ mathjs "^11.2"
+ ol "^7.1"
+ uuid "^9.0"
+
dicom-parser@^1.8.9:
version "1.8.20"
resolved "https://registry.npmjs.org/dicom-parser/-/dicom-parser-1.8.20.tgz#e5ef817d80d2fbc093ffd8c2a73a1cb5b47f50bd"
integrity sha512-R8NXEcaqXu7Qe5exY662aXiJCbiE6fEF8+QpSkVcXTTecFzfIaW8KJ2tSwYvt0UMHP0AUBW13VxQSXfKv/0FUA==
-dicomweb-client@^0.8.4:
+dicomicc@^0.1:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/dicomicc/-/dicomicc-0.1.0.tgz#c73acc60a8e2d73a20f462c8c7d0e1e0d977c486"
+ integrity sha512-kZejPGjLQ9NsgovSyVsiAuCpq6LofNR9Erc8Tt/vQAYGYCoQnTyWDlg5D0TJJQATKul7cSr9k/q0TF8G9qdDkQ==
+
+dicomweb-client@^0.8, dicomweb-client@^0.8.4:
version "0.8.4"
resolved "https://registry.yarnpkg.com/dicomweb-client/-/dicomweb-client-0.8.4.tgz#3da814cedb9415facb50bc5f43af8d961a991c74"
integrity sha512-/6oY3/Fg9JyAlbTWuJOYbVqici3+nlZt43+Z/Y47RNiqLc028JcxNlY28u4VQqksxfB59f1hhNbsqsHyDT4vhw==
@@ -9970,6 +10091,11 @@ duplexify@^3.4.2, duplexify@^3.6.0:
readable-stream "^2.0.0"
stream-shift "^1.0.0"
+earcut@^2.2.3:
+ version "2.2.4"
+ resolved "https://registry.yarnpkg.com/earcut/-/earcut-2.2.4.tgz#6d02fd4d68160c114825d06890a92ecaae60343a"
+ integrity sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==
+
eastasianwidth@^0.2.0:
version "0.2.0"
resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
@@ -10284,6 +10410,11 @@ escape-html@^1.0.3, escape-html@~1.0.3:
resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
+escape-latex@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/escape-latex/-/escape-latex-1.2.0.tgz#07c03818cf7dac250cce517f4fda1b001ef2bca1"
+ integrity sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==
+
escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
@@ -11240,6 +11371,11 @@ file-system-cache@^1.0.5:
fs-extra "^10.1.0"
ramda "^0.28.0"
+file-type@^10.10.0:
+ version "10.11.0"
+ resolved "https://registry.yarnpkg.com/file-type/-/file-type-10.11.0.tgz#2961d09e4675b9fb9a3ee6b69e9cd23f43fd1890"
+ integrity sha512-uzk64HRpUZyTGZtVuvrjP0FYxzQrBf4rojot6J65YMEbwBLB0CWm0CLojVpwpmFmxcE/lkvYICgfcGozbBq6rw==
+
file-uri-to-path@1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
@@ -11688,6 +11824,19 @@ gensync@^1.0.0-beta.1, gensync@^1.0.0-beta.2:
resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
+geotiff@^2.0.7:
+ version "2.0.7"
+ resolved "https://registry.yarnpkg.com/geotiff/-/geotiff-2.0.7.tgz#358e578233af70bfb0b4dee62d599ad78fc5cfca"
+ integrity sha512-FKvFTNowMU5K6lHYY2f83d4lS2rsCNdpUC28AX61x9ZzzqPNaWFElWv93xj0eJFaNyOYA63ic5OzJ88dHpoA5Q==
+ dependencies:
+ "@petamoriken/float16" "^3.4.7"
+ lerc "^3.0.0"
+ pako "^2.0.4"
+ parse-headers "^2.0.2"
+ quick-lru "^6.1.1"
+ web-worker "^1.2.0"
+ xml-utils "^1.0.2"
+
get-caller-file@^2.0.1, get-caller-file@^2.0.5:
version "2.0.5"
resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
@@ -12004,6 +12153,18 @@ globby@^11.0.1, globby@^11.0.2, globby@^11.0.3, globby@^11.0.4, globby@^11.1.0:
merge2 "^1.4.1"
slash "^3.0.0"
+globby@^12.0.2:
+ version "12.2.0"
+ resolved "https://registry.yarnpkg.com/globby/-/globby-12.2.0.tgz#2ab8046b4fba4ff6eede835b29f678f90e3d3c22"
+ integrity sha512-wiSuFQLZ+urS9x2gGPl1H5drc5twabmm4m2gTR27XDFyjUHJUNsS8o/2aKyIF6IoBaR630atdher0XJ5g6OMmA==
+ dependencies:
+ array-union "^3.0.1"
+ dir-glob "^3.0.1"
+ fast-glob "^3.2.7"
+ ignore "^5.1.9"
+ merge2 "^1.4.1"
+ slash "^4.0.0"
+
globby@^13.1.1:
version "13.1.3"
resolved "https://registry.npmjs.org/globby/-/globby-13.1.3.tgz#f62baf5720bcb2c1330c8d4ef222ee12318563ff"
@@ -12714,7 +12875,7 @@ identity-obj-proxy@3.0.x:
dependencies:
harmony-reflect "^1.4.6"
-ieee754@^1.1.13, ieee754@^1.1.4:
+ieee754@^1.1.12, ieee754@^1.1.13, ieee754@^1.1.4:
version "1.2.1"
resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
@@ -12736,7 +12897,7 @@ ignore@^4.0.3, ignore@^4.0.6:
resolved "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==
-ignore@^5.0.0, ignore@^5.1.1, ignore@^5.1.8, ignore@^5.2.0:
+ignore@^5.0.0, ignore@^5.1.1, ignore@^5.1.8, ignore@^5.1.9, ignore@^5.2.0:
version "5.2.4"
resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324"
integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==
@@ -12748,6 +12909,13 @@ image-size@^1.0.1:
dependencies:
queue "6.0.2"
+image-type@^4.1:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/image-type/-/image-type-4.1.0.tgz#72a88d64ff5021371ed67b9a466442100be57cd1"
+ integrity sha512-CFJMJ8QK8lJvRlTCEgarL4ro6hfDQKif2HjSvYCdQZESaIPV4v9imrf7BQHK+sQeTeNeMpWciR9hyC/g8ybXEg==
+ dependencies:
+ file-type "^10.10.0"
+
immer@^9.0.7:
version "9.0.19"
resolved "https://registry.npmjs.org/immer/-/immer-9.0.19.tgz#67fb97310555690b5f9cd8380d38fc0aabb6b38b"
@@ -13720,6 +13888,11 @@ jake@^10.8.5:
filelist "^1.0.1"
minimatch "^3.0.4"
+javascript-natural-sort@^0.7.1:
+ version "0.7.1"
+ resolved "https://registry.yarnpkg.com/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz#f9e2303d4507f6d74355a73664d1440fb5a0ef59"
+ integrity sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==
+
jest-canvas-mock@^2.1.0:
version "2.4.0"
resolved "https://registry.npmjs.org/jest-canvas-mock/-/jest-canvas-mock-2.4.0.tgz#947b71442d7719f8e055decaecdb334809465341"
@@ -14347,6 +14520,11 @@ json-stable-stringify-without-jsonify@^1.0.1:
resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
+json-stringify-pretty-compact@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/json-stringify-pretty-compact/-/json-stringify-pretty-compact-2.0.0.tgz#e77c419f52ff00c45a31f07f4c820c2433143885"
+ integrity sha512-WRitRfs6BGq4q8gTgOy4ek7iPFXjbra0H3PmDLKm2xnZ+Gh1HUhiKGgCZkSPNULlP7mvfu6FV/mOLhCarspADQ==
+
json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1:
version "5.0.1"
resolved "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
@@ -14517,6 +14695,11 @@ left-pad@^1.3.0:
resolved "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e"
integrity sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==
+lerc@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/lerc/-/lerc-3.0.0.tgz#36f36fbd4ba46f0abf4833799fff2e7d6865f5cb"
+ integrity sha512-Rm4J/WaHhRa93nCN2mwWDZFoRVF18G1f47C+kvQWyHGEZxFpTUi73p7lMVSAndyxGt6lJ2/CFbOcf9ra5p8aww==
+
lerna@^3.15.0:
version "3.22.1"
resolved "https://registry.npmjs.org/lerna/-/lerna-3.22.1.tgz#82027ac3da9c627fd8bf02ccfeff806a98e65b62"
@@ -14541,6 +14724,11 @@ lerna@^3.15.0:
import-local "^2.0.0"
npmlog "^4.1.2"
+lerp@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/lerp/-/lerp-1.0.3.tgz#a18c8968f917896de15ccfcc28d55a6b731e776e"
+ integrity sha512-70Rh4rCkJDvwWiTsyZ1HmJGvnyfFah4m6iTux29XmasRiZPDBpT9Cfa4ai73+uLZxnlKruUS62jj2lb11wURiA==
+
leven@^3.1.0:
version "3.1.0"
resolved "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
@@ -15072,6 +15260,11 @@ map-visit@^1.0.0:
dependencies:
object-visit "^1.0.0"
+mapbox-to-css-font@^2.4.1:
+ version "2.4.2"
+ resolved "https://registry.yarnpkg.com/mapbox-to-css-font/-/mapbox-to-css-font-2.4.2.tgz#a9e31b363ad8ca881cd339ca99f2d2a6b02ea5dd"
+ integrity sha512-f+NBjJJY4T3dHtlEz1wCG7YFlkODEjFIYlxDdLIDMNpkSksqTt+l/d4rjuwItxuzkuMFvPyrjzV2lxRM4ePcIA==
+
markdown-escapes@^1.0.0:
version "1.0.4"
resolved "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz#c95415ef451499d7602b91095f3c8e8975f78535"
@@ -15082,6 +15275,21 @@ material-colors@^1.2.1:
resolved "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz#6d1958871126992ceecc72f4bcc4d8f010865f46"
integrity sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==
+mathjs@^11.2:
+ version "11.7.0"
+ resolved "https://registry.yarnpkg.com/mathjs/-/mathjs-11.7.0.tgz#a197e82c760c57744b20324a8c4eed677bddf179"
+ integrity sha512-RCXtrP5xGIbl9PUc5+7QL81rBCUjzoIZ0ugNqKsarOUxg+x7deY0BzfNai+bGfUL/T+1uYq1xs5w2xVdL3lp0g==
+ dependencies:
+ "@babel/runtime" "^7.21.0"
+ complex.js "^2.1.1"
+ decimal.js "^10.4.3"
+ escape-latex "^1.2.0"
+ fraction.js "^4.2.0"
+ javascript-natural-sort "^0.7.1"
+ seedrandom "^3.0.5"
+ tiny-emitter "^2.1.0"
+ typed-function "^4.1.0"
+
md5.js@^1.3.4:
version "1.3.5"
resolved "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f"
@@ -16252,6 +16460,25 @@ oidc-client@1.11.5:
crypto-js "^4.0.0"
serialize-javascript "^4.0.0"
+ol-mapbox-style@^9.2.0:
+ version "9.7.0"
+ resolved "https://registry.yarnpkg.com/ol-mapbox-style/-/ol-mapbox-style-9.7.0.tgz#38a4f7abc8f0a94f378dcdb7cefdcc69ca3f6287"
+ integrity sha512-YX3u8FBJHsRHaoGxmd724Mp5WPTuV7wLQW6zZhcihMuInsSdCX1EiZfU+8IAL7jG0pbgl5YgC0aWE/MXJcUXxg==
+ dependencies:
+ "@mapbox/mapbox-gl-style-spec" "^13.23.1"
+ mapbox-to-css-font "^2.4.1"
+
+ol@^7.1:
+ version "7.3.0"
+ resolved "https://registry.yarnpkg.com/ol/-/ol-7.3.0.tgz#7ffb5f258dafa4a3e218208aad9054d61f6fe786"
+ integrity sha512-08vJE4xITKPazQ9qJjeqYjRngnM9s+1eSv219Pdlrjj3LpLqjEH386ncq+76Dw1oGPGR8eLVEePk7FEd9XqqMw==
+ dependencies:
+ earcut "^2.2.3"
+ geotiff "^2.0.7"
+ ol-mapbox-style "^9.2.0"
+ pbf "3.2.1"
+ rbush "^3.0.1"
+
on-finished@2.4.1:
version "2.4.1"
resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
@@ -16646,6 +16873,11 @@ parse-github-repo-url@^1.3.0:
resolved "https://registry.npmjs.org/parse-github-repo-url/-/parse-github-repo-url-1.4.1.tgz#9e7d8bb252a6cb6ba42595060b7bf6df3dbc1f50"
integrity sha512-bSWyzBKqcSL4RrncTpGsEKoJ7H8a4L3++ifTAbTFeMHyq2wRV+42DGmQcHIrJIvdcacjIOxEuKH/w4tthF17gg==
+parse-headers@^2.0.2:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/parse-headers/-/parse-headers-2.0.5.tgz#069793f9356a54008571eb7f9761153e6c770da9"
+ integrity sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==
+
parse-json@^2.2.0:
version "2.2.0"
resolved "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9"
@@ -16846,6 +17078,14 @@ pause-stream@0.0.11:
dependencies:
through "~2.3"
+pbf@3.2.1:
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/pbf/-/pbf-3.2.1.tgz#b4c1b9e72af966cd82c6531691115cc0409ffe2a"
+ integrity sha512-ClrV7pNOn7rtmoQVF4TS1vyU0WhYRnP92fzbfF75jAIwpnzdJXf8iTd4CMEqO4yUenH6NDqLiwjqlh6QgZzgLQ==
+ dependencies:
+ ieee754 "^1.1.12"
+ resolve-protobuf-schema "^2.1.0"
+
pbkdf2@^3.0.3:
version "3.1.2"
resolved "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075"
@@ -18155,6 +18395,11 @@ proto-list@~1.2.1:
resolved "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==
+protocol-buffers-schema@^3.3.1:
+ version "3.6.0"
+ resolved "https://registry.yarnpkg.com/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz#77bc75a48b2ff142c1ad5b5b90c94cd0fa2efd03"
+ integrity sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==
+
protocols@^1.4.0:
version "1.4.8"
resolved "https://registry.npmjs.org/protocols/-/protocols-1.4.8.tgz#48eea2d8f58d9644a4a32caae5d5db290a075ce8"
@@ -18350,6 +18595,16 @@ quick-lru@^5.1.1:
resolved "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
+quick-lru@^6.1.1:
+ version "6.1.1"
+ resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-6.1.1.tgz#f8e5bf9010376c126c80c1a62827a526c0e60adf"
+ integrity sha512-S27GBT+F0NTRiehtbrgaSE1idUAJ5bX8dPAQTdylEyNlrdcH5X4Lz7Edz3DYzecbsCluD5zO8ZNEe04z3D3u6Q==
+
+quickselect@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/quickselect/-/quickselect-2.0.0.tgz#f19680a486a5eefb581303e023e98faaf25dd018"
+ integrity sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==
+
raf@^3.4.1:
version "3.4.1"
resolved "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39"
@@ -18405,6 +18660,13 @@ raw-loader@^4.0.2:
loader-utils "^2.0.0"
schema-utils "^3.0.0"
+rbush@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/rbush/-/rbush-3.0.1.tgz#5fafa8a79b3b9afdfe5008403a720cc1de882ecf"
+ integrity sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==
+ dependencies:
+ quickselect "^2.0.0"
+
rc@1.2.8, rc@^1.0.1, rc@^1.1.6, rc@^1.2.7, rc@^1.2.8:
version "1.2.8"
resolved "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
@@ -19478,6 +19740,13 @@ resolve-pathname@^3.0.0:
resolved "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd"
integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==
+resolve-protobuf-schema@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz#9ca9a9e69cf192bbdaf1006ec1973948aa4a3758"
+ integrity sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==
+ dependencies:
+ protocol-buffers-schema "^3.3.1"
+
resolve-url@^0.2.1:
version "0.2.1"
resolved "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
@@ -19662,6 +19931,11 @@ run-queue@^1.0.0, run-queue@^1.0.3:
dependencies:
aproba "^1.1.1"
+rw@^1.3.3:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4"
+ integrity sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==
+
rxjs@^6.3.3, rxjs@^6.4.0:
version "6.6.7"
resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9"
@@ -19794,7 +20068,7 @@ section-matter@^1.0.0:
extend-shallow "^2.0.1"
kind-of "^6.0.0"
-seedrandom@3.0.5:
+seedrandom@3.0.5, seedrandom@^3.0.5:
version "3.0.5"
resolved "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz#54edc85c95222525b0c7a6f6b3543d8e0b3aa0a7"
integrity sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==
@@ -20242,11 +20516,21 @@ socks@~2.3.2:
ip "1.1.5"
smart-buffer "^4.1.0"
+sort-asc@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/sort-asc/-/sort-asc-0.1.0.tgz#ab799df61fc73ea0956c79c4b531ed1e9e7727e9"
+ integrity sha512-jBgdDd+rQ+HkZF2/OHCmace5dvpos/aWQpcxuyRs9QUbPRnkEJmYVo81PIGpjIdpOcsnJ4rGjStfDHsbn+UVyw==
+
sort-css-media-queries@2.1.0:
version "2.1.0"
resolved "https://registry.npmjs.org/sort-css-media-queries/-/sort-css-media-queries-2.1.0.tgz#7c85e06f79826baabb232f5560e9745d7a78c4ce"
integrity sha512-IeWvo8NkNiY2vVYdPa27MCQiR0MN0M80johAYFVxWWXQ44KU84WNxjslwBHmc/7ZL2ccwkM7/e6S5aiKZXm7jA==
+sort-desc@^0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/sort-desc/-/sort-desc-0.1.1.tgz#198b8c0cdeb095c463341861e3925d4ee359a9ee"
+ integrity sha512-jfZacW5SKOP97BF5rX5kQfJmRVZP5/adDUTY8fCSPvNcXDVpUEe2pr/iKGlcyZzchRJZrswnp68fgk3qBXgkJw==
+
sort-keys@^1.0.0:
version "1.1.2"
resolved "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad"
@@ -20261,6 +20545,14 @@ sort-keys@^2.0.0:
dependencies:
is-plain-obj "^1.0.0"
+sort-object@^0.3.2:
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/sort-object/-/sort-object-0.3.2.tgz#98e0d199ede40e07c61a84403c61d6c3b290f9e2"
+ integrity sha512-aAQiEdqFTTdsvUFxXm3umdo04J7MRljoVGbBlkH7BgNsMvVNAJyGj7C/wV1A8wHWAJj/YikeZbfuCKqhggNWGA==
+ dependencies:
+ sort-asc "^0.1.0"
+ sort-desc "^0.1.1"
+
source-list-map@^2.0.0:
version "2.0.1"
resolved "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
@@ -21342,6 +21634,11 @@ timsort@^0.3.0:
resolved "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4"
integrity sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A==
+tiny-emitter@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423"
+ integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==
+
tiny-invariant@^1.0.2:
version "1.3.1"
resolved "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642"
@@ -21613,6 +21910,11 @@ typed-array-length@^1.0.4:
for-each "^0.3.3"
is-typed-array "^1.1.9"
+typed-function@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/typed-function/-/typed-function-4.1.0.tgz#da4bdd8a6d19a89e22732f75e4a410860aaf9712"
+ integrity sha512-DGwUl6cioBW5gw2L+6SMupGwH/kZOqivy17E4nsh1JI9fKF87orMmlQx3KISQPmg3sfnOUGlwVkroosvgddrlg==
+
typedarray-to-buffer@^3.1.5:
version "3.1.5"
resolved "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080"
@@ -22088,7 +22390,7 @@ uuid@^8.3.2:
resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
-uuid@^9.0.0:
+uuid@^9.0, uuid@^9.0.0:
version "9.0.0"
resolved "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5"
integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==
@@ -22282,6 +22584,11 @@ web-streams-polyfill@^3.0.3:
resolved "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6"
integrity sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==
+web-worker@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/web-worker/-/web-worker-1.2.0.tgz#5d85a04a7fbc1e7db58f66595d7a3ac7c9c180da"
+ integrity sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA==
+
webgl-constants@^1.1.1:
version "1.1.1"
resolved "https://registry.npmjs.org/webgl-constants/-/webgl-constants-1.1.1.tgz#f9633ee87fea56647a60b9ce735cbdfb891c6855"
@@ -23079,6 +23386,11 @@ xml-name-validator@^3.0.0:
resolved "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==
+xml-utils@^1.0.2:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/xml-utils/-/xml-utils-1.3.0.tgz#f1043534e3ac3deda12ddab39f8442e16da98ebb"
+ integrity sha512-i4PIrX33Wd66dvwo4syicwlwmnr6wuvvn4f2ku9hA67C2Uk62Xubczuhct+Evnd12/DV71qKNeDdJwES8HX1RA==
+
xml@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5"
From c9291aa8bf0a7700034adde7fff9f6613895bcff Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Fri, 17 Mar 2023 17:24:09 -0400
Subject: [PATCH 11/51] demo config fix
---
platform/viewer/public/config/demo.js | 17 +++++++++++------
1 file changed, 11 insertions(+), 6 deletions(-)
diff --git a/platform/viewer/public/config/demo.js b/platform/viewer/public/config/demo.js
index e7dccb1012..c97558710e 100644
--- a/platform/viewer/public/config/demo.js
+++ b/platform/viewer/public/config/demo.js
@@ -1,25 +1,30 @@
window.config = {
routerBasename: '/',
+ modes: [],
extensions: [],
showStudyList: true,
// below flag is for performance reasons, but it might not work for all servers
omitQuotationForMultipartRequest: true,
showWarningMessageForCrossOrigin: true,
showCPUFallbackMessage: true,
- servers: {
- dicomWeb: [
- {
+ dataSources: [
+ {
+ friendlyName: 'DCM4CHEE Server',
+ namespace: '@ohif/extension-default.dataSourcesModule.dicomweb',
+ sourceName: 'dicomweb',
+ configuration: {
name: 'DCM4CHEE',
wadoUriRoot: 'https://domvja9iplmyu.cloudfront.net/dicomweb',
qidoRoot: 'https://domvja9iplmyu.cloudfront.net/dicomweb',
wadoRoot: 'https://domvja9iplmyu.cloudfront.net/dicomweb',
qidoSupportsIncludeField: true,
imageRendering: 'wadors',
- thumbnailRendering: 'wadors',
enableStudyLazyLoad: true,
+ thumbnailRendering: 'wadors',
},
- ],
- },
+ },
+ ],
+ defaultDataSourceName: 'dicomweb',
hotkeys: [
{
commandName: 'incrementActiveViewport',
From 5dceb8ff00b9b36faacd3a88feae214d54a0ae98 Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Fri, 17 Mar 2023 17:40:22 -0400
Subject: [PATCH 12/51] fix logs
---
.../src/DicomMicroscopyViewport.tsx | 4 +--
extensions/dicom-microscopy/src/index.tsx | 1 -
.../src/utils/getSourceDisplaySet.js | 27 ++++++++++++-------
3 files changed, 19 insertions(+), 13 deletions(-)
diff --git a/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
index 0f6403e96b..97bf66746d 100644
--- a/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
+++ b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
@@ -21,7 +21,7 @@ class DicomMicroscopyViewport extends Component {
overlayElement = React.createRef();
debouncedResize: () => any;
- constructor(props) {
+ constructor(props: any) {
super(props);
this.debouncedResize = debounce(() => {
@@ -93,7 +93,7 @@ class DicomMicroscopyViewport extends Component {
);
};
- console.log('Loading viewer metadata', displaySet);
+ console.debug('Loading viewer metadata', displaySet);
loadViewer(displaySet.others);
}
diff --git a/extensions/dicom-microscopy/src/index.tsx b/extensions/dicom-microscopy/src/index.tsx
index f9c4136657..2f3b7b50e9 100644
--- a/extensions/dicom-microscopy/src/index.tsx
+++ b/extensions/dicom-microscopy/src/index.tsx
@@ -38,7 +38,6 @@ export default {
getViewportModule({ servicesManager, extensionManager }) {
const ExtendedMicroscopyViewport = (props) => {
- console.log('Creating an extended microscopy viewport');
const {
displaySets,
viewportIndex,
diff --git a/extensions/dicom-microscopy/src/utils/getSourceDisplaySet.js b/extensions/dicom-microscopy/src/utils/getSourceDisplaySet.js
index 207dc1dd7c..0052f772c3 100644
--- a/extensions/dicom-microscopy/src/utils/getSourceDisplaySet.js
+++ b/extensions/dicom-microscopy/src/utils/getSourceDisplaySet.js
@@ -1,11 +1,14 @@
/**
* Get referenced SM displaySet from SR displaySet
- *
- * @param {*} allDisplaySets
- * @param {*} microscopySRDisplaySet
- * @returns
+ *
+ * @param {*} allDisplaySets
+ * @param {*} microscopySRDisplaySet
+ * @returns
*/
-export default function getSourceDisplaySet(allDisplaySets, microscopySRDisplaySet) {
+export default function getSourceDisplaySet(
+ allDisplaySets,
+ microscopySRDisplaySet
+) {
const { ReferencedFrameOfReferenceUID } = microscopySRDisplaySet;
const otherDisplaySets = allDisplaySets.filter(
@@ -15,17 +18,21 @@ export default function getSourceDisplaySet(allDisplaySets, microscopySRDisplayS
const referencedDisplaySet = otherDisplaySets.find(
displaySet =>
displaySet.Modality === 'SM' &&
- (displaySet.FrameOfReferenceUID === ReferencedFrameOfReferenceUID
+ (displaySet.FrameOfReferenceUID === ReferencedFrameOfReferenceUID ||
// sometimes each depth instance has the different FrameOfReferenceID
- || displaySet.othersFrameOfReferenceUID.includes(ReferencedFrameOfReferenceUID))
+ displaySet.othersFrameOfReferenceUID.includes(
+ ReferencedFrameOfReferenceUID
+ ))
);
if (!referencedDisplaySet && otherDisplaySets.length == 1) {
- console.log('No display set with FrameOfReferenceUID',
+ console.warn(
+ 'No display set with FrameOfReferenceUID',
ReferencedFrameOfReferenceUID,
- 'single series, assuming data error, defaulting to only series.')
+ 'single series, assuming data error, defaulting to only series.'
+ );
return otherDisplaySets[0];
}
return referencedDisplaySet;
-};
+}
From f867837e75e49348617e54d6d8e4dc688db9984c Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Fri, 17 Mar 2023 17:43:33 -0400
Subject: [PATCH 13/51] remove unsed imports
---
extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
index 97bf66746d..270b811bb3 100644
--- a/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
+++ b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
@@ -1,10 +1,9 @@
-import React, { Component, useCallback } from 'react';
+import React, { Component } from 'react';
import ReactResizeDetector from 'react-resize-detector';
import PropTypes from 'prop-types';
import debounce from 'lodash.debounce';
import microscopyManager from './tools/microscopyManager';
-// import ViewportOverlay from './components/ViewportOverlay';
import './DicomMicroscopyViewport.css';
import ViewportOverlay from './components/ViewportOverlay';
import getDicomWebClient from './utils/dicomWebClient';
From 6d0d839532ab6097e32280a26236ed84b6562489 Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Tue, 21 Mar 2023 17:47:31 -0400
Subject: [PATCH 14/51] [fix] loading of microscopy
---
extensions/dicom-microscopy/package.json | 2 +-
.../src/DicomMicroscopySopClassHandler.js | 22 +++++
.../src/DicomMicroscopyViewport.tsx | 96 +++++++++++++++++--
.../src/utils/cleanDenaturalizedDataset.ts | 52 ++++++++++
.../src/utils/deepCopyAndClean.ts | 29 ++++++
yarn.lock | 8 +-
6 files changed, 195 insertions(+), 14 deletions(-)
create mode 100644 extensions/dicom-microscopy/src/utils/cleanDenaturalizedDataset.ts
create mode 100644 extensions/dicom-microscopy/src/utils/deepCopyAndClean.ts
diff --git a/extensions/dicom-microscopy/package.json b/extensions/dicom-microscopy/package.json
index c06a70065f..3b813a5ccb 100644
--- a/extensions/dicom-microscopy/package.json
+++ b/extensions/dicom-microscopy/package.json
@@ -47,7 +47,7 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^0.0.7",
"@cornerstonejs/codec-openjpeg": "^0.1.1",
"colormap": "^2.3",
- "dicom-microscopy-viewer": "^0.44.0",
+ "dicom-microscopy-viewer": "^0.43.2",
"dicomicc": "^0.1"
},
"devDependencies": {
diff --git a/extensions/dicom-microscopy/src/DicomMicroscopySopClassHandler.js b/extensions/dicom-microscopy/src/DicomMicroscopySopClassHandler.js
index fbfa09075d..ab3441f8fa 100644
--- a/extensions/dicom-microscopy/src/DicomMicroscopySopClassHandler.js
+++ b/extensions/dicom-microscopy/src/DicomMicroscopySopClassHandler.js
@@ -59,10 +59,32 @@ function _getDisplaySetsFromSeries(
SOPClassUID,
} = instance;
+ instances = instances.map(inst => {
+ // NOTE: According to DICOM standard a series should have a FrameOfReferenceUID
+ // When the Microscopy file was built by certain tool from multiple image files,
+ // each instance's FrameOfReferenceUID is sometimes different.
+ // Let's override this value manuall here.
+ // https://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.7.4.html#sect_C.7.4.1.1.1
+
+ inst.FrameOfReferenceUID = instance.FrameOfReferenceUID;
+
+ // if (inst.ContainerIdentifier === null) {
+ // // ContainerIdentifier must not be null
+ // inst.ContainerIdentifier = '0';
+ // }
+ return inst;
+ });
+
const othersFrameOfReferenceUID = instances
.filter(v => v)
.map(inst => inst.FrameOfReferenceUID)
.filter((value, index, array) => array.indexOf(value) === index);
+ if (othersFrameOfReferenceUID.length > 1) {
+ console.warn(
+ 'Expected FrameOfReferenceUID of difference instances within a series to be the same, found multiple different values',
+ othersFrameOfReferenceUID
+ );
+ }
const displaySet = {
plugin: 'microscopy',
diff --git a/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
index 270b811bb3..f876a80413 100644
--- a/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
+++ b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
@@ -7,6 +7,10 @@ import microscopyManager from './tools/microscopyManager';
import './DicomMicroscopyViewport.css';
import ViewportOverlay from './components/ViewportOverlay';
import getDicomWebClient from './utils/dicomWebClient';
+import dcmjs from 'dcmjs';
+import deepCopyAndClean from './utils/deepCopyAndClean';
+import { DicomMetadataStore } from '@ohif/core';
+import cleanDenaturalizedDataset from './utils/cleanDenaturalizedDataset';
class DicomMicroscopyViewport extends Component {
state = {
@@ -49,19 +53,81 @@ class DicomMicroscopyViewport extends Component {
// install the microscopy renderer into the web page.
// you should only do this once.
installOpenLayersRenderer(container, displaySet) {
- const loadViewer = async (metadata) => {
- metadata = metadata.filter((m) => m);
-
- const { viewer: DicomMicroscopyViewer } = await import(
+ const loadViewer = async metadata => {
+ const {
+ viewer: DicomMicroscopyViewer,
+ metadata: metadataUtils,
+ } = await import(
/* webpackChunkName: "dicom-microscopy-viewer" */ 'dicom-microscopy-viewer'
);
const microscopyViewer = DicomMicroscopyViewer.VolumeImageViewer;
const client = getDicomWebClient();
+ // Parse, format, and filter metadata
+ const volumeImages: any[] = [];
+
+ // const retrieveOptions = {
+ // studyInstanceUID: metadata[0].StudyInstanceUID,
+ // seriesInstanceUID: metadata[0].SeriesInstanceUID,
+ // };
+ // metadata = await client.retrieveSeriesMetadata(retrieveOptions);
+ // // Parse, format, and filter metadata
+ // metadata.forEach(m => {
+ // if (
+ // volumeImages.length > 0 &&
+ // m['00200052'].Value[0] != volumeImages[0].FrameOfReferenceUID
+ // ) {
+ // console.warn(
+ // 'Expected FrameOfReferenceUID of difference instances within a series to be the same, found multiple different values',
+ // m['00200052'].Value[0]
+ // );
+ // m['00200052'].Value[0] = volumeImages[0].FrameOfReferenceUID;
+ // }
+
+ // const image = new metadataUtils.VLWholeSlideMicroscopyImage({
+ // metadata: m,
+ // });
+ // const imageFlavor = image.ImageType[2];
+ // if (imageFlavor === 'VOLUME' || imageFlavor === 'THUMBNAIL') {
+ // volumeImages.push(image);
+ // }
+ // });
+
+ metadata.forEach(m => {
+ const inst = cleanDenaturalizedDataset(
+ dcmjs.data.DicomMetaDictionary.denaturalizeDataset(m)
+ );
+ if (!inst['00480105']) {
+ // Optical Path Sequence
+ // no OpticalPathIdentifier?
+ inst['00480105'] = {
+ vr: 'SQ',
+ Value: [
+ {
+ '00480106': {
+ vr: 'SH',
+ Value: ['1'],
+ },
+ },
+ ],
+ };
+ }
+ console.log(inst);
+ const image = new metadataUtils.VLWholeSlideMicroscopyImage({
+ metadata: inst,
+ });
+ const imageFlavor = image.ImageType[2];
+ if (imageFlavor === 'VOLUME' || imageFlavor === 'THUMBNAIL') {
+ volumeImages.push(image);
+ }
+ });
+ console.log(volumeImages);
+
+ // format metadata for microscopy-viewer
const options = {
client,
- metadata,
+ metadata: volumeImages,
retrieveRendered: false,
controls: ['overview'],
};
@@ -75,6 +141,8 @@ class DicomMicroscopyViewport extends Component {
) {
this.viewer.addViewportOverlay({
element: this.overlayElement.current,
+ coordinates: [0, 0], // TODO: dicom-microscopy-viewer documentation says this can be false to be automatically, but it is not.
+ navigate: true,
className: 'OpenLayersOverlay',
});
}
@@ -102,8 +170,15 @@ class DicomMicroscopyViewport extends Component {
this.installOpenLayersRenderer(this.container.current, displaySet);
}
- componentDidUpdate(prevProps: Readonly<{}>, prevState: Readonly<{}>, snapshot?: any): void {
- if (this.managedViewer && prevProps.displaySets !== this.props.displaySets) {
+ componentDidUpdate(
+ prevProps: Readonly<{}>,
+ prevState: Readonly<{}>,
+ snapshot?: any
+ ): void {
+ if (
+ this.managedViewer &&
+ prevProps.displaySets !== this.props.displaySets
+ ) {
const { displaySets } = this.props;
const displaySet = displaySets[0];
@@ -122,8 +197,11 @@ class DicomMicroscopyViewport extends Component {
}
setViewportActiveHandler = () => {
- const { setViewportActive, viewportIndex, activeViewportIndex } =
- this.props;
+ const {
+ setViewportActive,
+ viewportIndex,
+ activeViewportIndex,
+ } = this.props;
if (viewportIndex !== activeViewportIndex) {
setViewportActive(viewportIndex);
diff --git a/extensions/dicom-microscopy/src/utils/cleanDenaturalizedDataset.ts b/extensions/dicom-microscopy/src/utils/cleanDenaturalizedDataset.ts
new file mode 100644
index 0000000000..a09ac29ea6
--- /dev/null
+++ b/extensions/dicom-microscopy/src/utils/cleanDenaturalizedDataset.ts
@@ -0,0 +1,52 @@
+function isPrimitive(v: any) {
+ return !(typeof v == 'object' || Array.isArray(v));
+}
+
+const vrNumerics = [
+ 'DS',
+ 'FL',
+ 'FD',
+ 'OB',
+ 'OD',
+ 'OF',
+ 'OL',
+ 'OV',
+ 'SL',
+ 'SS',
+ 'SV',
+ 'UL',
+ 'US',
+ 'UV',
+];
+
+/**
+ * Specialized for DICOM JSON format dataset cleaning.
+ * @param obj
+ * @returns
+ */
+export default function cleanDenaturalizedDataset(obj: any): any {
+ if (Array.isArray(obj)) {
+ const newAry = obj.map(o =>
+ isPrimitive(o) ? o : cleanDenaturalizedDataset(o)
+ );
+ return newAry;
+ } else if (isPrimitive(obj)) {
+ return obj;
+ } else {
+ Object.keys(obj).forEach(key => {
+ if (obj[key].Value === null) {
+ delete obj[key].Value;
+ } else if (Array.isArray(obj[key].Value)) {
+ if (obj[key].Value.length === 1 && obj[key].Value[0].BulkDataURI) {
+ obj[key].BulkDataURI = obj[key].Value[0].BulkDataURI;
+ delete obj[key].Value;
+ } else if (vrNumerics.includes(obj[key].vr)) {
+ obj[key].Value = obj[key].Value.map(v => new Number(v));
+ } else {
+ obj[key].Value.forEach(cleanDenaturalizedDataset);
+ }
+ }
+ });
+ return obj;
+ }
+}
diff --git a/extensions/dicom-microscopy/src/utils/deepCopyAndClean.ts b/extensions/dicom-microscopy/src/utils/deepCopyAndClean.ts
new file mode 100644
index 0000000000..2d1bae1cbb
--- /dev/null
+++ b/extensions/dicom-microscopy/src/utils/deepCopyAndClean.ts
@@ -0,0 +1,29 @@
+function isPrimitive(v: any) {
+ return !(typeof v == 'object' || Array.isArray(v) || typeof v === 'function');
+}
+
+/**
+ * Specialized for DICOM JSON format dataset cleaning.
+ * @param obj
+ * @returns
+ */
+export default function deepCopyAndClean(obj: any): any {
+ if (Array.isArray(obj)) {
+ const newAry = obj.map(o => (isPrimitive(o) ? o : deepCopyAndClean(o)));
+ return newAry;
+ } else {
+ const newObj = {} as any;
+ Object.keys(obj).forEach(key => {
+ if (obj[key] === null) {
+ // use empty array instead of null, especially the dicom-microscopy-viewer's
+ // metadata handler has some bug around null value
+ newObj[key] = [];
+ } else if (isPrimitive(obj[key])) {
+ newObj[key] = obj[key];
+ } else {
+ newObj[key] = deepCopyAndClean(obj[key]);
+ }
+ });
+ return newObj;
+ }
+}
diff --git a/yarn.lock b/yarn.lock
index 13a8ddb493..cdc9e215b9 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -9742,10 +9742,10 @@ dezalgo@^1.0.0:
asap "^2.0.0"
wrappy "1"
-dicom-microscopy-viewer@^0.44.0:
- version "0.44.0"
- resolved "https://registry.yarnpkg.com/dicom-microscopy-viewer/-/dicom-microscopy-viewer-0.44.0.tgz#d4a9e985acb23c5b82a9aedbe379b39198b8fd55"
- integrity sha512-7rcm8bXTcOLsXrhBPwWe5gf4Sj1Rz1lRh+ECcaznOEr/uvX6BTAYLqNJNmeAUMoKcadboEeDrmLihCwCteRSJQ==
+dicom-microscopy-viewer@^0.43.2:
+ version "0.43.2"
+ resolved "https://registry.yarnpkg.com/dicom-microscopy-viewer/-/dicom-microscopy-viewer-0.43.2.tgz#e789093f6c8173bf4082c7c1db320b1a5e9c0b31"
+ integrity sha512-eDFiumje9bGb3vv2ar2OPZajJk9oDJvmXmKrjr+E0maOg8zrvFj9wAp4eIn65vQMwxFvxZ1nR/AuHZ/fdp8vFA==
dependencies:
"@cornerstonejs/codec-charls" "^0.1.1"
"@cornerstonejs/codec-libjpeg-turbo-8bit" "^0.0.7"
From 7c2a01dab0eccdddb7e4ededa64bdec177fc2c3b Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Fri, 24 Mar 2023 09:38:52 -0400
Subject: [PATCH 15/51] [fix] webworker script loading, normalizing
denaturalized dataset
---
.webpack/rules/loadWebWorkers.js | 4 ++--
.../dicom-microscopy/src/DicomMicroscopyViewport.tsx | 2 --
.../src/utils/cleanDenaturalizedDataset.ts | 10 +++++-----
3 files changed, 7 insertions(+), 9 deletions(-)
diff --git a/.webpack/rules/loadWebWorkers.js b/.webpack/rules/loadWebWorkers.js
index a4ab284159..2e7f111931 100644
--- a/.webpack/rules/loadWebWorkers.js
+++ b/.webpack/rules/loadWebWorkers.js
@@ -5,11 +5,11 @@
*/
const loadWebWorkers = {
test: /\.worker\.js$/,
- include: /vtk\.js[\/\\]Sources/,
+ include: /vtk\.js[\/\\]Sources|dicom-microscopy-viewer[\/\\]/,
use: [
{
loader: 'worker-loader',
- options: { inline: true, fallback: false },
+ options: { inline: 'no-fallback' },
},
],
};
diff --git a/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
index f876a80413..5609f96732 100644
--- a/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
+++ b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
@@ -113,7 +113,6 @@ class DicomMicroscopyViewport extends Component {
],
};
}
- console.log(inst);
const image = new metadataUtils.VLWholeSlideMicroscopyImage({
metadata: inst,
});
@@ -122,7 +121,6 @@ class DicomMicroscopyViewport extends Component {
volumeImages.push(image);
}
});
- console.log(volumeImages);
// format metadata for microscopy-viewer
const options = {
diff --git a/extensions/dicom-microscopy/src/utils/cleanDenaturalizedDataset.ts b/extensions/dicom-microscopy/src/utils/cleanDenaturalizedDataset.ts
index a09ac29ea6..dd44c0487d 100644
--- a/extensions/dicom-microscopy/src/utils/cleanDenaturalizedDataset.ts
+++ b/extensions/dicom-microscopy/src/utils/cleanDenaturalizedDataset.ts
@@ -6,7 +6,7 @@ const vrNumerics = [
'DS',
'FL',
'FD',
- 'OB',
+ 'IS',
'OD',
'OF',
'OL',
@@ -34,16 +34,16 @@ export default function cleanDenaturalizedDataset(obj: any): any {
return obj;
} else {
Object.keys(obj).forEach(key => {
- if (obj[key].Value === null) {
+ if (obj[key].Value === null && obj[key].vr) {
delete obj[key].Value;
- } else if (Array.isArray(obj[key].Value)) {
+ } else if (Array.isArray(obj[key].Value) && obj[key].vr) {
if (obj[key].Value.length === 1 && obj[key].Value[0].BulkDataURI) {
obj[key].BulkDataURI = obj[key].Value[0].BulkDataURI;
delete obj[key].Value;
} else if (vrNumerics.includes(obj[key].vr)) {
- obj[key].Value = obj[key].Value.map(v => new Number(v));
+ obj[key].Value = obj[key].Value.map(v => +v);
} else {
- obj[key].Value.forEach(cleanDenaturalizedDataset);
+ obj[key].Value = obj[key].Value.map(cleanDenaturalizedDataset);
}
}
});
From 17a8a9e8ddd711b0adccd9b9216cbac44e801a42 Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Fri, 24 Mar 2023 10:09:38 -0400
Subject: [PATCH 16/51] found the latest version of dicom-microscopy-viewer
that works with current OHIF extension : 0.35.2
---
extensions/dicom-microscopy/package.json | 2 +-
yarn.lock | 122 +++++++++++------------
2 files changed, 58 insertions(+), 66 deletions(-)
diff --git a/extensions/dicom-microscopy/package.json b/extensions/dicom-microscopy/package.json
index 3b813a5ccb..f4aa77b29d 100644
--- a/extensions/dicom-microscopy/package.json
+++ b/extensions/dicom-microscopy/package.json
@@ -47,7 +47,7 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^0.0.7",
"@cornerstonejs/codec-openjpeg": "^0.1.1",
"colormap": "^2.3",
- "dicom-microscopy-viewer": "^0.43.2",
+ "dicom-microscopy-viewer": "^0.35.0",
"dicomicc": "^0.1"
},
"devDependencies": {
diff --git a/yarn.lock b/yarn.lock
index cdc9e215b9..d1e1548639 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1154,6 +1154,14 @@
"@babel/helper-create-regexp-features-plugin" "^7.18.6"
"@babel/helper-plugin-utils" "^7.18.6"
+"@babel/polyfill@^7.8.3":
+ version "7.12.1"
+ resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.12.1.tgz#1f2d6371d1261bbd961f3c5d5909150e12d0bd96"
+ integrity sha512-X0pi0V6gxLi6lFZpGmeNa4zxtwEmCs42isWLNjZZDE0Y8yVfgu0T2OAHlzBbdYlqbW/YXVvoBHpATEM+goCj8g==
+ dependencies:
+ core-js "^2.6.5"
+ regenerator-runtime "^0.13.4"
+
"@babel/preset-env@^7.11.0", "@babel/preset-env@^7.12.1", "@babel/preset-env@^7.12.11", "@babel/preset-env@^7.16.11", "@babel/preset-env@^7.18.6", "@babel/preset-env@^7.19.4", "@babel/preset-env@^7.5.0":
version "7.20.2"
resolved "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.20.2.tgz#9b1642aa47bb9f43a86f9630011780dab7f86506"
@@ -1308,7 +1316,7 @@
core-js-pure "^3.25.1"
regenerator-runtime "^0.13.11"
-"@babel/runtime@7.17.9", "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.6", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.6", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
+"@babel/runtime@7.17.9", "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.6", "@babel/runtime@^7.15.4", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.6", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.7", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
version "7.20.13"
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz#7055ab8a7cff2b8f6058bf6ae45ff84ad2aded4b"
integrity sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==
@@ -8250,7 +8258,7 @@ compare-func@^2.0.0:
array-ify "^1.0.0"
dot-prop "^5.1.0"
-complex.js@^2.1.1:
+complex.js@^2.0.15:
version "2.1.1"
resolved "https://registry.yarnpkg.com/complex.js/-/complex.js-2.1.1.tgz#0675dac8e464ec431fb2ab7d30f41d889fb25c31"
integrity sha512-8njCHOTtFFLtegk6zQo0kkVX1rngygb/KQI6z1qZxlFI3scluC+LVTCFbrkWjBv4vvLlbQ9t88IPMC6k95VTTg==
@@ -8580,7 +8588,7 @@ core-js-pure@^3.23.3, core-js-pure@^3.25.1:
resolved "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.28.0.tgz#4ef2888475b6c856ef6f5aeef8b4f618b76ad048"
integrity sha512-DSOVleA9/v3LNj/vFxAPfUHttKTzrB2RXhAPvR5TPXn4vrra3Z2ssytvRyt8eruJwAfwAiFADEbrjcRdcvPLQQ==
-core-js@^2.4.1, core-js@^2.5.7, core-js@^2.6.12:
+core-js@^2.4.1, core-js@^2.5.7, core-js@^2.6.12, core-js@^2.6.5:
version "2.6.12"
resolved "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec"
integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
@@ -9346,12 +9354,13 @@ dayjs@^1.10.4:
resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz#4b296922642f70999544d1144a2c25730fce63e2"
integrity sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==
-dcmjs@^0.27:
- version "0.27.0"
- resolved "https://registry.yarnpkg.com/dcmjs/-/dcmjs-0.27.0.tgz#2662818c8b20494e366583e6dd3577c20d04d6ff"
- integrity sha512-26wtatOLh+0b0aFy9iOg7PdOLG9EHevn9nEOn7Aoo5l7P9aFAMZ3XAa9Q+NULzLE2Q7DcIf2TQvfyVtdzhQzeg==
+dcmjs@^0.18:
+ version "0.18.11"
+ resolved "https://registry.yarnpkg.com/dcmjs/-/dcmjs-0.18.11.tgz#724cab26148503017d97bbcbb8fe9685dfae69dc"
+ integrity sha512-7C+tKWIzeo2ZzL1Fss+3HuztWyhZYWhQGXV5hrK7yi4s8ctIN0lCeTUNYPdRS1DyE2GEAI7gb9fDcQU9Wh1rgg==
dependencies:
- "@babel/runtime-corejs2" "^7.17.8"
+ "@babel/polyfill" "^7.8.3"
+ "@babel/runtime" "^7.8.4"
gl-matrix "^3.1.0"
lodash.clonedeep "^4.5.0"
loglevelnext "^3.0.1"
@@ -9420,7 +9429,7 @@ decamelize@^1.1.0, decamelize@^1.1.2, decamelize@^1.2.0:
resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==
-decimal.js@^10.4.3:
+decimal.js@^10.3.1:
version "10.4.3"
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23"
integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==
@@ -9742,22 +9751,16 @@ dezalgo@^1.0.0:
asap "^2.0.0"
wrappy "1"
-dicom-microscopy-viewer@^0.43.2:
- version "0.43.2"
- resolved "https://registry.yarnpkg.com/dicom-microscopy-viewer/-/dicom-microscopy-viewer-0.43.2.tgz#e789093f6c8173bf4082c7c1db320b1a5e9c0b31"
- integrity sha512-eDFiumje9bGb3vv2ar2OPZajJk9oDJvmXmKrjr+E0maOg8zrvFj9wAp4eIn65vQMwxFvxZ1nR/AuHZ/fdp8vFA==
- dependencies:
- "@cornerstonejs/codec-charls" "^0.1.1"
- "@cornerstonejs/codec-libjpeg-turbo-8bit" "^0.0.7"
- "@cornerstonejs/codec-openjpeg" "^0.1.1"
- colormap "^2.3"
- dcmjs "^0.27"
- dicomicc "^0.1"
+dicom-microscopy-viewer@^0.35.0:
+ version "0.35.2"
+ resolved "https://registry.yarnpkg.com/dicom-microscopy-viewer/-/dicom-microscopy-viewer-0.35.2.tgz#437a5a5b28e0c886931adfc29a296e4eb5d400f4"
+ integrity sha512-v+OTaOwG8oB+fMBmJqUSuhjYsHftmKZY8W06MGR5loLjb0xUZSS1fnTsMY6FcP+er9MgDamSG5MwKBid0OFkEA==
+ dependencies:
+ dcmjs "^0.18"
dicomweb-client "^0.8"
image-type "^4.1"
- mathjs "^11.2"
- ol "^7.1"
- uuid "^9.0"
+ mathjs "^9.4"
+ ol "^6.9"
dicom-parser@^1.8.9:
version "1.8.20"
@@ -10091,11 +10094,6 @@ duplexify@^3.4.2, duplexify@^3.6.0:
readable-stream "^2.0.0"
stream-shift "^1.0.0"
-earcut@^2.2.3:
- version "2.2.4"
- resolved "https://registry.yarnpkg.com/earcut/-/earcut-2.2.4.tgz#6d02fd4d68160c114825d06890a92ecaae60343a"
- integrity sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==
-
eastasianwidth@^0.2.0:
version "0.2.0"
resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
@@ -11650,7 +11648,7 @@ forwarded@0.2.0:
resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==
-fraction.js@^4.2.0:
+fraction.js@^4.1.1, fraction.js@^4.2.0:
version "4.2.0"
resolved "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950"
integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==
@@ -11824,16 +11822,16 @@ gensync@^1.0.0-beta.1, gensync@^1.0.0-beta.2:
resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
-geotiff@^2.0.7:
- version "2.0.7"
- resolved "https://registry.yarnpkg.com/geotiff/-/geotiff-2.0.7.tgz#358e578233af70bfb0b4dee62d599ad78fc5cfca"
- integrity sha512-FKvFTNowMU5K6lHYY2f83d4lS2rsCNdpUC28AX61x9ZzzqPNaWFElWv93xj0eJFaNyOYA63ic5OzJ88dHpoA5Q==
+geotiff@2.0.4:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/geotiff/-/geotiff-2.0.4.tgz#d6f231fdd76186aba21c61823ed759fcbf5d4f86"
+ integrity sha512-aG8h9bJccGusioPsEWsEqx8qdXpZN71A20WCvRKGxcnHSOWLKmC5ZmsAmodfxb9TRQvs+89KikGuPzxchhA+Uw==
dependencies:
"@petamoriken/float16" "^3.4.7"
lerc "^3.0.0"
+ lru-cache "^6.0.0"
pako "^2.0.4"
parse-headers "^2.0.2"
- quick-lru "^6.1.1"
web-worker "^1.2.0"
xml-utils "^1.0.2"
@@ -15275,20 +15273,20 @@ material-colors@^1.2.1:
resolved "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz#6d1958871126992ceecc72f4bcc4d8f010865f46"
integrity sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==
-mathjs@^11.2:
- version "11.7.0"
- resolved "https://registry.yarnpkg.com/mathjs/-/mathjs-11.7.0.tgz#a197e82c760c57744b20324a8c4eed677bddf179"
- integrity sha512-RCXtrP5xGIbl9PUc5+7QL81rBCUjzoIZ0ugNqKsarOUxg+x7deY0BzfNai+bGfUL/T+1uYq1xs5w2xVdL3lp0g==
+mathjs@^9.4:
+ version "9.5.2"
+ resolved "https://registry.yarnpkg.com/mathjs/-/mathjs-9.5.2.tgz#e0f3279320dc6f49e45d99c4fcdd8b52231f0462"
+ integrity sha512-c0erTq0GP503/Ch2OtDOAn50GIOsuxTMjmE00NI/vKJFSWrDaQHRjx6ai+16xYv70yBSnnpUgHZGNf9FR9IwmA==
dependencies:
- "@babel/runtime" "^7.21.0"
- complex.js "^2.1.1"
- decimal.js "^10.4.3"
+ "@babel/runtime" "^7.15.4"
+ complex.js "^2.0.15"
+ decimal.js "^10.3.1"
escape-latex "^1.2.0"
- fraction.js "^4.2.0"
+ fraction.js "^4.1.1"
javascript-natural-sort "^0.7.1"
seedrandom "^3.0.5"
tiny-emitter "^2.1.0"
- typed-function "^4.1.0"
+ typed-function "^2.0.0"
md5.js@^1.3.4:
version "1.3.5"
@@ -16460,22 +16458,21 @@ oidc-client@1.11.5:
crypto-js "^4.0.0"
serialize-javascript "^4.0.0"
-ol-mapbox-style@^9.2.0:
- version "9.7.0"
- resolved "https://registry.yarnpkg.com/ol-mapbox-style/-/ol-mapbox-style-9.7.0.tgz#38a4f7abc8f0a94f378dcdb7cefdcc69ca3f6287"
- integrity sha512-YX3u8FBJHsRHaoGxmd724Mp5WPTuV7wLQW6zZhcihMuInsSdCX1EiZfU+8IAL7jG0pbgl5YgC0aWE/MXJcUXxg==
+ol-mapbox-style@^8.0.5:
+ version "8.2.1"
+ resolved "https://registry.yarnpkg.com/ol-mapbox-style/-/ol-mapbox-style-8.2.1.tgz#0f0c252b6495853a137d7e4dd3f915fab664b356"
+ integrity sha512-3kBBuZC627vDL8vnUdfVbCbfkhkcZj2kXPHQcuLhC4JJEA+XkEVEtEde8x8+AZctRbHwBkSiubTPaRukgLxIRw==
dependencies:
"@mapbox/mapbox-gl-style-spec" "^13.23.1"
mapbox-to-css-font "^2.4.1"
-ol@^7.1:
- version "7.3.0"
- resolved "https://registry.yarnpkg.com/ol/-/ol-7.3.0.tgz#7ffb5f258dafa4a3e218208aad9054d61f6fe786"
- integrity sha512-08vJE4xITKPazQ9qJjeqYjRngnM9s+1eSv219Pdlrjj3LpLqjEH386ncq+76Dw1oGPGR8eLVEePk7FEd9XqqMw==
+ol@^6.9:
+ version "6.15.1"
+ resolved "https://registry.yarnpkg.com/ol/-/ol-6.15.1.tgz#364f459939ef71f970b2376a821a896529f65e3a"
+ integrity sha512-ZG2CKTpJ8Q+tPywYysVwPk+yevwJzlbwjRKhoCvd7kLVWMbfBl1O/+Kg/yrZZrhG9FNXbFH4GeOZ5yVRqo3P4w==
dependencies:
- earcut "^2.2.3"
- geotiff "^2.0.7"
- ol-mapbox-style "^9.2.0"
+ geotiff "2.0.4"
+ ol-mapbox-style "^8.0.5"
pbf "3.2.1"
rbush "^3.0.1"
@@ -18595,11 +18592,6 @@ quick-lru@^5.1.1:
resolved "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
-quick-lru@^6.1.1:
- version "6.1.1"
- resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-6.1.1.tgz#f8e5bf9010376c126c80c1a62827a526c0e60adf"
- integrity sha512-S27GBT+F0NTRiehtbrgaSE1idUAJ5bX8dPAQTdylEyNlrdcH5X4Lz7Edz3DYzecbsCluD5zO8ZNEe04z3D3u6Q==
-
quickselect@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/quickselect/-/quickselect-2.0.0.tgz#f19680a486a5eefb581303e023e98faaf25dd018"
@@ -19378,7 +19370,7 @@ regenerate@^1.4.2:
resolved "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a"
integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==
-regenerator-runtime@^0.13.11, regenerator-runtime@^0.13.7:
+regenerator-runtime@^0.13.11, regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7:
version "0.13.11"
resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==
@@ -21910,10 +21902,10 @@ typed-array-length@^1.0.4:
for-each "^0.3.3"
is-typed-array "^1.1.9"
-typed-function@^4.1.0:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/typed-function/-/typed-function-4.1.0.tgz#da4bdd8a6d19a89e22732f75e4a410860aaf9712"
- integrity sha512-DGwUl6cioBW5gw2L+6SMupGwH/kZOqivy17E4nsh1JI9fKF87orMmlQx3KISQPmg3sfnOUGlwVkroosvgddrlg==
+typed-function@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/typed-function/-/typed-function-2.1.0.tgz#ded6f8a442ba8749ff3fe75bc41419c8d46ccc3f"
+ integrity sha512-bctQIOqx2iVbWGDGPWwIm18QScpu2XRmkC19D8rQGFsjKSgteq/o1hTZvIG/wuDq8fanpBDrLkLq+aEN/6y5XQ==
typedarray-to-buffer@^3.1.5:
version "3.1.5"
@@ -22390,7 +22382,7 @@ uuid@^8.3.2:
resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
-uuid@^9.0, uuid@^9.0.0:
+uuid@^9.0.0:
version "9.0.0"
resolved "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5"
integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==
From f2d1f1de8263fef4cd480a9684d253d0d87f5e8b Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Fri, 24 Mar 2023 10:15:43 -0400
Subject: [PATCH 17/51] hide thumbnail pane by default, as we have issues with
---
modes/microscopy/src/index.tsx | 21 ++++++++++++---------
1 file changed, 12 insertions(+), 9 deletions(-)
diff --git a/modes/microscopy/src/index.tsx b/modes/microscopy/src/index.tsx
index 749e5ac040..9044ad6eb3 100644
--- a/modes/microscopy/src/index.tsx
+++ b/modes/microscopy/src/index.tsx
@@ -59,9 +59,7 @@ function modeFactory() {
},
onModeExit: ({ servicesManager }) => {
- const {
- toolbarService,
- } = servicesManager.services;
+ const { toolbarService } = servicesManager.services;
toolbarService.reset();
},
@@ -89,13 +87,18 @@ function modeFactory() {
id: ohif.layout,
props: {
leftPanels: [ohif.leftPanel],
- rightPanels: ['@ohif/extension-dicom-microscopy.panelModule.measure'],
+ leftPanelDefaultClosed: true, // we have problem with rendering thumbnails for microscopy images
+ rightPanelDefaultClosed: true, // we do not have the save microscopy measurements yet
+ rightPanels: [
+ '@ohif/extension-dicom-microscopy.panelModule.measure',
+ ],
viewports: [
{
- namespace: "@ohif/extension-dicom-microscopy.viewportModule.microscopy-dicom",
+ namespace:
+ '@ohif/extension-dicom-microscopy.viewportModule.microscopy-dicom',
displaySetsToDisplay: [
- "@ohif/extension-dicom-microscopy.sopClassHandlerModule.DicomMicroscopySopClassHandler",
- "@ohif/extension-dicom-microscopy.sopClassHandlerModule.DicomMicroscopySRSopClassHandler",
+ '@ohif/extension-dicom-microscopy.sopClassHandlerModule.DicomMicroscopySopClassHandler',
+ '@ohif/extension-dicom-microscopy.sopClassHandlerModule.DicomMicroscopySRSopClassHandler',
],
},
{
@@ -121,8 +124,8 @@ function modeFactory() {
// general handler needs to come last. For this case, the dicomvideo must
// come first to remove video transfer syntax before ohif uses images
sopClassHandlers: [
- "@ohif/extension-dicom-microscopy.sopClassHandlerModule.DicomMicroscopySopClassHandler",
- "@ohif/extension-dicom-microscopy.sopClassHandlerModule.DicomMicroscopySRSopClassHandler",
+ '@ohif/extension-dicom-microscopy.sopClassHandlerModule.DicomMicroscopySopClassHandler',
+ '@ohif/extension-dicom-microscopy.sopClassHandlerModule.DicomMicroscopySRSopClassHandler',
dicomvideo.sopClassHandler,
dicompdf.sopClassHandler,
],
From def4fb3b2a09e41eb0b1fbdc2342f05187c68905 Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Fri, 24 Mar 2023 10:28:46 -0400
Subject: [PATCH 18/51] comments
---
.../src/DicomMicroscopySopClassHandler.js | 4 +-
.../src/DicomMicroscopyViewport.tsx | 66 +++++++++++--------
.../src/utils/deepCopyAndClean.ts | 29 --------
3 files changed, 40 insertions(+), 59 deletions(-)
delete mode 100644 extensions/dicom-microscopy/src/utils/deepCopyAndClean.ts
diff --git a/extensions/dicom-microscopy/src/DicomMicroscopySopClassHandler.js b/extensions/dicom-microscopy/src/DicomMicroscopySopClassHandler.js
index ab3441f8fa..54ef918b86 100644
--- a/extensions/dicom-microscopy/src/DicomMicroscopySopClassHandler.js
+++ b/extensions/dicom-microscopy/src/DicomMicroscopySopClassHandler.js
@@ -63,7 +63,9 @@ function _getDisplaySetsFromSeries(
// NOTE: According to DICOM standard a series should have a FrameOfReferenceUID
// When the Microscopy file was built by certain tool from multiple image files,
// each instance's FrameOfReferenceUID is sometimes different.
- // Let's override this value manuall here.
+ // Even though this means the file was not well formatted DICOM VL Whole Slide Microscopy Image,
+ // the case is so often, so let's override this value manually here.
+ //
// https://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.7.4.html#sect_C.7.4.1.1.1
inst.FrameOfReferenceUID = instance.FrameOfReferenceUID;
diff --git a/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
index 5609f96732..ae35ca0df4 100644
--- a/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
+++ b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
@@ -8,8 +8,6 @@ import './DicomMicroscopyViewport.css';
import ViewportOverlay from './components/ViewportOverlay';
import getDicomWebClient from './utils/dicomWebClient';
import dcmjs from 'dcmjs';
-import deepCopyAndClean from './utils/deepCopyAndClean';
-import { DicomMetadataStore } from '@ohif/core';
import cleanDenaturalizedDataset from './utils/cleanDenaturalizedDataset';
class DicomMicroscopyViewport extends Component {
@@ -67,40 +65,50 @@ class DicomMicroscopyViewport extends Component {
// Parse, format, and filter metadata
const volumeImages: any[] = [];
- // const retrieveOptions = {
- // studyInstanceUID: metadata[0].StudyInstanceUID,
- // seriesInstanceUID: metadata[0].SeriesInstanceUID,
- // };
- // metadata = await client.retrieveSeriesMetadata(retrieveOptions);
- // // Parse, format, and filter metadata
- // metadata.forEach(m => {
- // if (
- // volumeImages.length > 0 &&
- // m['00200052'].Value[0] != volumeImages[0].FrameOfReferenceUID
- // ) {
- // console.warn(
- // 'Expected FrameOfReferenceUID of difference instances within a series to be the same, found multiple different values',
- // m['00200052'].Value[0]
- // );
- // m['00200052'].Value[0] = volumeImages[0].FrameOfReferenceUID;
- // }
-
- // const image = new metadataUtils.VLWholeSlideMicroscopyImage({
- // metadata: m,
+ /**
+ * This block of code is the original way of loading DICOM into dicom-microscopy-viewer
+ * as in their documentation.
+ * But we have the metadata already loaded by our loaders.
+ * As the metadata for microscopy DIOM files tends to be big and we don't
+ * want to double load it, below we have the mechanism to reconstruct the
+ * DICOM JSON structure (denaturalized) from naturalized metadata.
+ * (NOTE: Our loaders cache only naturalized metadata, not the denaturalized.)
+ */
+ // {
+ // const retrieveOptions = {
+ // studyInstanceUID: metadata[0].StudyInstanceUID,
+ // seriesInstanceUID: metadata[0].SeriesInstanceUID,
+ // };
+ // metadata = await client.retrieveSeriesMetadata(retrieveOptions);
+ // // Parse, format, and filter metadata
+ // metadata.forEach(m => {
+ // if (
+ // volumeImages.length > 0 &&
+ // m['00200052'].Value[0] != volumeImages[0].FrameOfReferenceUID
+ // ) {
+ // console.warn(
+ // 'Expected FrameOfReferenceUID of difference instances within a series to be the same, found multiple different values',
+ // m['00200052'].Value[0]
+ // );
+ // m['00200052'].Value[0] = volumeImages[0].FrameOfReferenceUID;
+ // }
+ // const image = new metadataUtils.VLWholeSlideMicroscopyImage({
+ // metadata: m,
+ // });
+ // const imageFlavor = image.ImageType[2];
+ // if (imageFlavor === 'VOLUME' || imageFlavor === 'THUMBNAIL') {
+ // volumeImages.push(image);
+ // }
// });
- // const imageFlavor = image.ImageType[2];
- // if (imageFlavor === 'VOLUME' || imageFlavor === 'THUMBNAIL') {
- // volumeImages.push(image);
- // }
- // });
+ // }
metadata.forEach(m => {
const inst = cleanDenaturalizedDataset(
dcmjs.data.DicomMetaDictionary.denaturalizeDataset(m)
);
if (!inst['00480105']) {
- // Optical Path Sequence
- // no OpticalPathIdentifier?
+ // Optical Path Sequence, no OpticalPathIdentifier?
+ // NOTE: this is actually a not-well formatted DICOM VL Whole Slide Microscopy Image.
inst['00480105'] = {
vr: 'SQ',
Value: [
diff --git a/extensions/dicom-microscopy/src/utils/deepCopyAndClean.ts b/extensions/dicom-microscopy/src/utils/deepCopyAndClean.ts
deleted file mode 100644
index 2d1bae1cbb..0000000000
--- a/extensions/dicom-microscopy/src/utils/deepCopyAndClean.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-function isPrimitive(v: any) {
- return !(typeof v == 'object' || Array.isArray(v) || typeof v === 'function');
-}
-
-/**
- * Specialized for DICOM JSON format dataset cleaning.
- * @param obj
- * @returns
- */
-export default function deepCopyAndClean(obj: any): any {
- if (Array.isArray(obj)) {
- const newAry = obj.map(o => (isPrimitive(o) ? o : deepCopyAndClean(o)));
- return newAry;
- } else {
- const newObj = {} as any;
- Object.keys(obj).forEach(key => {
- if (obj[key] === null) {
- // use empty array instead of null, especially the dicom-microscopy-viewer's
- // metadata handler has some bug around null value
- newObj[key] = [];
- } else if (isPrimitive(obj[key])) {
- newObj[key] = obj[key];
- } else {
- newObj[key] = deepCopyAndClean(obj[key]);
- }
- });
- return newObj;
- }
-}
From 462aa2774acb51de645315b4eef6bc0587cab431 Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Fri, 24 Mar 2023 11:01:49 -0400
Subject: [PATCH 19/51] remove unused code
---
.../dicom-microscopy/src/DicomMicroscopySopClassHandler.js | 4 ----
1 file changed, 4 deletions(-)
diff --git a/extensions/dicom-microscopy/src/DicomMicroscopySopClassHandler.js b/extensions/dicom-microscopy/src/DicomMicroscopySopClassHandler.js
index 54ef918b86..d7c6af93e3 100644
--- a/extensions/dicom-microscopy/src/DicomMicroscopySopClassHandler.js
+++ b/extensions/dicom-microscopy/src/DicomMicroscopySopClassHandler.js
@@ -70,10 +70,6 @@ function _getDisplaySetsFromSeries(
inst.FrameOfReferenceUID = instance.FrameOfReferenceUID;
- // if (inst.ContainerIdentifier === null) {
- // // ContainerIdentifier must not be null
- // inst.ContainerIdentifier = '0';
- // }
return inst;
});
From cffdef8106cc88b4f2298bdd0e90bb9d0159b987 Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Fri, 24 Mar 2023 13:59:13 -0400
Subject: [PATCH 20/51] [feat] microscopy - annotation selection
---
.../MicroscopyPanel/MicroscopyPanel.tsx | 78 +++++++++----------
.../dicom-microscopy/src/getCommandsModule.ts | 28 +++----
extensions/dicom-microscopy/src/index.tsx | 29 +++----
.../src/tools/microscopyManager.js | 78 +++++++++++--------
.../src/tools/viewerManager.js | 63 ++++++++-------
.../dicom-microscopy/src/utils/styles.js | 8 +-
6 files changed, 153 insertions(+), 131 deletions(-)
diff --git a/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx b/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx
index 03b67432b7..74b2ff309c 100644
--- a/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx
+++ b/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx
@@ -16,7 +16,7 @@ import callInputDialog from '../../utils/callInputDialog';
let saving = false;
const { datasetToBuffer } = dcmjs.data;
-const formatArea = (area) => {
+const formatArea = area => {
let mult = 1;
let unit = 'mm';
if (area > 1000000) {
@@ -61,7 +61,6 @@ function saveByteArray(buffer: ArrayBuffer, filename: string) {
link.click();
}
-
interface IMicroscopyPanelProps extends WithTranslation {
viewports: PropTypes.array;
activeViewportIndex: PropTypes.number;
@@ -108,8 +107,9 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) {
useEffect(() => {
const onAnnotationUpdated = () => {
- const roiAnnotations =
- microscopyManager.getAnnotationsForStudy(studyInstanceUID);
+ const roiAnnotations = microscopyManager.getAnnotationsForStudy(
+ studyInstanceUID
+ );
setRoiAnnotations(roiAnnotations);
};
@@ -163,8 +163,9 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) {
* @returns
*/
const promptSave = () => {
- const annotations =
- microscopyManager.getAnnotationsForStudy(studyInstanceUID);
+ const annotations = microscopyManager.getAnnotationsForStudy(
+ studyInstanceUID
+ );
if (!annotations || saving) {
return;
@@ -205,8 +206,9 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) {
const dataSource = extensionManager.getActiveDataSource()[0];
const { onSaveComplete } = props;
const imagingMeasurements = [];
- const annotations =
- microscopyManager.getAnnotationsForStudy(studyInstanceUID);
+ const annotations = microscopyManager.getAnnotationsForStudy(
+ studyInstanceUID
+ );
saving = true;
@@ -216,18 +218,18 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) {
const studyMetadata = DicomMetadataStore.getStudy(studyInstanceUID);
const displaySets = getAllDisplaySets(studyMetadata);
- const smDisplaySet = displaySets.find((ds) => ds.Modality === 'SM');
+ const smDisplaySet = displaySets.find(ds => ds.Modality === 'SM');
// Get the next available series number after 4700.
const dsWithMetadata = displaySets.filter(
- (ds) =>
+ ds =>
ds.metadata &&
ds.metadata.SeriesNumber &&
typeof ds.metadata.SeriesNumber === 'number'
);
- const seriesNumbers = dsWithMetadata.map((ds) => ds.metadata.SeriesNumber);
+ const seriesNumbers = dsWithMetadata.map(ds => ds.metadata.SeriesNumber);
const maxSeriesNumber = Math.max(...seriesNumbers, 4700);
const SeriesNumber = maxSeriesNumber + 1;
@@ -249,10 +251,11 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) {
schemeDesignator: 'DCM',
meaning: 'Person',
}),
- observerIdentifyingAttributes:
- new dcmjs.sr.templates.PersonObserverIdentifyingAttributes({
+ observerIdentifyingAttributes: new dcmjs.sr.templates.PersonObserverIdentifyingAttributes(
+ {
name: '@ohif/extension-dicom-microscopy',
- }),
+ }
+ ),
}),
observerDeviceContext: new dcmjs.sr.templates.ObserverContext({
observerType: new dcmjs.sr.coding.CodedConcept({
@@ -260,10 +263,11 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) {
schemeDesignator: 'DCM',
meaning: 'Device',
}),
- observerIdentifyingAttributes:
- new dcmjs.sr.templates.DeviceObserverIdentifyingAttributes({
+ observerIdentifyingAttributes: new dcmjs.sr.templates.DeviceObserverIdentifyingAttributes(
+ {
uid: DEVICE_OBSERVER_UID,
- }),
+ }
+ ),
}),
subjectContext: new dcmjs.sr.templates.SubjectContext({
subjectClass: new dcmjs.sr.coding.CodedConcept({
@@ -271,22 +275,27 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) {
schemeDesignator: 'DCM',
meaning: 'Specimen',
}),
- subjectClassSpecificContext:
- new dcmjs.sr.templates.SubjectContextSpecimen({
+ subjectClassSpecificContext: new dcmjs.sr.templates.SubjectContextSpecimen(
+ {
uid: SpecimenDescriptionSequence.SpecimenUID,
identifier:
SpecimenDescriptionSequence.SpecimenIdentifier ||
metadata.SeriesInstanceUID,
containerIdentifier:
metadata.ContainerIdentifier || metadata.SeriesInstanceUID,
- }),
+ }
+ ),
}),
});
for (let i = 0; i < annotations.length; i++) {
const { roiGraphic: roi, label } = annotations[i];
- let { measurements, evaluations, marker, presentationState } =
- roi.properties;
+ let {
+ measurements,
+ evaluations,
+ marker,
+ presentationState,
+ } = roi.properties;
console.debug('[SR] storing marker...', marker);
console.debug('[SR] storing measurements...', measurements);
@@ -345,8 +354,8 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) {
debugger;
const identifier = `ROI #${i + 1}`;
- const group =
- new dcmjs.sr.templates.PlanarROIMeasurementsAndQualitativeEvaluations({
+ const group = new dcmjs.sr.templates.PlanarROIMeasurementsAndQualitativeEvaluations(
+ {
trackingIdentifier: new dcmjs.sr.templates.TrackingIdentifier({
uid: roi.uid,
identifier: presentationState
@@ -366,13 +375,15 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) {
/** Evaluations will conflict with current tracking identifier */
/** qualitativeEvaluations: evaluations, */
measurements,
- });
+ }
+ );
imagingMeasurements.push(...group);
}
const measurementReport = new dcmjs.sr.templates.MeasurementReport({
- languageOfContentItemAndDescendants:
- new dcmjs.sr.templates.LanguageOfContentItemAndDescendants({}),
+ languageOfContentItemAndDescendants: new dcmjs.sr.templates.LanguageOfContentItemAndDescendants(
+ {}
+ ),
observationContext,
procedureReported: new dcmjs.sr.coding.CodedConcept({
value: '112703',
@@ -538,21 +549,8 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) {
const shortAxisLength = roiAnnotation.roiGraphic.properties.shortAxisLength;
const isSelected: boolean = selectedAnnotation === roiAnnotation;
- // actions
- const onRelabel = () => microscopyManager.triggerRelabel(roiAnnotation);
- const onDelete = () => microscopyManager.triggerDelete(roiAnnotation);
- const onSelect = () => microscopyManager.selectAnnotation(roiAnnotation);
-
- const rowActions = [];
- rowActions.push(getActionButton('Relabels', onRelabel));
- rowActions.push(getActionButton('Delete', onDelete));
-
// other events
const { uid } = roiAnnotation;
- const onMouseEnter = () =>
- microscopyManager.setROIStyle(uid, styles.active);
- const onMouseLeave = () =>
- microscopyManager.setROIStyle(uid, styles.default);
// display text
const displayText = [];
diff --git a/extensions/dicom-microscopy/src/getCommandsModule.ts b/extensions/dicom-microscopy/src/getCommandsModule.ts
index 9e5d281438..d752b1c06c 100644
--- a/extensions/dicom-microscopy/src/getCommandsModule.ts
+++ b/extensions/dicom-microscopy/src/getCommandsModule.ts
@@ -1,14 +1,15 @@
import microscopyManager from './tools/microscopyManager';
import { ServicesManager, CommandsManager, ExtensionManager } from '@ohif/core';
+import styles from './utils/styles';
export default function getCommandsModule({
servicesManager,
commandsManager,
extensionManager,
-} : {
- servicesManager: ServicesManager,
- commandsManager: CommandsManager,
- extensionManager: ExtensionManager,
+}: {
+ servicesManager: ServicesManager;
+ commandsManager: CommandsManager;
+ extensionManager: ExtensionManager;
}) {
const {
viewportGridService,
@@ -33,22 +34,15 @@ export default function getCommandsModule({
].indexOf(toolName) >= 0
) {
// TODO: read from configuration
- const styleOptions = {
- stroke: {
- color: [0, 255, 0, 1],
- width: 1.2,
- },
- };
let options = {
geometryType: toolName,
vertexEnabled: true,
- styleOptions,
+ styleOptions: styles.default,
} as any;
if ('line' === toolName) {
options.minPoints = 2;
options.maxPoints = 2;
- }
- else if('point' === toolName) {
+ } else if ('point' === toolName) {
delete options.styleOptions;
delete options.vertexEnabled;
}
@@ -79,7 +73,9 @@ export default function getCommandsModule({
toggleOverlays: () => {
// overlay
- const overlays = document.getElementsByClassName('microscopy-viewport-overlay');
+ const overlays = document.getElementsByClassName(
+ 'microscopy-viewport-overlay'
+ );
let onoff = false; // true if this will toggle on
for (let i = 0; i < overlays.length; i++) {
if (i === 0) onoff = overlays.item(0).classList.contains('hidden');
@@ -92,7 +88,7 @@ export default function getCommandsModule({
},
toggleAnnotations: () => {
microscopyManager.toggleROIsVisibility();
- }
+ },
};
const definitions = {
@@ -166,4 +162,4 @@ export default function getCommandsModule({
definitions,
defaultContext: 'MICROSCOPY',
};
-};
+}
diff --git a/extensions/dicom-microscopy/src/index.tsx b/extensions/dicom-microscopy/src/index.tsx
index 2f3b7b50e9..a38da0c110 100644
--- a/extensions/dicom-microscopy/src/index.tsx
+++ b/extensions/dicom-microscopy/src/index.tsx
@@ -11,7 +11,7 @@ const Component = React.lazy(() => {
return import('./DicomMicroscopyViewport');
});
-const MicroscopyViewport = (props) => {
+const MicroscopyViewport = props => {
return (
Loading...}>
@@ -36,16 +36,19 @@ export default {
* that is provided by the Cornerstone extension in OHIF.
*/
getViewportModule({ servicesManager, extensionManager }) {
-
- const ExtendedMicroscopyViewport = (props) => {
- const {
- displaySets,
- viewportIndex,
- viewportLabel,
- dataSource,
- viewportOptions,
- displaySetOptions,
- } = props;
+ /**
+ *
+ * @param props {*}
+ * @param props.displaySets
+ * @param props.viewportIndex
+ * @param props.viewportLabel
+ * @param props.dataSource
+ * @param props.viewportOptions
+ * @param props.displaySetOptions
+ * @returns
+ */
+ const ExtendedMicroscopyViewport = props => {
+ const { viewportOptions } = props;
const [viewportGrid, viewportGridService] = useViewportGrid();
const { viewports, activeViewportIndex } = viewportGrid;
@@ -67,7 +70,7 @@ export default {
return [
{
name: 'microscopy-dicom',
- component: ExtendedMicroscopyViewport
+ component: ExtendedMicroscopyViewport,
},
];
},
@@ -86,7 +89,7 @@ export default {
return [
getDicomMicroscopySopClassHandler({
servicesManager,
- extensionManager
+ extensionManager,
}),
getDicomMicroscopySRSopClassHandler({
servicesManager,
diff --git a/extensions/dicom-microscopy/src/tools/microscopyManager.js b/extensions/dicom-microscopy/src/tools/microscopyManager.js
index 082e4bf6eb..59fd3c5bb8 100644
--- a/extensions/dicom-microscopy/src/tools/microscopyManager.js
+++ b/extensions/dicom-microscopy/src/tools/microscopyManager.js
@@ -3,6 +3,7 @@ import ViewerManager, { EVENTS as ViewerEvents } from './viewerManager';
import RoiAnnotation, {
EVENTS as AnnotationEvents,
} from '../utils/RoiAnnotation';
+import styles from '../utils/styles';
import { DicomMetadataStore } from '@ohif/core';
const EVENTS = {
@@ -39,7 +40,7 @@ class MicroscopyManager extends Publisher {
* to its initial state
*/
clear() {
- this.managedViewers.forEach((managedViewer) => managedViewer.destroy());
+ this.managedViewers.forEach(managedViewer => managedViewer.destroy());
this.managedViewers.clear();
for (var key in this.annotations) {
delete this.annotations[key];
@@ -51,7 +52,7 @@ class MicroscopyManager extends Publisher {
}
clearAnnotations() {
- Object.keys(this.annotations).forEach((uid) => {
+ Object.keys(this.annotations).forEach(uid => {
this.removeAnnotation(this.annotations[uid]);
});
}
@@ -90,7 +91,7 @@ class MicroscopyManager extends Publisher {
if (label !== undefined) {
roiAnnotation.setLabel(label);
} else {
- const onRelabel = (item) =>
+ const onRelabel = item =>
managedViewer.updateROIProperties({
uid: roiGraphic.uid,
properties: { label: item.label, finding: item.finding },
@@ -177,7 +178,7 @@ class MicroscopyManager extends Publisher {
* @returns {Array} The managed viewers for the given series UID
*/
_getManagedViewersForSeries(studyInstanceUID, seriesInstanceUID) {
- const filter = (managedViewer) =>
+ const filter = managedViewer =>
managedViewer.studyInstanceUID === studyInstanceUID &&
managedViewer.seriesInstanceUID === seriesInstanceUID;
return Array.from(this.managedViewers).filter(filter);
@@ -192,7 +193,7 @@ class MicroscopyManager extends Publisher {
* @returns {Array} The managed viewers for the given series UID
*/
getManagedViewersForStudy(studyInstanceUID) {
- const filter = (managedViewer) =>
+ const filter = managedViewer =>
managedViewer.studyInstanceUID === studyInstanceUID;
return Array.from(this.managedViewers).filter(filter);
}
@@ -208,7 +209,7 @@ class MicroscopyManager extends Publisher {
studyInstanceUID,
seriesInstanceUID
);
- annotations.forEach((roiAnnotation) => {
+ annotations.forEach(roiAnnotation => {
managedViewer.addRoiGraphic(roiAnnotation.roiGraphic);
});
}
@@ -226,7 +227,7 @@ class MicroscopyManager extends Publisher {
* @param {String} studyInstanceUID The study UID of the loaded image
* @param {String} seriesInstanceUID The series UID of the loaded image
* @param {Array} displaySets All displaySets related to the same StudyInstanceUID
- *
+ *
* @returns {ViewerManager} managed viewer
*/
addViewer(
@@ -234,7 +235,7 @@ class MicroscopyManager extends Publisher {
viewportIndex,
container,
studyInstanceUID,
- seriesInstanceUID,
+ seriesInstanceUID
) {
const managedViewer = new ViewerManager(
viewer,
@@ -261,7 +262,7 @@ class MicroscopyManager extends Publisher {
_potentiallyLoadSR(StudyInstanceUID, displaySets) {
const studyMetadata = DicomMetadataStore.getStudy(StudyInstanceUID);
- const smDisplaySet = displaySets.find((ds) => ds.Modality === 'SM');
+ const smDisplaySet = displaySets.find(ds => ds.Modality === 'SM');
const { FrameOfReferenceUID, othersFrameOfReferenceUID } = smDisplaySet;
@@ -271,7 +272,7 @@ class MicroscopyManager extends Publisher {
let derivedDisplaySets = FrameOfReferenceUID
? displaySets.filter(
- (ds) =>
+ ds =>
ds.ReferencedFrameOfReferenceUID === FrameOfReferenceUID ||
// sometimes each depth instance has the different FrameOfReferenceID
othersFrameOfReferenceUID.includes(ds.ReferencedFrameOfReferenceUID)
@@ -282,11 +283,9 @@ class MicroscopyManager extends Publisher {
return;
}
- derivedDisplaySets = derivedDisplaySets.filter(
- (ds) => ds.Modality === 'SR'
- );
+ derivedDisplaySets = derivedDisplaySets.filter(ds => ds.Modality === 'SR');
- if (derivedDisplaySets.some((ds) => ds.isLoaded === true)) {
+ if (derivedDisplaySets.some(ds => ds.isLoaded === true)) {
// Don't auto load
return;
}
@@ -295,7 +294,7 @@ class MicroscopyManager extends Publisher {
let recentDateTime = 0;
let recentDisplaySet = derivedDisplaySets[0];
- derivedDisplaySets.forEach((ds) => {
+ derivedDisplaySets.forEach(ds => {
const dateTime = Number(`${ds.SeriesDate}${ds.SeriesTime}`);
if (dateTime > recentDateTime) {
recentDateTime = dateTime;
@@ -338,12 +337,12 @@ class MicroscopyManager extends Publisher {
* Hide all ROIs
*/
hideROIs() {
- this.managedViewers.forEach((mv) => mv.hideROIs());
+ this.managedViewers.forEach(mv => mv.hideROIs());
}
/** Show all ROIs */
showROIs() {
- this.managedViewers.forEach((mv) => mv.showROIs());
+ this.managedViewers.forEach(mv => mv.showROIs());
}
/**
@@ -364,7 +363,7 @@ class MicroscopyManager extends Publisher {
*/
getAnnotations() {
const annotations = [];
- Object.keys(this.annotations).forEach((uid) => {
+ Object.keys(this.annotations).forEach(uid => {
annotations.push(this.getAnnotation(uid));
});
return annotations;
@@ -376,7 +375,7 @@ class MicroscopyManager extends Publisher {
* @param {String} studyInstanceUID UID for the study
*/
getAnnotationsForStudy(studyInstanceUID) {
- const filter = (a) => a.studyInstanceUID === studyInstanceUID;
+ const filter = a => a.studyInstanceUID === studyInstanceUID;
return this.getAnnotations().filter(filter);
}
@@ -388,7 +387,7 @@ class MicroscopyManager extends Publisher {
* @param {String} seriesInstanceUID UID for the series
*/
getAnnotationsForSeries(studyInstanceUID, seriesInstanceUID) {
- const filter = (annotation) =>
+ const filter = annotation =>
annotation.studyInstanceUID === studyInstanceUID &&
annotation.seriesInstanceUID === seriesInstanceUID;
return this.getAnnotations().filter(filter);
@@ -403,6 +402,20 @@ class MicroscopyManager extends Publisher {
return this.selectedAnnotation;
}
+ /**
+ * Clear current RoiAnnotation selection
+ */
+ clearSelection() {
+ if (this.selectedAnnotation) {
+ this.setROIStyle(this.selectedAnnotation.uid, {
+ stroke: {
+ color: '#00ff00',
+ },
+ });
+ }
+ this.selectedAnnotation = null;
+ }
+
/**
* Selects the given RoiAnnotation instance, publishing an ANNOTATION_SELECTED
* event to notify all the subscribers
@@ -410,8 +423,11 @@ class MicroscopyManager extends Publisher {
* @param {RoiAnnotation} roiAnnotation The instance to be selected
*/
selectAnnotation(roiAnnotation) {
+ if (this.selectedAnnotation) this.clearSelection();
+
this.selectedAnnotation = roiAnnotation;
this.publish(EVENTS.ANNOTATION_SELECTED, roiAnnotation);
+ this.setROIStyle(roiAnnotation.uid, styles.active);
}
/**
@@ -423,7 +439,7 @@ class MicroscopyManager extends Publisher {
toggleOverviewMap(viewportIndex) {
const managedViewers = Array.from(this.managedViewers);
const managedViewer = managedViewers.find(
- (mv) => mv.viewportIndex === viewportIndex
+ mv => mv.viewportIndex === viewportIndex
);
if (managedViewer) {
managedViewer.toggleOverviewMap();
@@ -438,13 +454,13 @@ class MicroscopyManager extends Publisher {
*/
removeAnnotation(roiAnnotation) {
const { uid, studyInstanceUID, seriesInstanceUID } = roiAnnotation;
- const filter = (managedViewer) =>
+ const filter = managedViewer =>
managedViewer.studyInstanceUID === studyInstanceUID &&
managedViewer.seriesInstanceUID === seriesInstanceUID;
const managedViewers = Array.from(this.managedViewers).filter(filter);
- managedViewers.forEach((managedViewer) =>
+ managedViewers.forEach(managedViewer =>
managedViewer.removeRoiGraphic(uid)
);
}
@@ -460,7 +476,7 @@ class MicroscopyManager extends Publisher {
* @param {Number} viewportIndex Index of the viewport to focus
*/
focusAnnotation(roiAnnotation, viewportIndex) {
- const filter = (mv) => mv.viewportIndex === viewportIndex;
+ const filter = mv => mv.viewportIndex === viewportIndex;
const managedViewer = Array.from(this.managedViewers).find(filter);
if (managedViewer) {
managedViewer.setViewStateByExtent(roiAnnotation);
@@ -483,11 +499,11 @@ class MicroscopyManager extends Publisher {
);
// Prevent infinite loops arrising from updates.
- managedViewers.forEach((managedViewer) =>
+ managedViewers.forEach(managedViewer =>
this._removeManagedViewerSubscriptions(managedViewer)
);
- managedViewers.forEach((managedViewer) => {
+ managedViewers.forEach(managedViewer => {
if (managedViewer === baseManagedViewer) {
return;
}
@@ -497,12 +513,12 @@ class MicroscopyManager extends Publisher {
seriesInstanceUID
);
managedViewer.clearRoiGraphics();
- annotations.forEach((roiAnnotation) => {
+ annotations.forEach(roiAnnotation => {
managedViewer.addRoiGraphic(roiAnnotation.roiGraphic);
});
});
- managedViewers.forEach((managedViewer) =>
+ managedViewers.forEach(managedViewer =>
this._addManagedViewerSubscriptions(managedViewer)
);
}
@@ -513,7 +529,7 @@ class MicroscopyManager extends Publisher {
* @param {Array} interactions interactions
*/
activateInteractions(interactions) {
- this.managedViewers.forEach((mv) => mv.activateInteractions(interactions));
+ this.managedViewers.forEach(mv => mv.activateInteractions(interactions));
this.activeInteractions = interactions;
}
@@ -527,7 +543,7 @@ class MicroscopyManager extends Publisher {
triggerRelabel(roiAnnotation, newAnnotation = false, onRelabel) {
if (!onRelabel) {
onRelabel = ({ label }) =>
- this.managedViewers.forEach((mv) =>
+ this.managedViewers.forEach(mv =>
mv.updateROIProperties({
uid: roiAnnotation.uid,
properties: { label },
@@ -566,7 +582,7 @@ class MicroscopyManager extends Publisher {
* @param {object*} styleOptions.image - Style options for image
*/
setROIStyle(uid, styleOptions) {
- this.managedViewers.forEach((mv) => mv.setROIStyle(uid, styleOptions));
+ this.managedViewers.forEach(mv => mv.setROIStyle(uid, styleOptions));
}
}
diff --git a/extensions/dicom-microscopy/src/tools/viewerManager.js b/extensions/dicom-microscopy/src/tools/viewerManager.js
index 410abc9939..39145bbcde 100644
--- a/extensions/dicom-microscopy/src/tools/viewerManager.js
+++ b/extensions/dicom-microscopy/src/tools/viewerManager.js
@@ -38,11 +38,16 @@ class ViewerManager extends Publisher {
this.onRoiAdded = this.roiAddedHandler.bind(this);
this.onRoiModified = this.roiModifiedHandler.bind(this);
this.onRoiRemoved = this.roiRemovedHandler.bind(this);
+ this.contextMenuCallback = () => {};
this.registerEvents();
this.activateDefaultInteractions();
}
+ addContextMenuCallback(callback) {
+ this.contextMenuCallback = callback;
+ }
+
/**
* Destroys this managed viewer instance, clearing all the event handlers
*/
@@ -230,13 +235,16 @@ class ViewerManager extends Publisher {
*/
activateDefaultInteractions() {
/** Disable browser's native context menu inside the canvas */
- document
- .querySelector('.DicomMicroscopyViewer')
- .addEventListener(
- 'contextmenu',
- (event) => event.preventDefault(),
- false
- );
+ document.querySelector('.DicomMicroscopyViewer').addEventListener(
+ 'contextmenu',
+ event => {
+ event.preventDefault();
+ if (typeof this.contextMenuCallback === 'function') {
+ this.contextMenuCallback(event);
+ }
+ },
+ false
+ );
const defaultInteractions = [
[
'dragPan',
@@ -246,14 +254,14 @@ class ViewerManager extends Publisher {
},
},
],
- [
- 'dragZoom',
- {
- bindings: {
- mouseButtons: ['right'],
- },
- },
- ],
+ // [
+ // 'dragZoom',
+ // {
+ // bindings: {
+ // mouseButtons: ['right'],
+ // },
+ // },
+ // ],
['modify', {}],
];
this.activateInteractions(defaultInteractions);
@@ -266,34 +274,35 @@ class ViewerManager extends Publisher {
*/
activateInteractions(interactions) {
const interactionsMap = {
- draw: (activate) =>
+ draw: activate =>
activate ? 'activateDrawInteraction' : 'deactivateDrawInteraction',
- modify: (activate) =>
+ modify: activate =>
activate ? 'activateModifyInteraction' : 'deactivateModifyInteraction',
- translate: (activate) =>
+ translate: activate =>
activate
? 'activateTranslateInteraction'
: 'deactivateTranslateInteraction',
- snap: (activate) =>
+ snap: activate =>
activate ? 'activateSnapInteraction' : 'deactivateSnapInteraction',
- dragPan: (activate) =>
+ dragPan: activate =>
activate
? 'activateDragPanInteraction'
: 'deactivateDragPanInteraction',
- dragZoom: (activate) =>
+ dragZoom: activate =>
activate
? 'activateDragZoomInteraction'
: 'deactivateDragZoomInteraction',
};
const availableInteractionsName = Object.keys(interactionsMap);
- availableInteractionsName.forEach((availableInteractionName) => {
+ availableInteractionsName.forEach(availableInteractionName => {
const interaction = interactions.find(
- (interaction) => interaction[0] === availableInteractionName
+ interaction => interaction[0] === availableInteractionName
);
if (!interaction) {
- const deactivateInteractionMethod =
- interactionsMap[availableInteractionName](false);
+ const deactivateInteractionMethod = interactionsMap[
+ availableInteractionName
+ ](false);
this.viewer[deactivateInteractionMethod]();
} else {
const [name, config] = interaction;
@@ -315,7 +324,7 @@ class ViewerManager extends Publisher {
_getMap() {
const symbols = Object.getOwnPropertySymbols(this.viewer);
- const _map = symbols.find((s) => String(s) === 'Symbol(map)');
+ const _map = symbols.find(s => String(s) === 'Symbol(map)');
window['map'] = this.viewer[_map];
return this.viewer[_map];
}
@@ -392,7 +401,7 @@ class ViewerManager extends Publisher {
let minY = Infinity;
let maxY = -Infinity;
- coordinates.forEach((coord) => {
+ coordinates.forEach(coord => {
let mappedCoord = coordinateFormatScoord3d2Geometry(coord, pyramid);
const [x, y] = mappedCoord;
diff --git a/extensions/dicom-microscopy/src/utils/styles.js b/extensions/dicom-microscopy/src/utils/styles.js
index 348455087c..030b910bc4 100644
--- a/extensions/dicom-microscopy/src/utils/styles.js
+++ b/extensions/dicom-microscopy/src/utils/styles.js
@@ -7,13 +7,13 @@ const emptyFill = {
};
const defaultStroke = {
- color: 'black',
- width: 3,
+ color: 'rgb(0,255,0)',
+ width: 1.5,
};
const activeStroke = {
- color: 'white',
- width: 3,
+ color: 'rgb(255,255,0)',
+ width: 1.5,
};
const defaultStyle = {
From 119b9bfe6149572a62f25b7b31c389e6b1ecd29e Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Fri, 24 Mar 2023 14:24:20 -0400
Subject: [PATCH 21/51] [feat] microscopy - edit annotation label
---
.../MicroscopyPanel/MicroscopyPanel.tsx | 32 ++--------------
.../dicom-microscopy/src/getCommandsModule.ts | 38 +++++++++++++++++++
2 files changed, 41 insertions(+), 29 deletions(-)
diff --git a/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx b/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx
index 74b2ff309c..64841825cc 100644
--- a/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx
+++ b/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import {
ServicesManager,
ExtensionManager,
+ CommandsManager,
DicomMetadataStore,
} from '@ohif/core';
import { MeasurementTable, Icon, ButtonGroup, Button } from '@ohif/ui';
@@ -73,6 +74,7 @@ interface IMicroscopyPanelProps extends WithTranslation {
//
servicesManager: ServicesManager;
extensionManager: ExtensionManager;
+ commandsManager: CommandsManager;
}
/**
@@ -142,20 +144,6 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) {
};
}, [studyInstanceUID]);
- const getActionButton = (
- btnLabel: string,
- onClickCallback: (evt: any) => {}
- ) => {
- return (
-
-
-
-
- {btnLabel}
-
- );
- };
-
/**
* On clicking "Save Annotations" button, prompt an input modal for the
* new series' description, and continue to save.
@@ -523,21 +511,7 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) {
uid: string;
isActive: boolean;
}) => {
- const roiAnnotation = microscopyManager.getAnnotation(uid);
-
- callInputDialog({
- uiDialogService,
- title: 'Enter your annotation',
- defaultValue: '',
- callback: (value: string, action: string) => {
- switch (action) {
- case 'save': {
- roiAnnotation.setLabel(value);
- microscopyManager.triggerRelabel(roiAnnotation);
- }
- }
- },
- });
+ props.commandsManager.runCommand('setLabel', { uid }, 'MICROSCOPY');
};
// Convert ROI annotations managed by microscopyManager into our
diff --git a/extensions/dicom-microscopy/src/getCommandsModule.ts b/extensions/dicom-microscopy/src/getCommandsModule.ts
index d752b1c06c..964538595f 100644
--- a/extensions/dicom-microscopy/src/getCommandsModule.ts
+++ b/extensions/dicom-microscopy/src/getCommandsModule.ts
@@ -1,6 +1,7 @@
import microscopyManager from './tools/microscopyManager';
import { ServicesManager, CommandsManager, ExtensionManager } from '@ohif/core';
import styles from './utils/styles';
+import callInputDialog from './utils/callInputDialog';
export default function getCommandsModule({
servicesManager,
@@ -21,6 +22,32 @@ export default function getCommandsModule({
} = servicesManager.services;
const actions = {
+ // Measurement tool commands:
+ deleteMeasurement: ({ uid }) => {
+ if (uid) {
+ const roiAnnotation = microscopyManager.getAnnotation(uid);
+ if (roiAnnotation) microscopyManager.removeAnnotation(roiAnnotation);
+ }
+ },
+
+ setLabel: ({ uid }) => {
+ const roiAnnotation = microscopyManager.getAnnotation(uid);
+
+ callInputDialog({
+ uiDialogService,
+ title: 'Enter your annotation',
+ defaultValue: '',
+ callback: (value: string, action: string) => {
+ switch (action) {
+ case 'save': {
+ roiAnnotation.setLabel(value);
+ microscopyManager.triggerRelabel(roiAnnotation);
+ }
+ }
+ },
+ });
+ },
+
setToolActive: ({ toolName, toolGroupId = 'MICROSCOPY' }) => {
if (
[
@@ -52,6 +79,7 @@ export default function getCommandsModule({
microscopyManager.activateInteractions([['dragPan']]);
}
},
+
rotateViewport: ({ rotation }) => {},
flipViewportHorizontal: () => {},
flipViewportVertical: () => {},
@@ -92,6 +120,16 @@ export default function getCommandsModule({
};
const definitions = {
+ deleteMeasurement: {
+ commandFn: actions.deleteMeasurement,
+ storeContexts: [] as any[],
+ options: {},
+ },
+ setLabel: {
+ commandFn: actions.setLabel,
+ storeContexts: [] as any[],
+ options: {},
+ },
setToolActive: {
commandFn: actions.setToolActive,
storeContexts: [] as any[],
From ffe10ad753c6b68405287c32c3b1aa3fbfb7f189 Mon Sep 17 00:00:00 2001
From: Alireza
Date: Wed, 29 Mar 2023 23:36:12 -0400
Subject: [PATCH 22/51] wip
---
.webpack/rules/loadWebWorkers.js | 4 +-
.webpack/webpack.base.js | 10 +-
extensions/dicom-microscopy/package.json | 2 +-
platform/viewer/.webpack/webpack.pwa.js | 11 +-
platform/viewer/public/config/default.js | 12 +--
yarn.lock | 126 ++++++++++++-----------
6 files changed, 92 insertions(+), 73 deletions(-)
diff --git a/.webpack/rules/loadWebWorkers.js b/.webpack/rules/loadWebWorkers.js
index 2e7f111931..a4ab284159 100644
--- a/.webpack/rules/loadWebWorkers.js
+++ b/.webpack/rules/loadWebWorkers.js
@@ -5,11 +5,11 @@
*/
const loadWebWorkers = {
test: /\.worker\.js$/,
- include: /vtk\.js[\/\\]Sources|dicom-microscopy-viewer[\/\\]/,
+ include: /vtk\.js[\/\\]Sources/,
use: [
{
loader: 'worker-loader',
- options: { inline: 'no-fallback' },
+ options: { inline: true, fallback: false },
},
],
};
diff --git a/.webpack/webpack.base.js b/.webpack/webpack.base.js
index 306fee5e76..93b024ce91 100644
--- a/.webpack/webpack.base.js
+++ b/.webpack/webpack.base.js
@@ -110,6 +110,8 @@ module.exports = (env, argv, { SRC_DIR, DIST_DIR }) => {
'@state': path.resolve(__dirname, '../platform/viewer/src/state'),
'cornerstone-wado-image-loader':
'cornerstone-wado-image-loader/dist/dynamic-import/cornerstoneWADOImageLoader.min.js',
+ 'dicom-microscopy-viewer':
+ 'dicom-microscopy-viewer/dist/dynamic-import/dicomMicroscopyViewer.min.js',
},
// Which directories to search when resolving modules
modules: [
@@ -154,10 +156,10 @@ module.exports = (env, argv, { SRC_DIR, DIST_DIR }) => {
// new BundleAnalyzerPlugin(),
],
// enable experimental features, like webAssembly
- experiments: {
- asyncWebAssembly: true,
- syncWebAssembly: true,
- },
+ // experiments: {
+ // asyncWebAssembly: true,
+ // syncWebAssembly: true,
+ // },
};
if (isProdBuild) {
diff --git a/extensions/dicom-microscopy/package.json b/extensions/dicom-microscopy/package.json
index f4aa77b29d..c06a70065f 100644
--- a/extensions/dicom-microscopy/package.json
+++ b/extensions/dicom-microscopy/package.json
@@ -47,7 +47,7 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^0.0.7",
"@cornerstonejs/codec-openjpeg": "^0.1.1",
"colormap": "^2.3",
- "dicom-microscopy-viewer": "^0.35.0",
+ "dicom-microscopy-viewer": "^0.44.0",
"dicomicc": "^0.1"
},
"devDependencies": {
diff --git a/platform/viewer/.webpack/webpack.pwa.js b/platform/viewer/.webpack/webpack.pwa.js
index 339edbde22..2999495077 100644
--- a/platform/viewer/.webpack/webpack.pwa.js
+++ b/platform/viewer/.webpack/webpack.pwa.js
@@ -120,11 +120,20 @@ module.exports = (env, argv) => {
// Need to exclude the theme as it is updated independently
exclude: [/theme/],
}),
+ // new CopyPlugin({
+ // patterns: [
+ // {
+ // from:
+ // '../../../node_modules/cornerstone-wado-image-loader/dist/dynamic-import',
+ // to: DIST_DIR,
+ // },
+ // ],
+ // }),
new CopyPlugin({
patterns: [
{
from:
- '../../../node_modules/cornerstone-wado-image-loader/dist/dynamic-import',
+ '../../../node_modules/dicom-microscopy-viewer/dist/dynamic-import',
to: DIST_DIR,
},
],
diff --git a/platform/viewer/public/config/default.js b/platform/viewer/public/config/default.js
index c0de3708a9..0d88de828b 100644
--- a/platform/viewer/public/config/default.js
+++ b/platform/viewer/public/config/default.js
@@ -29,13 +29,13 @@ window.config = {
configuration: {
name: 'aws',
// old server
- // wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado',
- // qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
- // wadoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
+ wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado',
+ qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
+ wadoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
// new server
- wadoUriRoot: 'https://domvja9iplmyu.cloudfront.net/dicomweb',
- qidoRoot: 'https://domvja9iplmyu.cloudfront.net/dicomweb',
- wadoRoot: 'https://domvja9iplmyu.cloudfront.net/dicomweb',
+ // wadoUriRoot: 'https://domvja9iplmyu.cloudfront.net/dicomweb',
+ // qidoRoot: 'https://domvja9iplmyu.cloudfront.net/dicomweb',
+ // wadoRoot: 'https://domvja9iplmyu.cloudfront.net/dicomweb',
qidoSupportsIncludeField: false,
supportsReject: false,
imageRendering: 'wadors',
diff --git a/yarn.lock b/yarn.lock
index d1e1548639..fda39ad54e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1154,14 +1154,6 @@
"@babel/helper-create-regexp-features-plugin" "^7.18.6"
"@babel/helper-plugin-utils" "^7.18.6"
-"@babel/polyfill@^7.8.3":
- version "7.12.1"
- resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.12.1.tgz#1f2d6371d1261bbd961f3c5d5909150e12d0bd96"
- integrity sha512-X0pi0V6gxLi6lFZpGmeNa4zxtwEmCs42isWLNjZZDE0Y8yVfgu0T2OAHlzBbdYlqbW/YXVvoBHpATEM+goCj8g==
- dependencies:
- core-js "^2.6.5"
- regenerator-runtime "^0.13.4"
-
"@babel/preset-env@^7.11.0", "@babel/preset-env@^7.12.1", "@babel/preset-env@^7.12.11", "@babel/preset-env@^7.16.11", "@babel/preset-env@^7.18.6", "@babel/preset-env@^7.19.4", "@babel/preset-env@^7.5.0":
version "7.20.2"
resolved "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.20.2.tgz#9b1642aa47bb9f43a86f9630011780dab7f86506"
@@ -1316,7 +1308,7 @@
core-js-pure "^3.25.1"
regenerator-runtime "^0.13.11"
-"@babel/runtime@7.17.9", "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.6", "@babel/runtime@^7.15.4", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.6", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.7", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
+"@babel/runtime@7.17.9", "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.6", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.6", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
version "7.20.13"
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz#7055ab8a7cff2b8f6058bf6ae45ff84ad2aded4b"
integrity sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==
@@ -8258,9 +8250,9 @@ compare-func@^2.0.0:
array-ify "^1.0.0"
dot-prop "^5.1.0"
-complex.js@^2.0.15:
+complex.js@^2.1.1:
version "2.1.1"
- resolved "https://registry.yarnpkg.com/complex.js/-/complex.js-2.1.1.tgz#0675dac8e464ec431fb2ab7d30f41d889fb25c31"
+ resolved "https://registry.npmjs.org/complex.js/-/complex.js-2.1.1.tgz#0675dac8e464ec431fb2ab7d30f41d889fb25c31"
integrity sha512-8njCHOTtFFLtegk6zQo0kkVX1rngygb/KQI6z1qZxlFI3scluC+LVTCFbrkWjBv4vvLlbQ9t88IPMC6k95VTTg==
component-emitter@^1.2.1:
@@ -8588,7 +8580,7 @@ core-js-pure@^3.23.3, core-js-pure@^3.25.1:
resolved "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.28.0.tgz#4ef2888475b6c856ef6f5aeef8b4f618b76ad048"
integrity sha512-DSOVleA9/v3LNj/vFxAPfUHttKTzrB2RXhAPvR5TPXn4vrra3Z2ssytvRyt8eruJwAfwAiFADEbrjcRdcvPLQQ==
-core-js@^2.4.1, core-js@^2.5.7, core-js@^2.6.12, core-js@^2.6.5:
+core-js@^2.4.1, core-js@^2.5.7, core-js@^2.6.12:
version "2.6.12"
resolved "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec"
integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
@@ -9354,13 +9346,12 @@ dayjs@^1.10.4:
resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.7.tgz#4b296922642f70999544d1144a2c25730fce63e2"
integrity sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==
-dcmjs@^0.18:
- version "0.18.11"
- resolved "https://registry.yarnpkg.com/dcmjs/-/dcmjs-0.18.11.tgz#724cab26148503017d97bbcbb8fe9685dfae69dc"
- integrity sha512-7C+tKWIzeo2ZzL1Fss+3HuztWyhZYWhQGXV5hrK7yi4s8ctIN0lCeTUNYPdRS1DyE2GEAI7gb9fDcQU9Wh1rgg==
+dcmjs@^0.27:
+ version "0.27.0"
+ resolved "https://registry.npmjs.org/dcmjs/-/dcmjs-0.27.0.tgz#2662818c8b20494e366583e6dd3577c20d04d6ff"
+ integrity sha512-26wtatOLh+0b0aFy9iOg7PdOLG9EHevn9nEOn7Aoo5l7P9aFAMZ3XAa9Q+NULzLE2Q7DcIf2TQvfyVtdzhQzeg==
dependencies:
- "@babel/polyfill" "^7.8.3"
- "@babel/runtime" "^7.8.4"
+ "@babel/runtime-corejs2" "^7.17.8"
gl-matrix "^3.1.0"
lodash.clonedeep "^4.5.0"
loglevelnext "^3.0.1"
@@ -9429,9 +9420,9 @@ decamelize@^1.1.0, decamelize@^1.1.2, decamelize@^1.2.0:
resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==
-decimal.js@^10.3.1:
+decimal.js@^10.4.3:
version "10.4.3"
- resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23"
+ resolved "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23"
integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==
decode-uri-component@^0.2.0:
@@ -9751,16 +9742,22 @@ dezalgo@^1.0.0:
asap "^2.0.0"
wrappy "1"
-dicom-microscopy-viewer@^0.35.0:
- version "0.35.2"
- resolved "https://registry.yarnpkg.com/dicom-microscopy-viewer/-/dicom-microscopy-viewer-0.35.2.tgz#437a5a5b28e0c886931adfc29a296e4eb5d400f4"
- integrity sha512-v+OTaOwG8oB+fMBmJqUSuhjYsHftmKZY8W06MGR5loLjb0xUZSS1fnTsMY6FcP+er9MgDamSG5MwKBid0OFkEA==
- dependencies:
- dcmjs "^0.18"
+dicom-microscopy-viewer@^0.44.0:
+ version "0.44.0"
+ resolved "https://registry.npmjs.org/dicom-microscopy-viewer/-/dicom-microscopy-viewer-0.44.0.tgz#d4a9e985acb23c5b82a9aedbe379b39198b8fd55"
+ integrity sha512-7rcm8bXTcOLsXrhBPwWe5gf4Sj1Rz1lRh+ECcaznOEr/uvX6BTAYLqNJNmeAUMoKcadboEeDrmLihCwCteRSJQ==
+ dependencies:
+ "@cornerstonejs/codec-charls" "^0.1.1"
+ "@cornerstonejs/codec-libjpeg-turbo-8bit" "^0.0.7"
+ "@cornerstonejs/codec-openjpeg" "^0.1.1"
+ colormap "^2.3"
+ dcmjs "^0.27"
+ dicomicc "^0.1"
dicomweb-client "^0.8"
image-type "^4.1"
- mathjs "^9.4"
- ol "^6.9"
+ mathjs "^11.2"
+ ol "^7.1"
+ uuid "^9.0"
dicom-parser@^1.8.9:
version "1.8.20"
@@ -10094,6 +10091,11 @@ duplexify@^3.4.2, duplexify@^3.6.0:
readable-stream "^2.0.0"
stream-shift "^1.0.0"
+earcut@^2.2.3:
+ version "2.2.4"
+ resolved "https://registry.npmjs.org/earcut/-/earcut-2.2.4.tgz#6d02fd4d68160c114825d06890a92ecaae60343a"
+ integrity sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==
+
eastasianwidth@^0.2.0:
version "0.2.0"
resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
@@ -11648,7 +11650,7 @@ forwarded@0.2.0:
resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==
-fraction.js@^4.1.1, fraction.js@^4.2.0:
+fraction.js@^4.2.0:
version "4.2.0"
resolved "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950"
integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==
@@ -11822,16 +11824,16 @@ gensync@^1.0.0-beta.1, gensync@^1.0.0-beta.2:
resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
-geotiff@2.0.4:
- version "2.0.4"
- resolved "https://registry.yarnpkg.com/geotiff/-/geotiff-2.0.4.tgz#d6f231fdd76186aba21c61823ed759fcbf5d4f86"
- integrity sha512-aG8h9bJccGusioPsEWsEqx8qdXpZN71A20WCvRKGxcnHSOWLKmC5ZmsAmodfxb9TRQvs+89KikGuPzxchhA+Uw==
+geotiff@^2.0.7:
+ version "2.0.7"
+ resolved "https://registry.npmjs.org/geotiff/-/geotiff-2.0.7.tgz#358e578233af70bfb0b4dee62d599ad78fc5cfca"
+ integrity sha512-FKvFTNowMU5K6lHYY2f83d4lS2rsCNdpUC28AX61x9ZzzqPNaWFElWv93xj0eJFaNyOYA63ic5OzJ88dHpoA5Q==
dependencies:
"@petamoriken/float16" "^3.4.7"
lerc "^3.0.0"
- lru-cache "^6.0.0"
pako "^2.0.4"
parse-headers "^2.0.2"
+ quick-lru "^6.1.1"
web-worker "^1.2.0"
xml-utils "^1.0.2"
@@ -15273,20 +15275,20 @@ material-colors@^1.2.1:
resolved "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz#6d1958871126992ceecc72f4bcc4d8f010865f46"
integrity sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==
-mathjs@^9.4:
- version "9.5.2"
- resolved "https://registry.yarnpkg.com/mathjs/-/mathjs-9.5.2.tgz#e0f3279320dc6f49e45d99c4fcdd8b52231f0462"
- integrity sha512-c0erTq0GP503/Ch2OtDOAn50GIOsuxTMjmE00NI/vKJFSWrDaQHRjx6ai+16xYv70yBSnnpUgHZGNf9FR9IwmA==
+mathjs@^11.2:
+ version "11.7.0"
+ resolved "https://registry.npmjs.org/mathjs/-/mathjs-11.7.0.tgz#a197e82c760c57744b20324a8c4eed677bddf179"
+ integrity sha512-RCXtrP5xGIbl9PUc5+7QL81rBCUjzoIZ0ugNqKsarOUxg+x7deY0BzfNai+bGfUL/T+1uYq1xs5w2xVdL3lp0g==
dependencies:
- "@babel/runtime" "^7.15.4"
- complex.js "^2.0.15"
- decimal.js "^10.3.1"
+ "@babel/runtime" "^7.21.0"
+ complex.js "^2.1.1"
+ decimal.js "^10.4.3"
escape-latex "^1.2.0"
- fraction.js "^4.1.1"
+ fraction.js "^4.2.0"
javascript-natural-sort "^0.7.1"
seedrandom "^3.0.5"
tiny-emitter "^2.1.0"
- typed-function "^2.0.0"
+ typed-function "^4.1.0"
md5.js@^1.3.4:
version "1.3.5"
@@ -16458,21 +16460,22 @@ oidc-client@1.11.5:
crypto-js "^4.0.0"
serialize-javascript "^4.0.0"
-ol-mapbox-style@^8.0.5:
- version "8.2.1"
- resolved "https://registry.yarnpkg.com/ol-mapbox-style/-/ol-mapbox-style-8.2.1.tgz#0f0c252b6495853a137d7e4dd3f915fab664b356"
- integrity sha512-3kBBuZC627vDL8vnUdfVbCbfkhkcZj2kXPHQcuLhC4JJEA+XkEVEtEde8x8+AZctRbHwBkSiubTPaRukgLxIRw==
+ol-mapbox-style@^9.2.0:
+ version "9.7.0"
+ resolved "https://registry.npmjs.org/ol-mapbox-style/-/ol-mapbox-style-9.7.0.tgz#38a4f7abc8f0a94f378dcdb7cefdcc69ca3f6287"
+ integrity sha512-YX3u8FBJHsRHaoGxmd724Mp5WPTuV7wLQW6zZhcihMuInsSdCX1EiZfU+8IAL7jG0pbgl5YgC0aWE/MXJcUXxg==
dependencies:
"@mapbox/mapbox-gl-style-spec" "^13.23.1"
mapbox-to-css-font "^2.4.1"
-ol@^6.9:
- version "6.15.1"
- resolved "https://registry.yarnpkg.com/ol/-/ol-6.15.1.tgz#364f459939ef71f970b2376a821a896529f65e3a"
- integrity sha512-ZG2CKTpJ8Q+tPywYysVwPk+yevwJzlbwjRKhoCvd7kLVWMbfBl1O/+Kg/yrZZrhG9FNXbFH4GeOZ5yVRqo3P4w==
+ol@^7.1:
+ version "7.3.0"
+ resolved "https://registry.npmjs.org/ol/-/ol-7.3.0.tgz#7ffb5f258dafa4a3e218208aad9054d61f6fe786"
+ integrity sha512-08vJE4xITKPazQ9qJjeqYjRngnM9s+1eSv219Pdlrjj3LpLqjEH386ncq+76Dw1oGPGR8eLVEePk7FEd9XqqMw==
dependencies:
- geotiff "2.0.4"
- ol-mapbox-style "^8.0.5"
+ earcut "^2.2.3"
+ geotiff "^2.0.7"
+ ol-mapbox-style "^9.2.0"
pbf "3.2.1"
rbush "^3.0.1"
@@ -18592,6 +18595,11 @@ quick-lru@^5.1.1:
resolved "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
+quick-lru@^6.1.1:
+ version "6.1.1"
+ resolved "https://registry.npmjs.org/quick-lru/-/quick-lru-6.1.1.tgz#f8e5bf9010376c126c80c1a62827a526c0e60adf"
+ integrity sha512-S27GBT+F0NTRiehtbrgaSE1idUAJ5bX8dPAQTdylEyNlrdcH5X4Lz7Edz3DYzecbsCluD5zO8ZNEe04z3D3u6Q==
+
quickselect@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/quickselect/-/quickselect-2.0.0.tgz#f19680a486a5eefb581303e023e98faaf25dd018"
@@ -19370,7 +19378,7 @@ regenerate@^1.4.2:
resolved "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a"
integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==
-regenerator-runtime@^0.13.11, regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7:
+regenerator-runtime@^0.13.11, regenerator-runtime@^0.13.7:
version "0.13.11"
resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==
@@ -21902,10 +21910,10 @@ typed-array-length@^1.0.4:
for-each "^0.3.3"
is-typed-array "^1.1.9"
-typed-function@^2.0.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/typed-function/-/typed-function-2.1.0.tgz#ded6f8a442ba8749ff3fe75bc41419c8d46ccc3f"
- integrity sha512-bctQIOqx2iVbWGDGPWwIm18QScpu2XRmkC19D8rQGFsjKSgteq/o1hTZvIG/wuDq8fanpBDrLkLq+aEN/6y5XQ==
+typed-function@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.npmjs.org/typed-function/-/typed-function-4.1.0.tgz#da4bdd8a6d19a89e22732f75e4a410860aaf9712"
+ integrity sha512-DGwUl6cioBW5gw2L+6SMupGwH/kZOqivy17E4nsh1JI9fKF87orMmlQx3KISQPmg3sfnOUGlwVkroosvgddrlg==
typedarray-to-buffer@^3.1.5:
version "3.1.5"
@@ -22382,7 +22390,7 @@ uuid@^8.3.2:
resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
-uuid@^9.0.0:
+uuid@^9.0, uuid@^9.0.0:
version "9.0.0"
resolved "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5"
integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==
From 80796c697158d457850535ea57f3275698eb9519 Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Wed, 5 Apr 2023 14:49:38 -0400
Subject: [PATCH 23/51] [bugfix] dicom-microscopy tool
---
.../dicom-microscopy/src/DicomMicroscopyViewport.css | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/extensions/dicom-microscopy/src/DicomMicroscopyViewport.css b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.css
index 0317d716d9..79cf2ed24f 100644
--- a/extensions/dicom-microscopy/src/DicomMicroscopyViewport.css
+++ b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.css
@@ -1,9 +1,13 @@
+.DicomMicroscopyViewer .ol-overviewmap.ol-unselectable.ol-control {
+ position: absolute;
+ bottom: 0;
+}
.DicomMicroscopyViewer .ol-overviewmap .ol-overviewmap-map {
width: 300px !important;
height: 200px !important;
- background: rgba(0,0,0,0.3);
+ background: rgba(0, 0, 0, 0.3);
}
-.DicomMicroscopyViewer .ol-tooltip {
+.DicomMicroscopyViewer .ol-tooltip {
font-size: 16px !important;
}
From ea94c03ab393592fbdb3584078659771d0dc3a29 Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Wed, 5 Apr 2023 15:44:52 -0400
Subject: [PATCH 24/51] [bugfix] dicom microscopy annotations
---
.../src/DicomMicroscopyViewport.tsx | 58 +++++++++++++++++++
extensions/dicom-microscopy/src/index.tsx | 3 +-
.../src/tools/viewerManager.js | 20 ++++++-
3 files changed, 77 insertions(+), 4 deletions(-)
diff --git a/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
index ae35ca0df4..e6f82e61a4 100644
--- a/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
+++ b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
@@ -46,8 +46,47 @@ class DicomMicroscopyViewport extends Component {
// other props from wrapping component
servicesManager: PropTypes.object,
extensionManager: PropTypes.object,
+ commandsManager: PropTypes.object,
};
+ /**
+ * Get the nearest ROI from the mouse click point
+ *
+ * @param event
+ * @param autoselect
+ * @returns
+ */
+ getNearbyROI(event: Event, autoselect = true) {
+ const _drawingSource = Object.getOwnPropertySymbols(this.viewer).find(
+ p => p.description == 'drawingSource'
+ );
+ const _pyramid = Object.getOwnPropertySymbols(this.viewer).find(
+ p => p.description == 'pyramid'
+ );
+ const _map = Object.getOwnPropertySymbols(this.viewer).find(
+ p => p.description == 'map'
+ );
+ const _affine = Object.getOwnPropertySymbols(this.viewer).find(
+ p => p.description == 'affine'
+ );
+ const feature = this.viewer[_drawingSource].getClosestFeatureToCoordinate(
+ this.viewer[_map].getEventCoordinate(event)
+ );
+
+ if (feature) {
+ const roiAnnotation = this.viewer._getROIFromFeature(
+ feature,
+ this.viewer[_pyramid].metadata,
+ this.viewer[_affine]
+ );
+ if (roiAnnotation && autoselect) {
+ microscopyManager.selectAnnotation(roiAnnotation);
+ }
+ return roiAnnotation;
+ }
+ return null;
+ }
+
// install the microscopy renderer into the web page.
// you should only do this once.
installOpenLayersRenderer(container, displaySet) {
@@ -164,6 +203,25 @@ class DicomMicroscopyViewport extends Component {
StudyInstanceUID,
SeriesInstanceUID
);
+
+ this.managedViewer.addContextMenuCallback((event: Event) => {
+ const roiAnnotationNearBy = this.getNearbyROI(event);
+
+ // TODO: refactor this after Bill's changes on ContextMenu feature get merged
+ this.props.commandsManager.runCommand(
+ 'showViewerContextMenu',
+ {
+ menuName: 'microscopyContextMenu',
+ event,
+ container,
+ viewer: this.viewer,
+ managedViewer: this.managedViewer,
+ viewportIndex: this.props.viewportIndex,
+ roiAnnotationNearBy,
+ },
+ 'MICROSCOPY'
+ );
+ });
};
console.debug('Loading viewer metadata', displaySet);
diff --git a/extensions/dicom-microscopy/src/index.tsx b/extensions/dicom-microscopy/src/index.tsx
index a38da0c110..cfccb9904e 100644
--- a/extensions/dicom-microscopy/src/index.tsx
+++ b/extensions/dicom-microscopy/src/index.tsx
@@ -35,7 +35,7 @@ export default {
* {name, component} object. Example of a viewport module is the CornerstoneViewport
* that is provided by the Cornerstone extension in OHIF.
*/
- getViewportModule({ servicesManager, extensionManager }) {
+ getViewportModule({ servicesManager, extensionManager, commandsManager }) {
/**
*
* @param props {*}
@@ -57,6 +57,7 @@ export default {
{
viewportGridService.setActiveViewportIndex(viewportIndex);
diff --git a/extensions/dicom-microscopy/src/tools/viewerManager.js b/extensions/dicom-microscopy/src/tools/viewerManager.js
index 39145bbcde..6bcbfb0525 100644
--- a/extensions/dicom-microscopy/src/tools/viewerManager.js
+++ b/extensions/dicom-microscopy/src/tools/viewerManager.js
@@ -40,6 +40,20 @@ class ViewerManager extends Publisher {
this.onRoiRemoved = this.roiRemovedHandler.bind(this);
this.contextMenuCallback = () => {};
+ // init symbols
+ this._drawingSource = Object.getOwnPropertySymbols(this.viewer).find(
+ p => p.description == 'drawingSource'
+ );
+ this._pyramid = Object.getOwnPropertySymbols(this.viewer).find(
+ p => p.description == 'pyramid'
+ );
+ this._map = Object.getOwnPropertySymbols(this.viewer).find(
+ p => p.description == 'map'
+ );
+ this._affine = Object.getOwnPropertySymbols(this.viewer).find(
+ p => p.description == 'affine'
+ );
+
this.registerEvents();
this.activateDefaultInteractions();
}
@@ -369,7 +383,7 @@ class ViewerManager extends Publisher {
}
_jumpToPoint(coord) {
- const pyramid = this.viewer.imageMetadata;
+ const pyramid = this.viewer[this._pyramid].metadata;
const mappedCoord = coordinateFormatScoord3d2Geometry(coord, pyramid);
const view = this._getMapView();
@@ -378,7 +392,7 @@ class ViewerManager extends Publisher {
}
_jumpToPolyline(coord) {
- const pyramid = this.viewer.imageMetadata;
+ const pyramid = this.viewer[this._pyramid].metadata;
const mappedCoord = coordinateFormatScoord3d2Geometry(coord, pyramid);
const view = this._getMapView();
@@ -394,7 +408,7 @@ class ViewerManager extends Publisher {
}
_jumpToPolygonOrEllipse(coordinates) {
- const pyramid = this.viewer.imageMetadata;
+ const pyramid = this.viewer[this._pyramid].metadata;
let minX = Infinity;
let maxX = -Infinity;
From 518a83d40983b1718e159abf335a554366041b74 Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Wed, 5 Apr 2023 16:40:06 -0400
Subject: [PATCH 25/51] [fix] mixed-content blocking caused by BulkDataURI
---
.../dicom-microscopy/src/tools/viewerManager.js | 16 ++++++++--------
.../src/utils/cleanDenaturalizedDataset.ts | 11 +++++++++++
2 files changed, 19 insertions(+), 8 deletions(-)
diff --git a/extensions/dicom-microscopy/src/tools/viewerManager.js b/extensions/dicom-microscopy/src/tools/viewerManager.js
index 6bcbfb0525..53d13e4613 100644
--- a/extensions/dicom-microscopy/src/tools/viewerManager.js
+++ b/extensions/dicom-microscopy/src/tools/viewerManager.js
@@ -268,14 +268,14 @@ class ViewerManager extends Publisher {
},
},
],
- // [
- // 'dragZoom',
- // {
- // bindings: {
- // mouseButtons: ['right'],
- // },
- // },
- // ],
+ [
+ 'dragZoom',
+ {
+ bindings: {
+ mouseButtons: ['right'],
+ },
+ },
+ ],
['modify', {}],
];
this.activateInteractions(defaultInteractions);
diff --git a/extensions/dicom-microscopy/src/utils/cleanDenaturalizedDataset.ts b/extensions/dicom-microscopy/src/utils/cleanDenaturalizedDataset.ts
index dd44c0487d..91d72e3e28 100644
--- a/extensions/dicom-microscopy/src/utils/cleanDenaturalizedDataset.ts
+++ b/extensions/dicom-microscopy/src/utils/cleanDenaturalizedDataset.ts
@@ -39,6 +39,17 @@ export default function cleanDenaturalizedDataset(obj: any): any {
} else if (Array.isArray(obj[key].Value) && obj[key].vr) {
if (obj[key].Value.length === 1 && obj[key].Value[0].BulkDataURI) {
obj[key].BulkDataURI = obj[key].Value[0].BulkDataURI;
+
+ // prevent mixed-content blockage
+ if (
+ window.location.protocol === 'https:' &&
+ obj[key].BulkDataURI.startsWith('http:')
+ ) {
+ obj[key].BulkDataURI = obj[key].BulkDataURI.replace(
+ 'http:',
+ 'https:'
+ );
+ }
delete obj[key].Value;
} else if (vrNumerics.includes(obj[key].vr)) {
obj[key].Value = obj[key].Value.map(v => +v);
From 9a6d5dddc41ce0104eb180125f29581f7d9209bb Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Wed, 5 Apr 2023 16:45:51 -0400
Subject: [PATCH 26/51] [fix] microscopy measurements panel - center button
---
.../MicroscopyPanel/MicroscopyPanel.tsx | 28 ++++++++++---------
1 file changed, 15 insertions(+), 13 deletions(-)
diff --git a/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx b/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx
index 64841825cc..1a0ed66214 100644
--- a/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx
+++ b/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx
@@ -567,19 +567,21 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) {
onEdit={onMeasurementItemEditHandler}
/>
-
- {promptSave && (
-
- {props.t('Save Measurements')}
-
- )}
- {/*
- {props.t('Reject latest report')}
- */}
-
+
+
+ {promptSave && (
+
+ {props.t('Create Report')}
+
+ )}
+ {/*
+ {props.t('Reject latest report')}
+ */}
+
+
>
);
}
From 7ed5ac4335410c80aeb9a5e287ff6bc6659d7b8b Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Wed, 5 Apr 2023 16:53:19 -0400
Subject: [PATCH 27/51] [fix] microscopy measurements panel - styling
---
.../components/MicroscopyPanel/MicroscopyPanel.tsx | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx b/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx
index 1a0ed66214..c17336a2fa 100644
--- a/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx
+++ b/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx
@@ -553,6 +553,8 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) {
};
});
+ const disabled = data.length === 0;
+
return (
<>
{promptSave && (
-
+
{props.t('Create Report')}
)}
From be75c04a17804024b048a83125ae7fe5e4a753c7 Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Wed, 5 Apr 2023 17:22:21 -0400
Subject: [PATCH 28/51] [fix] microscopy - controls
---
.../src/DicomMicroscopyViewport.css | 347 +++++++++++++++++-
.../src/DicomMicroscopyViewport.tsx | 2 +-
2 files changed, 344 insertions(+), 5 deletions(-)
diff --git a/extensions/dicom-microscopy/src/DicomMicroscopyViewport.css b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.css
index 79cf2ed24f..7e10bb501f 100644
--- a/extensions/dicom-microscopy/src/DicomMicroscopyViewport.css
+++ b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.css
@@ -1,13 +1,352 @@
-.DicomMicroscopyViewer .ol-overviewmap.ol-unselectable.ol-control {
+.DicomMicroscopyViewer .ol-box {
+ box-sizing: border-box;
+ border-radius: 2px;
+ border: 1.5px solid var(--ol-background-color);
+ background-color: var(--ol-partial-background-color);
+}
+
+.DicomMicroscopyViewer .ol-mouse-position {
+ top: 8px;
+ right: 8px;
+ position: absolute;
+}
+
+.DicomMicroscopyViewer .ol-scale-line {
+ background: var(--ol-partial-background-color);
+ border-radius: 4px;
+ bottom: 8px;
+ left: 8px;
+ padding: 2px;
+ position: absolute;
+}
+
+.DicomMicroscopyViewer .ol-scale-line-inner {
+ border: 1px solid var(--ol-subtle-foreground-color);
+ border-top: none;
+ color: var(--ol-foreground-color);
+ font-size: 10px;
+ text-align: center;
+ margin: 1px;
+ will-change: contents, width;
+ transition: all 0.25s;
+}
+
+.DicomMicroscopyViewer .ol-scale-bar {
+ position: absolute;
+ bottom: 8px;
+ left: 8px;
+}
+
+.DicomMicroscopyViewer .ol-scale-bar-inner {
+ display: flex;
+}
+
+.DicomMicroscopyViewer .ol-scale-step-marker {
+ width: 1px;
+ height: 15px;
+ background-color: var(--ol-foreground-color);
+ float: right;
+ z-index: 10;
+}
+
+.DicomMicroscopyViewer .ol-scale-step-text {
+ position: absolute;
+ bottom: -5px;
+ font-size: 10px;
+ z-index: 11;
+ color: var(--ol-foreground-color);
+ text-shadow: -1.5px 0 var(--ol-partial-background-color),
+ 0 1.5px var(--ol-partial-background-color),
+ 1.5px 0 var(--ol-partial-background-color),
+ 0 -1.5px var(--ol-partial-background-color);
+}
+
+.DicomMicroscopyViewer .ol-scale-text {
+ position: absolute;
+ font-size: 12px;
+ text-align: center;
+ bottom: 25px;
+ color: var(--ol-foreground-color);
+ text-shadow: -1.5px 0 var(--ol-partial-background-color),
+ 0 1.5px var(--ol-partial-background-color),
+ 1.5px 0 var(--ol-partial-background-color),
+ 0 -1.5px var(--ol-partial-background-color);
+}
+
+.DicomMicroscopyViewer .ol-scale-singlebar {
+ position: relative;
+ height: 10px;
+ z-index: 9;
+ box-sizing: border-box;
+ border: 1px solid var(--ol-foreground-color);
+}
+
+.DicomMicroscopyViewer .ol-scale-singlebar-even {
+ background-color: var(--ol-subtle-foreground-color);
+}
+
+.DicomMicroscopyViewer .ol-scale-singlebar-odd {
+ background-color: var(--ol-background-color);
+}
+
+.DicomMicroscopyViewer .ol-unsupported {
+ display: none;
+}
+
+.DicomMicroscopyViewer .ol-viewport,
+.DicomMicroscopyViewer .ol-unselectable {
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
+ -webkit-tap-highlight-color: transparent;
+}
+
+.DicomMicroscopyViewer .ol-viewport canvas {
+ all: unset;
+}
+
+.DicomMicroscopyViewer .ol-selectable {
+ -webkit-touch-callout: default;
+ -webkit-user-select: text;
+ -moz-user-select: text;
+ user-select: text;
+}
+
+.DicomMicroscopyViewer .ol-grabbing {
+ cursor: -webkit-grabbing;
+ cursor: -moz-grabbing;
+ cursor: grabbing;
+}
+
+.DicomMicroscopyViewer .ol-grab {
+ cursor: move;
+ cursor: -webkit-grab;
+ cursor: -moz-grab;
+ cursor: grab;
+}
+
+.DicomMicroscopyViewer .ol-control {
position: absolute;
+ background-color: var(--ol-subtle-background-color);
+ border-radius: 4px;
+}
+
+.DicomMicroscopyViewer .ol-zoom {
+ top: 0.5em;
+ left: 0.5em;
+}
+
+.DicomMicroscopyViewer .ol-rotate {
+ top: 0.5em;
+ right: 0.5em;
+ transition: opacity 0.25s linear, visibility 0s linear;
+}
+
+.DicomMicroscopyViewer .ol-rotate.ol-hidden {
+ opacity: 0;
+ visibility: hidden;
+ transition: opacity 0.25s linear, visibility 0s linear 0.25s;
+}
+
+.DicomMicroscopyViewer .ol-zoom-extent {
+ top: 4.643em;
+ left: 0.5em;
+}
+
+.DicomMicroscopyViewer .ol-full-screen {
+ right: 0.5em;
+ top: 0.5em;
+}
+
+.DicomMicroscopyViewer .ol-control button {
+ display: block;
+ margin: 1px;
+ padding: 0;
+ color: var(--ol-subtle-foreground-color);
+ font-weight: bold;
+ text-decoration: none;
+ font-size: inherit;
+ text-align: center;
+ height: 1.375em;
+ width: 1.375em;
+ line-height: 0.4em;
+ background-color: var(--ol-background-color);
+ border: none;
+ border-radius: 2px;
+}
+
+.DicomMicroscopyViewer .ol-control button::-moz-focus-inner {
+ border: none;
+ padding: 0;
+}
+
+.DicomMicroscopyViewer .ol-zoom-extent button {
+ line-height: 1.4em;
+}
+
+.DicomMicroscopyViewer .ol-compass {
+ display: block;
+ font-weight: normal;
+ will-change: transform;
+}
+
+.DicomMicroscopyViewer .ol-touch .ol-control button {
+ font-size: 1.5em;
+}
+
+.DicomMicroscopyViewer .ol-touch .ol-zoom-extent {
+ top: 5.5em;
+}
+
+.DicomMicroscopyViewer .ol-control button:hover,
+.DicomMicroscopyViewer .ol-control button:focus {
+ text-decoration: none;
+ outline: 1px solid var(--ol-subtle-foreground-color);
+ color: var(--ol-foreground-color);
+}
+
+.DicomMicroscopyViewer .ol-zoom .ol-zoom-in {
+ border-radius: 2px 2px 0 0;
+}
+
+.DicomMicroscopyViewer .ol-zoom .ol-zoom-out {
+ border-radius: 0 0 2px 2px;
+}
+
+.DicomMicroscopyViewer .ol-attribution {
+ text-align: right;
+ bottom: 0.5em;
+ right: 0.5em;
+ max-width: calc(100% - 1.3em);
+ display: flex;
+ flex-flow: row-reverse;
+ align-items: center;
+}
+
+.DicomMicroscopyViewer .ol-attribution a {
+ color: var(--ol-subtle-foreground-color);
+ text-decoration: none;
+}
+
+.DicomMicroscopyViewer .ol-attribution ul {
+ margin: 0;
+ padding: 1px 0.5em;
+ color: var(--ol-foreground-color);
+ text-shadow: 0 0 2px var(--ol-background-color);
+ font-size: 12px;
+}
+
+.DicomMicroscopyViewer .ol-attribution li {
+ display: inline;
+ list-style: none;
+}
+
+.DicomMicroscopyViewer .ol-attribution li:not(:last-child):after {
+ content: ' ';
+}
+
+.DicomMicroscopyViewer .ol-attribution img {
+ max-height: 2em;
+ max-width: inherit;
+ vertical-align: middle;
+}
+
+.DicomMicroscopyViewer .ol-attribution button {
+ flex-shrink: 0;
+}
+
+.DicomMicroscopyViewer .ol-attribution.ol-collapsed ul {
+ display: none;
+}
+
+.DicomMicroscopyViewer .ol-attribution:not(.ol-collapsed) {
+ background: var(--ol-partial-background-color);
+}
+
+.DicomMicroscopyViewer .ol-attribution.ol-uncollapsible {
+ bottom: 0;
+ right: 0;
+ border-radius: 4px 0 0;
+}
+
+.DicomMicroscopyViewer .ol-attribution.ol-uncollapsible img {
+ margin-top: -0.2em;
+ max-height: 1.6em;
+}
+
+.DicomMicroscopyViewer .ol-attribution.ol-uncollapsible button {
+ display: none;
+}
+
+.DicomMicroscopyViewer .ol-zoomslider {
+ top: 4.5em;
+ left: 0.5em;
+ height: 200px;
+}
+
+.DicomMicroscopyViewer .ol-zoomslider button {
+ position: relative;
+ height: 10px;
+}
+
+.DicomMicroscopyViewer .ol-touch .ol-zoomslider {
+ top: 5.5em;
+}
+
+.DicomMicroscopyViewer .ol-overviewmap {
+ left: 0.5em;
+ bottom: 0.5em;
+}
+
+.DicomMicroscopyViewer .ol-overviewmap.ol-uncollapsible {
bottom: 0;
+ left: 0;
+ border-radius: 0 4px 0 0;
+}
+
+.DicomMicroscopyViewer .ol-overviewmap .ol-overviewmap-map,
+.DicomMicroscopyViewer .ol-overviewmap button {
+ display: block;
}
+
.DicomMicroscopyViewer .ol-overviewmap .ol-overviewmap-map {
- width: 300px !important;
- height: 200px !important;
- background: rgba(0, 0, 0, 0.3);
+ border: 1px solid var(--ol-subtle-foreground-color);
+ height: 150px;
+ width: 150px;
+}
+
+.DicomMicroscopyViewer .ol-overviewmap:not(.ol-collapsed) button {
+ bottom: 0;
+ left: 0;
+ position: absolute;
+}
+
+.DicomMicroscopyViewer .ol-overviewmap.ol-collapsed .ol-overviewmap-map,
+.DicomMicroscopyViewer .ol-overviewmap.ol-uncollapsible button {
+ display: none;
}
+.DicomMicroscopyViewer .ol-overviewmap:not(.ol-collapsed) {
+ background: var(--ol-subtle-background-color);
+}
+
+.DicomMicroscopyViewer .ol-overviewmap-box {
+ border: 1.5px dotted var(--ol-subtle-foreground-color);
+}
+
+.DicomMicroscopyViewer .ol-overviewmap .ol-overviewmap-box:hover {
+ cursor: move;
+}
+
+@layout-header-background: #007ea3;
+@primary-color: #007ea3;
+@processing-color: #8cb8c6;
+@success-color: #3f9c35;
+@warning-color: #eeaf30;
+@error-color: #96172e;
+@font-size-base: 14px;
+
.DicomMicroscopyViewer .ol-tooltip {
font-size: 16px !important;
}
diff --git a/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
index e6f82e61a4..90b938e8ce 100644
--- a/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
+++ b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
@@ -174,7 +174,7 @@ class DicomMicroscopyViewport extends Component {
client,
metadata: volumeImages,
retrieveRendered: false,
- controls: ['overview'],
+ controls: ['overview', 'position'],
};
this.viewer = new microscopyViewer(options);
From 69d54a9b73678736ff551bf104d10dc633207308 Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Tue, 18 Apr 2023 09:48:08 -0400
Subject: [PATCH 29/51] fix local loading of microscopy
---
.../src/DicomMicroscopyViewport.tsx | 5 +-
.../src/utils/dicomWebClient.ts | 98 ++++++++++++++++++-
platform/viewer/src/routes/Local/Local.tsx | 28 +++++-
3 files changed, 121 insertions(+), 10 deletions(-)
diff --git a/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
index 90b938e8ce..9bee4e308f 100644
--- a/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
+++ b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
@@ -99,7 +99,10 @@ class DicomMicroscopyViewport extends Component {
);
const microscopyViewer = DicomMicroscopyViewer.VolumeImageViewer;
- const client = getDicomWebClient();
+ const client = getDicomWebClient({
+ extensionManager: this.props.extensionManager,
+ servicesManager: this.props.servicesManager,
+ });
// Parse, format, and filter metadata
const volumeImages: any[] = [];
diff --git a/extensions/dicom-microscopy/src/utils/dicomWebClient.ts b/extensions/dicom-microscopy/src/utils/dicomWebClient.ts
index 3ecc15b0c3..8174d6c26c 100644
--- a/extensions/dicom-microscopy/src/utils/dicomWebClient.ts
+++ b/extensions/dicom-microscopy/src/utils/dicomWebClient.ts
@@ -1,8 +1,96 @@
-import * as DICOMwebClient from 'dicomweb-client';
+import { api } from 'dicomweb-client';
+import { errorHandler, DicomMetadataStore } from '@ohif/core';
+
+/**
+ * create a DICOMwebClient object to be used by Dicom Microscopy Viewer
+ *
+ * Referenced the code from `/extensions/default/src/DicomWebDataSource/index.js`
+ *
+ * @param param0
+ * @returns
+ */
+export default function getDicomWebClient({
+ extensionManager,
+ servicesManager,
+}) {
+ const dataSourceConfig = window.config.dataSources.find(
+ ds => ds.sourceName === extensionManager.activeDataSource
+ );
+ const { userAuthenticationService } = servicesManager.services;
+
+ const { wadoRoot, staticWado, singlepart } = dataSourceConfig.configuration;
+
+ const wadoConfig = {
+ url: wadoRoot || '/dicomlocal',
+ staticWado,
+ singlepart,
+ headers: userAuthenticationService.getAuthorizationHeader(),
+ errorInterceptor: errorHandler.getHTTPErrorHandler(),
+ };
+
+ const client = new api.DICOMwebClient(wadoConfig);
+ client.wadoURL = wadoConfig.url;
+
+ if (extensionManager.activeDataSource === 'dicomlocal') {
+ /**
+ * For local data source, override the retrieveInstanceFrames() method of the
+ * dicomweb-client to retrieve image data from memory cached metadata.
+ * Other methods of the client doesn't matter, as we are feeding the DMV
+ * with the series metadata already.
+ *
+ * @param {Object} options
+ * @param {String} options.studyInstanceUID - Study Instance UID
+ * @param {String} options.seriesInstanceUID - Series Instance UID
+ * @param {String} options.sopInstanceUID - SOP Instance UID
+ * @param {String} options.frameNumbers - One-based indices of Frame Items
+ * @param {Object} [options.queryParams] - HTTP query parameters
+ * @returns {ArrayBuffer[]} Rendered Frame Items as byte arrays
+ */
+ //
+ client.retrieveInstanceFrames = async options => {
+ if (!('studyInstanceUID' in options)) {
+ throw new Error(
+ 'Study Instance UID is required for retrieval of instance frames'
+ );
+ }
+ if (!('seriesInstanceUID' in options)) {
+ throw new Error(
+ 'Series Instance UID is required for retrieval of instance frames'
+ );
+ }
+ if (!('sopInstanceUID' in options)) {
+ throw new Error(
+ 'SOP Instance UID is required for retrieval of instance frames'
+ );
+ }
+ if (!('frameNumbers' in options)) {
+ throw new Error(
+ 'frame numbers are required for retrieval of instance frames'
+ );
+ }
+ console.log(
+ `retrieve frames ${options.frameNumbers.toString()} of instance ${
+ options.sopInstanceUID
+ }`
+ );
+
+ const instance = DicomMetadataStore.getInstance(
+ options.studyInstanceUID,
+ options.seriesInstanceUID,
+ options.sopInstanceUID
+ );
+
+ const frameNumbers = Array.isArray(options.frameNumbers)
+ ? options.frameNumbers
+ : options.frameNumbers.split(',');
+
+ return frameNumbers.map(fr =>
+ Array.isArray(instance.PixelData)
+ ? instance.PixelData[+fr - 1]
+ : instance.PixelData
+ );
+ };
+ }
-export default function getDicomWebClient() {
- const url = window.config.dataSources[0].configuration.wadoRoot;
- const client = new DICOMwebClient.api.DICOMwebClient({ url });
- client.wadoURL = url;
return client;
}
diff --git a/platform/viewer/src/routes/Local/Local.tsx b/platform/viewer/src/routes/Local/Local.tsx
index af0dea0701..1474544ca2 100644
--- a/platform/viewer/src/routes/Local/Local.tsx
+++ b/platform/viewer/src/routes/Local/Local.tsx
@@ -1,7 +1,7 @@
import React, { useEffect, useRef } from 'react';
import classnames from 'classnames';
import { useNavigate } from 'react-router-dom';
-import { MODULE_TYPES } from '@ohif/core';
+import { DicomMetadataStore, MODULE_TYPES } from '@ohif/core';
import Dropzone from 'react-dropzone';
import filesToStudies from './filesToStudies';
@@ -62,10 +62,30 @@ function Local() {
const onDrop = async acceptedFiles => {
const studies = await filesToStudies(acceptedFiles, dataSource);
- // Todo: navigate to work list and let user select a mode
+
const query = new URLSearchParams();
- studies.forEach(id => query.append('StudyInstanceUIDs', id));
- navigate(`/viewer/dicomlocal?${decodeURIComponent(query.toString())}`);
+
+ const smStudies = studies.filter(id => {
+ const study = DicomMetadataStore.getStudy(id);
+ return (
+ study.series.findIndex(
+ s => s.Modality === 'SM' || s.instances[0].Modality === 'SM'
+ ) >= 0
+ );
+ });
+
+ if (smStudies.length > 0) {
+ smStudies.forEach(id => query.append('StudyInstanceUIDs', id));
+
+ navigate(
+ `/microscopy/dicomlocal?${decodeURIComponent(query.toString())}`
+ );
+ } else {
+ // Todo: navigate to work list and let user select a mode
+ studies.forEach(id => query.append('StudyInstanceUIDs', id));
+
+ navigate(`/viewer/dicomlocal?${decodeURIComponent(query.toString())}`);
+ }
};
// Set body style
From f357066428f33b7eb081d038be68468f934f9fe8 Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Tue, 18 Apr 2023 10:03:29 -0400
Subject: [PATCH 30/51] fix local loading of dicom microscopy
---
platform/viewer/src/routes/Local/Local.tsx | 45 +++++++++++++---------
1 file changed, 26 insertions(+), 19 deletions(-)
diff --git a/platform/viewer/src/routes/Local/Local.tsx b/platform/viewer/src/routes/Local/Local.tsx
index 1474544ca2..72994e8764 100644
--- a/platform/viewer/src/routes/Local/Local.tsx
+++ b/platform/viewer/src/routes/Local/Local.tsx
@@ -60,32 +60,39 @@ function Local() {
const firstLocalDataSource = localDataSources[0];
const dataSource = firstLocalDataSource.createDataSource({});
+ const microscopyExtensionLoaded = extensionManager.registeredExtensionIds.includes(
+ '@ohif/extension-dicom-microscopy'
+ );
+
const onDrop = async acceptedFiles => {
const studies = await filesToStudies(acceptedFiles, dataSource);
const query = new URLSearchParams();
- const smStudies = studies.filter(id => {
- const study = DicomMetadataStore.getStudy(id);
- return (
- study.series.findIndex(
- s => s.Modality === 'SM' || s.instances[0].Modality === 'SM'
- ) >= 0
- );
- });
-
- if (smStudies.length > 0) {
- smStudies.forEach(id => query.append('StudyInstanceUIDs', id));
+ if (microscopyExtensionLoaded) {
+ const smStudies = studies.filter(id => {
+ const study = DicomMetadataStore.getStudy(id);
+ return (
+ study.series.findIndex(
+ s => s.Modality === 'SM' || s.instances[0].Modality === 'SM'
+ ) >= 0
+ );
+ });
+
+ if (smStudies.length > 0) {
+ smStudies.forEach(id => query.append('StudyInstanceUIDs', id));
+
+ navigate(
+ `/microscopy/dicomlocal?${decodeURIComponent(query.toString())}`
+ );
+ return;
+ }
+ }
- navigate(
- `/microscopy/dicomlocal?${decodeURIComponent(query.toString())}`
- );
- } else {
- // Todo: navigate to work list and let user select a mode
- studies.forEach(id => query.append('StudyInstanceUIDs', id));
+ // Todo: navigate to work list and let user select a mode
+ studies.forEach(id => query.append('StudyInstanceUIDs', id));
- navigate(`/viewer/dicomlocal?${decodeURIComponent(query.toString())}`);
- }
+ navigate(`/viewer/dicomlocal?${decodeURIComponent(query.toString())}`);
};
// Set body style
From 3402cd08d2f0fecb00567025f64a195221047370 Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Tue, 18 Apr 2023 10:07:24 -0400
Subject: [PATCH 31/51] fix typo - indexof to indexOf
---
.../core/src/services/DicomMetadataStore/createStudyMetadata.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/platform/core/src/services/DicomMetadataStore/createStudyMetadata.js b/platform/core/src/services/DicomMetadataStore/createStudyMetadata.js
index 4822b616c9..dc43aa503f 100644
--- a/platform/core/src/services/DicomMetadataStore/createStudyMetadata.js
+++ b/platform/core/src/services/DicomMetadataStore/createStudyMetadata.js
@@ -27,7 +27,7 @@ function createStudyMetadata(StudyInstanceUID) {
const series = createSeriesMetadata([instance]);
this.series.push(series);
const { Modality } = series;
- if (this.ModalitiesInStudy.indexof(Modality) === -1) {
+ if (this.ModalitiesInStudy.indexOf(Modality) === -1) {
this.ModalitiesInStudy.push(Modality);
}
}
From a9a19e872ecb03eedf4d27ccdc71eb02e8115d12 Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Tue, 18 Apr 2023 14:38:39 -0400
Subject: [PATCH 32/51] [fix] remove unused icons
---
.../public/assets/icons/tool-circle.svg | 3 --
.../assets/icons/tool-freehand-polygon.svg | 37 -------------------
.../public/assets/icons/tool-freehand.svg | 34 -----------------
.../public/assets/icons/tool-polygon.svg | 3 --
4 files changed, 77 deletions(-)
delete mode 100644 modes/microscopy/public/assets/icons/tool-circle.svg
delete mode 100644 modes/microscopy/public/assets/icons/tool-freehand-polygon.svg
delete mode 100644 modes/microscopy/public/assets/icons/tool-freehand.svg
delete mode 100644 modes/microscopy/public/assets/icons/tool-polygon.svg
diff --git a/modes/microscopy/public/assets/icons/tool-circle.svg b/modes/microscopy/public/assets/icons/tool-circle.svg
deleted file mode 100644
index e94375564e..0000000000
--- a/modes/microscopy/public/assets/icons/tool-circle.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
diff --git a/modes/microscopy/public/assets/icons/tool-freehand-polygon.svg b/modes/microscopy/public/assets/icons/tool-freehand-polygon.svg
deleted file mode 100644
index bfc1b0dcd5..0000000000
--- a/modes/microscopy/public/assets/icons/tool-freehand-polygon.svg
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/modes/microscopy/public/assets/icons/tool-freehand.svg b/modes/microscopy/public/assets/icons/tool-freehand.svg
deleted file mode 100644
index 2159f91505..0000000000
--- a/modes/microscopy/public/assets/icons/tool-freehand.svg
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/modes/microscopy/public/assets/icons/tool-polygon.svg b/modes/microscopy/public/assets/icons/tool-polygon.svg
deleted file mode 100644
index ff2f9e0299..0000000000
--- a/modes/microscopy/public/assets/icons/tool-polygon.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
From f165970c96004740e9dafdebf8fb7ff1a5d8bcde Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Tue, 18 Apr 2023 14:39:03 -0400
Subject: [PATCH 33/51] remove commented out lines from webpack configuration
---
.webpack/webpack.base.js | 5 -----
1 file changed, 5 deletions(-)
diff --git a/.webpack/webpack.base.js b/.webpack/webpack.base.js
index 93b024ce91..e6a43e3fcd 100644
--- a/.webpack/webpack.base.js
+++ b/.webpack/webpack.base.js
@@ -155,11 +155,6 @@ module.exports = (env, argv, { SRC_DIR, DIST_DIR }) => {
// Uncomment to generate bundle analyzer
// new BundleAnalyzerPlugin(),
],
- // enable experimental features, like webAssembly
- // experiments: {
- // asyncWebAssembly: true,
- // syncWebAssembly: true,
- // },
};
if (isProdBuild) {
From 49070d5bd8cef854c6f2fb1e5ce31a567051aadb Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Tue, 18 Apr 2023 14:41:07 -0400
Subject: [PATCH 34/51] platform/viewer/public/config/default.js - revert
accidental changes
---
platform/viewer/public/config/default.js | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/platform/viewer/public/config/default.js b/platform/viewer/public/config/default.js
index 0d88de828b..c0de3708a9 100644
--- a/platform/viewer/public/config/default.js
+++ b/platform/viewer/public/config/default.js
@@ -29,13 +29,13 @@ window.config = {
configuration: {
name: 'aws',
// old server
- wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado',
- qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
- wadoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
+ // wadoUriRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/wado',
+ // qidoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
+ // wadoRoot: 'https://server.dcmjs.org/dcm4chee-arc/aets/DCM4CHEE/rs',
// new server
- // wadoUriRoot: 'https://domvja9iplmyu.cloudfront.net/dicomweb',
- // qidoRoot: 'https://domvja9iplmyu.cloudfront.net/dicomweb',
- // wadoRoot: 'https://domvja9iplmyu.cloudfront.net/dicomweb',
+ wadoUriRoot: 'https://domvja9iplmyu.cloudfront.net/dicomweb',
+ qidoRoot: 'https://domvja9iplmyu.cloudfront.net/dicomweb',
+ wadoRoot: 'https://domvja9iplmyu.cloudfront.net/dicomweb',
qidoSupportsIncludeField: false,
supportsReject: false,
imageRendering: 'wadors',
From 01f368825e61c96ec0a48f0948b93e6762faf8b6 Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Tue, 18 Apr 2023 14:46:32 -0400
Subject: [PATCH 35/51] [refactor] redirecting to microscopy mode on Local mode
---
platform/viewer/src/routes/Local/Local.tsx | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/platform/viewer/src/routes/Local/Local.tsx b/platform/viewer/src/routes/Local/Local.tsx
index 72994e8764..ddd0834e35 100644
--- a/platform/viewer/src/routes/Local/Local.tsx
+++ b/platform/viewer/src/routes/Local/Local.tsx
@@ -64,12 +64,17 @@ function Local() {
'@ohif/extension-dicom-microscopy'
);
+ let modePath = 'viewer';
+
const onDrop = async acceptedFiles => {
const studies = await filesToStudies(acceptedFiles, dataSource);
const query = new URLSearchParams();
if (microscopyExtensionLoaded) {
+ // TODO: for microscopy, we are forcing microscopy mode, which is not ideal.
+ // we should make the local drag and drop navigate to the worklist and
+ // there user can select microscopy mode
const smStudies = studies.filter(id => {
const study = DicomMetadataStore.getStudy(id);
return (
@@ -82,17 +87,14 @@ function Local() {
if (smStudies.length > 0) {
smStudies.forEach(id => query.append('StudyInstanceUIDs', id));
- navigate(
- `/microscopy/dicomlocal?${decodeURIComponent(query.toString())}`
- );
- return;
+ modePath = 'microscopy';
}
}
// Todo: navigate to work list and let user select a mode
studies.forEach(id => query.append('StudyInstanceUIDs', id));
- navigate(`/viewer/dicomlocal?${decodeURIComponent(query.toString())}`);
+ navigate(`/${modePath}/dicomlocal?${decodeURIComponent(query.toString())}`);
};
// Set body style
From 1dc6a5f05c8f3c4c8c20027add7b5e63f40ad9de Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Tue, 18 Apr 2023 16:26:53 -0400
Subject: [PATCH 36/51] attribution to DMV and SLIM viewer
---
extensions/dicom-microscopy/README.md | 9 ++++++---
modes/microscopy/README.md | 11 ++++++++---
2 files changed, 14 insertions(+), 6 deletions(-)
diff --git a/extensions/dicom-microscopy/README.md b/extensions/dicom-microscopy/README.md
index 85f5a32cc4..572a528425 100644
--- a/extensions/dicom-microscopy/README.md
+++ b/extensions/dicom-microscopy/README.md
@@ -1,7 +1,10 @@
-# dicom-microscopy
+# OHIF extension for microscopy
+Adapter for *DICOM Microscopy Viewer* to get it integrated into OHIF Viewer.
-## Description
-OHIF extension for DICOM microscopy
+## Acknowledgements
+
+- [DICOM Microscopy Viewer](https://github.com/ImagingDataCommons/dicom-microscopy-viewer) is a Vanilla JS library for web-based visualization of DICOM VL Whole Slide Microscopy Image datasets and derived information.
+- [SLIM Viewer](https://github.com/imagingdatacommons/slim) is a single-page application for interactive visualization and annotation of digital whole slide microscopy images and derived image analysis results in standard DICOM format. The application is based on the dicom-microscopy-viewer JavaScript library and runs fully client side without any custom server components.
## Author
Bill Wallace, md-prog, Radical Imaging
diff --git a/modes/microscopy/README.md b/modes/microscopy/README.md
index 1ad98b7a7a..dc63fd2915 100644
--- a/modes/microscopy/README.md
+++ b/modes/microscopy/README.md
@@ -1,7 +1,12 @@
-# microscopy
+# OHIF mode for microscopy
+Mode for *DICOM VL Whole Slide Microscopy Image*.
+This mode uses [OHIF extension for microscopy](../../extensions/dicom-microscopy/).
-## Description
-OHIF mode for DICOM microscopy
+
+## Acknowledgements
+
+- [DICOM Microscopy Viewer](https://github.com/ImagingDataCommons/dicom-microscopy-viewer) is a Vanilla JS library for web-based visualization of DICOM VL Whole Slide Microscopy Image datasets and derived information.
+- [SLIM Viewer](https://github.com/imagingdatacommons/slim) is a single-page application for interactive visualization and annotation of digital whole slide microscopy images and derived image analysis results in standard DICOM format. The application is based on the dicom-microscopy-viewer JavaScript library and runs fully client side without any custom server components.
## Author
Bill Wallace, md-prog, Radical Imaging
From 7b4c436c621d56ce49b9bea0ba8d764ffba5aff8 Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Tue, 18 Apr 2023 17:32:07 -0400
Subject: [PATCH 37/51] [fix] code review feedbacks
---
.../src/DicomMicroscopySRSopClassHandler.js | 14 +++--
.../src/DicomMicroscopyViewport.tsx | 52 ++++++++--------
.../MicroscopyDeleteDialog.js | 23 -------
.../MicroscopyDeleteDialog.styl | 2 -
.../MicroscopyLabelDialog.js | 61 -------------------
.../MicroscopyLabelDialog.styl | 3 -
.../MicroscopyPanel/MicroscopyPanel.tsx | 39 ++++++------
.../dicom-microscopy/src/getCommandsModule.ts | 22 +++----
.../dicom-microscopy/src/getPanelModule.tsx | 10 ++-
extensions/dicom-microscopy/src/index.tsx | 12 ++++
.../MicroscopyService.js} | 55 ++++++++++-------
.../src/tools/viewerManager.js | 24 +++-----
.../dicom-microscopy/src/utils/Publisher.js | 53 ----------------
.../src/utils/RoiAnnotation.js | 13 ++--
.../dicom-microscopy/src/utils/loadSR.js | 9 +--
15 files changed, 127 insertions(+), 265 deletions(-)
delete mode 100644 extensions/dicom-microscopy/src/components/MicroscopyDeleteDialog/MicroscopyDeleteDialog.js
delete mode 100644 extensions/dicom-microscopy/src/components/MicroscopyDeleteDialog/MicroscopyDeleteDialog.styl
delete mode 100644 extensions/dicom-microscopy/src/components/MicroscopyLabelDialog/MicroscopyLabelDialog.js
delete mode 100644 extensions/dicom-microscopy/src/components/MicroscopyLabelDialog/MicroscopyLabelDialog.styl
rename extensions/dicom-microscopy/src/{tools/microscopyManager.js => services/MicroscopyService.js} (91%)
delete mode 100644 extensions/dicom-microscopy/src/utils/Publisher.js
diff --git a/extensions/dicom-microscopy/src/DicomMicroscopySRSopClassHandler.js b/extensions/dicom-microscopy/src/DicomMicroscopySRSopClassHandler.js
index d4ba8bbb27..9f5fb3979c 100644
--- a/extensions/dicom-microscopy/src/DicomMicroscopySRSopClassHandler.js
+++ b/extensions/dicom-microscopy/src/DicomMicroscopySRSopClassHandler.js
@@ -48,7 +48,7 @@ function _getDisplaySetsFromSeries(
throw new Error('No instances were provided');
}
- const { displaySetService } = servicesManager.services;
+ const { displaySetService, microscopyService } = servicesManager.services;
const instance = instances[0];
@@ -99,11 +99,13 @@ function _getDisplaySetsFromSeries(
};
displaySet.load = function(referencedDisplaySet) {
- return loadSR(displaySet, referencedDisplaySet).catch(error => {
- displaySet.isLoaded = false;
- displaySet.loadError = true;
- throw new Error(error);
- });
+ return loadSR(microscopyService, displaySet, referencedDisplaySet).catch(
+ error => {
+ displaySet.isLoaded = false;
+ displaySet.loadError = true;
+ throw new Error(error);
+ }
+ );
};
displaySet.getSourceDisplaySet = function() {
diff --git a/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
index 9bee4e308f..9864c90238 100644
--- a/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
+++ b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
@@ -3,18 +3,19 @@ import ReactResizeDetector from 'react-resize-detector';
import PropTypes from 'prop-types';
import debounce from 'lodash.debounce';
-import microscopyManager from './tools/microscopyManager';
import './DicomMicroscopyViewport.css';
import ViewportOverlay from './components/ViewportOverlay';
import getDicomWebClient from './utils/dicomWebClient';
import dcmjs from 'dcmjs';
import cleanDenaturalizedDataset from './utils/cleanDenaturalizedDataset';
+import MicroscopyService from './services/MicroscopyService';
class DicomMicroscopyViewport extends Component {
state = {
error: null as any,
};
+ microscopyService: MicroscopyService;
viewer: any = null; // dicom-microscopy-viewer instance
managedViewer: any = null; // managed wrapper of microscopy-dicom extension
@@ -25,6 +26,8 @@ class DicomMicroscopyViewport extends Component {
constructor(props: any) {
super(props);
+ const { microscopyService } = this.props.servicesManager.services;
+ this.microscopyService = microscopyService;
this.debouncedResize = debounce(() => {
if (this.viewer) this.viewer.resize();
}, 100);
@@ -57,34 +60,29 @@ class DicomMicroscopyViewport extends Component {
* @returns
*/
getNearbyROI(event: Event, autoselect = true) {
- const _drawingSource = Object.getOwnPropertySymbols(this.viewer).find(
- p => p.description == 'drawingSource'
- );
- const _pyramid = Object.getOwnPropertySymbols(this.viewer).find(
- p => p.description == 'pyramid'
- );
- const _map = Object.getOwnPropertySymbols(this.viewer).find(
- p => p.description == 'map'
- );
- const _affine = Object.getOwnPropertySymbols(this.viewer).find(
- p => p.description == 'affine'
- );
+ const symbols = Object.getOwnPropertySymbols(this.viewer);
+ const _drawingSource = symbols.find(p => p.description === 'drawingSource');
+ const _pyramid = symbols.find(p => p.description === 'pyramid');
+ const _map = symbols.find(p => p.description === 'map');
+ const _affine = symbols.find(p => p.description === 'affine');
+
const feature = this.viewer[_drawingSource].getClosestFeatureToCoordinate(
this.viewer[_map].getEventCoordinate(event)
);
- if (feature) {
- const roiAnnotation = this.viewer._getROIFromFeature(
- feature,
- this.viewer[_pyramid].metadata,
- this.viewer[_affine]
- );
- if (roiAnnotation && autoselect) {
- microscopyManager.selectAnnotation(roiAnnotation);
- }
- return roiAnnotation;
+ if (!feature) {
+ return null;
+ }
+
+ const roiAnnotation = this.viewer._getROIFromFeature(
+ feature,
+ this.viewer[_pyramid].metadata,
+ this.viewer[_affine]
+ );
+ if (roiAnnotation && autoselect) {
+ this.microscopyService.selectAnnotation(roiAnnotation);
}
- return null;
+ return roiAnnotation;
}
// install the microscopy renderer into the web page.
@@ -199,7 +197,7 @@ class DicomMicroscopyViewport extends Component {
const { StudyInstanceUID, SeriesInstanceUID } = displaySet;
- this.managedViewer = microscopyManager.addViewer(
+ this.managedViewer = this.microscopyService.addViewer(
this.viewer,
this.props.viewportIndex,
container,
@@ -249,7 +247,7 @@ class DicomMicroscopyViewport extends Component {
const { displaySets } = this.props;
const displaySet = displaySets[0];
- microscopyManager.clearAnnotations();
+ this.microscopyService.clearAnnotations();
// loading SR
if (displaySet.Modality === 'SR') {
@@ -260,7 +258,7 @@ class DicomMicroscopyViewport extends Component {
}
componentWillUnmount() {
- microscopyManager.removeViewer(this.viewer);
+ this.microscopyService.removeViewer(this.viewer);
}
setViewportActiveHandler = () => {
diff --git a/extensions/dicom-microscopy/src/components/MicroscopyDeleteDialog/MicroscopyDeleteDialog.js b/extensions/dicom-microscopy/src/components/MicroscopyDeleteDialog/MicroscopyDeleteDialog.js
deleted file mode 100644
index 0871aaecd0..0000000000
--- a/extensions/dicom-microscopy/src/components/MicroscopyDeleteDialog/MicroscopyDeleteDialog.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import { SimpleDialog } from '@ohif/ui';
-
-import './MicroscopyDeleteDialog.styl';
-
-const MicroscopyDeleteDialog = ({ title, onSubmit, onClose }) => {
- return (
-
-
- Are you sure you want to remove this Region of Interest?
-
-
- );
-};
-
-MicroscopyDeleteDialog.propTypes = {
- title: PropTypes.string,
- onSubmit: PropTypes.func,
- onClose: PropTypes.func,
-};
-
-export default MicroscopyDeleteDialog;
diff --git a/extensions/dicom-microscopy/src/components/MicroscopyDeleteDialog/MicroscopyDeleteDialog.styl b/extensions/dicom-microscopy/src/components/MicroscopyDeleteDialog/MicroscopyDeleteDialog.styl
deleted file mode 100644
index 60291b89ad..0000000000
--- a/extensions/dicom-microscopy/src/components/MicroscopyDeleteDialog/MicroscopyDeleteDialog.styl
+++ /dev/null
@@ -1,2 +0,0 @@
-.InputDialog.MicroscopyDeleteDialog .content
- padding-bottom: 16px
diff --git a/extensions/dicom-microscopy/src/components/MicroscopyLabelDialog/MicroscopyLabelDialog.js b/extensions/dicom-microscopy/src/components/MicroscopyLabelDialog/MicroscopyLabelDialog.js
deleted file mode 100644
index e57b1ca86d..0000000000
--- a/extensions/dicom-microscopy/src/components/MicroscopyLabelDialog/MicroscopyLabelDialog.js
+++ /dev/null
@@ -1,61 +0,0 @@
-import React, { useState } from 'react';
-import PropTypes from 'prop-types';
-import { SimpleDialog, TextInput, SelectTree } from '@ohif/ui';
-import './MicroscopyLabelDialog.styl';
-import ConfigPoint from 'config-point';
-
-const MicroscopyLabelDialog = ({
- title,
- onSubmit,
- onClose,
- label,
- defaultValue = '',
-}) => {
- const [value, setValue] = useState({ label: defaultValue });
- const onSubmitHandler = () => onSubmit(value);
-
- const labellingItemConfig = ConfigPoint.MicroscopyLabellingData;
- const { computedItems } = labellingItemConfig || {};
- const selectTreeSelectCallback = (event, item) => {
- setValue(item);
- onSubmit(item);
- };
-
- return (
-
-
- setValue({ label: event.target.value })}
- label={label}
- autoComplete="off"
- autoFocus
- onFocus={e => e.currentTarget.select()}
- />
- {computedItems && (
-
- )}
-
-
- );
-};
-
-MicroscopyLabelDialog.propTypes = {
- title: PropTypes.string,
- onSubmit: PropTypes.func,
- onClose: PropTypes.func,
- label: PropTypes.string,
- defaultValue: PropTypes.string,
-};
-
-export default MicroscopyLabelDialog;
diff --git a/extensions/dicom-microscopy/src/components/MicroscopyLabelDialog/MicroscopyLabelDialog.styl b/extensions/dicom-microscopy/src/components/MicroscopyLabelDialog/MicroscopyLabelDialog.styl
deleted file mode 100644
index 4e0d89f09f..0000000000
--- a/extensions/dicom-microscopy/src/components/MicroscopyLabelDialog/MicroscopyLabelDialog.styl
+++ /dev/null
@@ -1,3 +0,0 @@
-.InputDialog.MicroscopyLabelDialog .content
- padding-bottom: 16px
- flex-direction: column
diff --git a/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx b/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx
index c17336a2fa..5759bfd5c4 100644
--- a/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx
+++ b/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx
@@ -9,7 +9,7 @@ import {
import { MeasurementTable, Icon, ButtonGroup, Button } from '@ohif/ui';
import { withTranslation, WithTranslation } from 'react-i18next';
import DEVICE_OBSERVER_UID from '../../utils/DEVICE_OBSERVER_UID';
-import { EVENTS as MicroscopyEvents } from '../../tools/microscopyManager';
+import { EVENTS as MicroscopyEvents } from '../../services/MicroscopyService';
import dcmjs from 'dcmjs';
import styles from '../../utils/styles';
import callInputDialog from '../../utils/callInputDialog';
@@ -67,7 +67,6 @@ interface IMicroscopyPanelProps extends WithTranslation {
activeViewportIndex: PropTypes.number;
//
- microscopyManager: PropTypes.object;
onSaveComplete?: PropTypes.func; // callback when successfully saved annotations
onRejectComplete?: PropTypes.func; // callback when rejected annotations
@@ -84,7 +83,7 @@ interface IMicroscopyPanelProps extends WithTranslation {
* @returns
*/
function MicroscopyPanel(props: IMicroscopyPanelProps) {
- const microscopyManager: any = props.microscopyManager;
+ const { microscopyService } = props.servicesManager.services;
const [studyInstanceUID, setStudyInstanceUID] = useState(
null as string | null
@@ -109,22 +108,26 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) {
useEffect(() => {
const onAnnotationUpdated = () => {
- const roiAnnotations = microscopyManager.getAnnotationsForStudy(
+ const roiAnnotations = microscopyService.getAnnotationsForStudy(
studyInstanceUID
);
setRoiAnnotations(roiAnnotations);
};
const onAnnotationSelected = () => {
- const selectedAnnotation = microscopyManager.getSelectedAnnotation();
+ const selectedAnnotation = microscopyService.getSelectedAnnotation();
setSelectedAnnotation(selectedAnnotation);
};
- microscopyManager.subscribe(
+ const {
+ unsubscribe: unsubscribeAnnotationUpdated,
+ } = microscopyService.subscribe(
MicroscopyEvents.ANNOTATION_UPDATED,
onAnnotationUpdated
);
- microscopyManager.subscribe(
+ const {
+ unsubscribe: unsubscribeAnnotationSelected,
+ } = microscopyService.subscribe(
MicroscopyEvents.ANNOTATION_SELECTED,
onAnnotationSelected
);
@@ -133,14 +136,8 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) {
// on unload unsubscribe from events
return () => {
- microscopyManager.unsubscribe(
- MicroscopyEvents.ANNOTATION_UPDATED,
- onAnnotationUpdated
- );
- microscopyManager.unsubscribe(
- MicroscopyEvents.ANNOTATION_SELECTED,
- onAnnotationSelected
- );
+ unsubscribeAnnotationUpdated();
+ unsubscribeAnnotationSelected();
};
}, [studyInstanceUID]);
@@ -151,7 +148,7 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) {
* @returns
*/
const promptSave = () => {
- const annotations = microscopyManager.getAnnotationsForStudy(
+ const annotations = microscopyService.getAnnotationsForStudy(
studyInstanceUID
);
@@ -194,7 +191,7 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) {
const dataSource = extensionManager.getActiveDataSource()[0];
const { onSaveComplete } = props;
const imagingMeasurements = [];
- const annotations = microscopyManager.getAnnotationsForStudy(
+ const annotations = microscopyService.getAnnotationsForStudy(
studyInstanceUID
);
@@ -495,9 +492,9 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) {
* @param param0
*/
const onMeasurementItemClickHandler = ({ uid }: { uid: string }) => {
- const roiAnnotation = microscopyManager.getAnnotation(uid);
- microscopyManager.selectAnnotation(roiAnnotation);
- microscopyManager.focusAnnotation(roiAnnotation, props.activeViewportIndex);
+ const roiAnnotation = microscopyService.getAnnotation(uid);
+ microscopyService.selectAnnotation(roiAnnotation);
+ microscopyService.focusAnnotation(roiAnnotation, props.activeViewportIndex);
};
/**
@@ -514,7 +511,7 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) {
props.commandsManager.runCommand('setLabel', { uid }, 'MICROSCOPY');
};
- // Convert ROI annotations managed by microscopyManager into our
+ // Convert ROI annotations managed by microscopyService into our
// own format for display
const data = roiAnnotations.map((roiAnnotation, index) => {
const label = roiAnnotation.getDetailedLabel();
diff --git a/extensions/dicom-microscopy/src/getCommandsModule.ts b/extensions/dicom-microscopy/src/getCommandsModule.ts
index 964538595f..050a8a6155 100644
--- a/extensions/dicom-microscopy/src/getCommandsModule.ts
+++ b/extensions/dicom-microscopy/src/getCommandsModule.ts
@@ -1,4 +1,3 @@
-import microscopyManager from './tools/microscopyManager';
import { ServicesManager, CommandsManager, ExtensionManager } from '@ohif/core';
import styles from './utils/styles';
import callInputDialog from './utils/callInputDialog';
@@ -14,24 +13,21 @@ export default function getCommandsModule({
}) {
const {
viewportGridService,
- toolbarService,
uiDialogService,
- cornerstoneViewportService,
- customizationService,
- measurementService,
+ microscopyService,
} = servicesManager.services;
const actions = {
// Measurement tool commands:
deleteMeasurement: ({ uid }) => {
if (uid) {
- const roiAnnotation = microscopyManager.getAnnotation(uid);
- if (roiAnnotation) microscopyManager.removeAnnotation(roiAnnotation);
+ const roiAnnotation = microscopyService.getAnnotation(uid);
+ if (roiAnnotation) microscopyService.removeAnnotation(roiAnnotation);
}
},
setLabel: ({ uid }) => {
- const roiAnnotation = microscopyManager.getAnnotation(uid);
+ const roiAnnotation = microscopyService.getAnnotation(uid);
callInputDialog({
uiDialogService,
@@ -41,7 +37,7 @@ export default function getCommandsModule({
switch (action) {
case 'save': {
roiAnnotation.setLabel(value);
- microscopyManager.triggerRelabel(roiAnnotation);
+ microscopyService.triggerRelabel(roiAnnotation);
}
}
},
@@ -74,9 +70,9 @@ export default function getCommandsModule({
delete options.vertexEnabled;
}
- microscopyManager.activateInteractions([['draw', options]]);
+ microscopyService.activateInteractions([['draw', options]]);
} else if (toolName == 'dragPan') {
- microscopyManager.activateInteractions([['dragPan']]);
+ microscopyService.activateInteractions([['dragPan']]);
}
},
@@ -112,10 +108,10 @@ export default function getCommandsModule({
// overview
const { activeViewportIndex, viewports } = viewportGridService.getState();
- microscopyManager.toggleOverviewMap(activeViewportIndex);
+ microscopyService.toggleOverviewMap(activeViewportIndex);
},
toggleAnnotations: () => {
- microscopyManager.toggleROIsVisibility();
+ microscopyService.toggleROIsVisibility();
},
};
diff --git a/extensions/dicom-microscopy/src/getPanelModule.tsx b/extensions/dicom-microscopy/src/getPanelModule.tsx
index 4393b0f130..ef49c9331a 100644
--- a/extensions/dicom-microscopy/src/getPanelModule.tsx
+++ b/extensions/dicom-microscopy/src/getPanelModule.tsx
@@ -2,7 +2,6 @@ import React from 'react';
import { ServicesManager, CommandsManager, ExtensionManager } from '@ohif/core';
import { useViewportGrid } from '@ohif/ui';
import MicroscopyPanel from './components/MicroscopyPanel/MicroscopyPanel';
-import microscopyManager from './tools/microscopyManager';
// TODO:
// - No loading UI exists yet
@@ -13,10 +12,10 @@ export default function getPanelModule({
commandsManager,
extensionManager,
servicesManager,
-} : {
- servicesManager: ServicesManager,
- commandsManager: CommandsManager,
- extensionManager: ExtensionManager,
+}: {
+ servicesManager: ServicesManager;
+ commandsManager: CommandsManager;
+ extensionManager: ExtensionManager;
}) {
const wrappedMeasurementPanel = () => {
const [
@@ -28,7 +27,6 @@ export default function getPanelModule({
{}}
onRejectComplete={() => {}}
commandsManager={commandsManager}
diff --git a/extensions/dicom-microscopy/src/index.tsx b/extensions/dicom-microscopy/src/index.tsx
index cfccb9904e..66dfeacd98 100644
--- a/extensions/dicom-microscopy/src/index.tsx
+++ b/extensions/dicom-microscopy/src/index.tsx
@@ -6,6 +6,7 @@ import getCommandsModule from './getCommandsModule';
import { useViewportGrid } from '@ohif/ui';
import getDicomMicroscopySopClassHandler from './DicomMicroscopySopClassHandler';
import getDicomMicroscopySRSopClassHandler from './DicomMicroscopySRSopClassHandler';
+import MicroscopyService from './services/MicroscopyService';
const Component = React.lazy(() => {
return import('./DicomMicroscopyViewport');
@@ -29,6 +30,17 @@ export default {
*/
id,
+ async preRegistration({
+ servicesManager,
+ commandsManager,
+ configuration = {},
+ appConfig,
+ }) {
+ servicesManager.registerService(
+ MicroscopyService.REGISTRATION(servicesManager)
+ );
+ },
+
/**
* ViewportModule should provide a list of viewports that will be available in OHIF
* for Modes to consume and use in the viewports. Each viewport is defined by
diff --git a/extensions/dicom-microscopy/src/tools/microscopyManager.js b/extensions/dicom-microscopy/src/services/MicroscopyService.js
similarity index 91%
rename from extensions/dicom-microscopy/src/tools/microscopyManager.js
rename to extensions/dicom-microscopy/src/services/MicroscopyService.js
index 59fd3c5bb8..83e31b3dd0 100644
--- a/extensions/dicom-microscopy/src/tools/microscopyManager.js
+++ b/extensions/dicom-microscopy/src/services/MicroscopyService.js
@@ -1,10 +1,9 @@
-import Publisher from '../utils/Publisher';
-import ViewerManager, { EVENTS as ViewerEvents } from './viewerManager';
+import ViewerManager, { EVENTS as ViewerEvents } from '../tools/viewerManager';
import RoiAnnotation, {
EVENTS as AnnotationEvents,
} from '../utils/RoiAnnotation';
import styles from '../utils/styles';
-import { DicomMetadataStore } from '@ohif/core';
+import { DicomMetadataStore, PubSubService } from '@ohif/core';
const EVENTS = {
ANNOTATION_UPDATED: 'annotationUpdated',
@@ -14,20 +13,33 @@ const EVENTS = {
};
/**
- * MicroscopyManager is responsible to manage multiple third-party API's
+ * MicroscopyService is responsible to manage multiple third-party API's
* microscopy viewers expose methods to manage the interaction with these
* viewers and handle their ROI graphics to create, remove and modify the
* ROI annotations relevant to the application
*/
-class MicroscopyManager extends Publisher {
+export default class MicroscopyService extends PubSubService {
+ public static REGISTRATION = serviceManager => {
+ return {
+ name: 'microscopyService',
+ altName: 'MicroscopyService',
+ create: ({ configuration = {} }) => {
+ return new MicroscopyService(serviceManager);
+ },
+ };
+ };
+
+ serviceManager: any;
+
managedViewers = new Set();
roiUids = new Set();
annotations = {};
selectedAnnotation = null;
pendingFocus = false;
- constructor() {
- super();
+ constructor(serviceManager) {
+ super(EVENTS);
+ this.serviceManager = serviceManager;
this._onRoiAdded = this._onRoiAdded.bind(this);
this._onRoiModified = this._onRoiModified.bind(this);
this._onRoiRemoved = this._onRoiRemoved.bind(this);
@@ -150,10 +162,10 @@ class MicroscopyManager extends Publisher {
* @param {ViewerManager} managedViewer The viewer being added
*/
_addManagedViewerSubscriptions(managedViewer) {
- managedViewer.subscribe(ViewerEvents.ADDED, this._onRoiAdded);
- managedViewer.subscribe(ViewerEvents.MODIFIED, this._onRoiModified);
- managedViewer.subscribe(ViewerEvents.REMOVED, this._onRoiRemoved);
- managedViewer.subscribe(ViewerEvents.UPDATED, this._onRoiUpdated);
+ managedViewer._roiAddedSubscription = managedViewer.subscribe(ViewerEvents.ADDED, this._onRoiAdded);
+ managedViewer._roiModifiedSubscription = managedViewer.subscribe(ViewerEvents.MODIFIED, this._onRoiModified);
+ managedViewer._roiRemovedSubscription = managedViewer.subscribe(ViewerEvents.REMOVED, this._onRoiRemoved);
+ managedViewer._roiUpdatedSubscription = managedViewer.subscribe(ViewerEvents.UPDATED, this._onRoiUpdated);
}
/**
@@ -162,10 +174,15 @@ class MicroscopyManager extends Publisher {
* @param {ViewerManager} managedViewer The viewer being removed
*/
_removeManagedViewerSubscriptions(managedViewer) {
- managedViewer.unsubscribe(ViewerEvents.ADDED, this._onRoiAdded);
- managedViewer.unsubscribe(ViewerEvents.MODIFIED, this._onRoiModified);
- managedViewer.unsubscribe(ViewerEvents.REMOVED, this._onRoiRemoved);
- managedViewer.unsubscribe(ViewerEvents.UPDATED, this._onRoiUpdated);
+ managedViewer._roiAddedSubscription && managedViewer._roiAddedSubscription.unsubscribe();
+ managedViewer._roiModifiedSubscription && managedViewer._roiModifiedSubscription.unsubscribe();
+ managedViewer._roiRemovedSubscription && managedViewer._roiRemovedSubscription.unsubscribe();
+ managedViewer._roiUpdatedSubscription && managedViewer._roiUpdatedSubscription.unsubscribe();
+
+ managedViewer._roiAddedSubscription = null;
+ managedViewer._roiModifiedSubscription = null;
+ managedViewer._roiRemovedSubscription = null;
+ managedViewer._roiUpdatedSubscription = null;
}
/**
@@ -325,11 +342,7 @@ class MicroscopyManager extends Publisher {
* Toggle ROIs visibility
*/
toggleROIsVisibility() {
- if (!this.isROIsVisible) {
- this.showROIs();
- } else {
- this.hideROIs();
- }
+ this.isROIsVisible ? this.hideROIs() : this.showROIs;
this.isROIsVisible = !this.isROIsVisible;
}
@@ -587,5 +600,3 @@ class MicroscopyManager extends Publisher {
}
export { EVENTS };
-
-export default new MicroscopyManager();
diff --git a/extensions/dicom-microscopy/src/tools/viewerManager.js b/extensions/dicom-microscopy/src/tools/viewerManager.js
index 53d13e4613..3309b8bf30 100644
--- a/extensions/dicom-microscopy/src/tools/viewerManager.js
+++ b/extensions/dicom-microscopy/src/tools/viewerManager.js
@@ -1,7 +1,8 @@
-import Publisher from '../utils/Publisher';
import coordinateFormatScoord3d2Geometry from '../utils/coordinateFormatScoord3d2Geometry';
import styles from '../utils/styles';
+import { PubSubService } from '@ohif/core';
+
// Events from the third-party viewer
const ApiEvents = {
ROI_ADDED: 'dicommicroscopyviewer_roi_added',
@@ -20,7 +21,7 @@ const EVENTS = {
* ViewerManager encapsulates the complexity of the third-party viewer and
* expose only the features/behaviors that are relevant to the application
*/
-class ViewerManager extends Publisher {
+class ViewerManager extends PubSubService {
constructor(
viewer,
viewportIndex,
@@ -28,7 +29,7 @@ class ViewerManager extends Publisher {
studyInstanceUID,
seriesInstanceUID
) {
- super();
+ super(EVENTS);
this.viewer = viewer;
this.viewportIndex = viewportIndex;
this.container = container;
@@ -41,18 +42,11 @@ class ViewerManager extends Publisher {
this.contextMenuCallback = () => {};
// init symbols
- this._drawingSource = Object.getOwnPropertySymbols(this.viewer).find(
- p => p.description == 'drawingSource'
- );
- this._pyramid = Object.getOwnPropertySymbols(this.viewer).find(
- p => p.description == 'pyramid'
- );
- this._map = Object.getOwnPropertySymbols(this.viewer).find(
- p => p.description == 'map'
- );
- this._affine = Object.getOwnPropertySymbols(this.viewer).find(
- p => p.description == 'affine'
- );
+ const symbols = Object.getOwnPropertySymbols(this.viewer);
+ this._drawingSource = symbols.find(p => p.description === 'drawingSource');
+ this._pyramid = symbols.find(p => p.description === 'pyramid');
+ this._map = symbols.find(p => p.description === 'map');
+ this._affine = symbols.find(p => p.description === 'affine');
this.registerEvents();
this.activateDefaultInteractions();
diff --git a/extensions/dicom-microscopy/src/utils/Publisher.js b/extensions/dicom-microscopy/src/utils/Publisher.js
deleted file mode 100644
index e7a2d1c5fe..0000000000
--- a/extensions/dicom-microscopy/src/utils/Publisher.js
+++ /dev/null
@@ -1,53 +0,0 @@
-/**
- * Publisher allows applying pub-sub by extending it in other classes definition
- */
-export default class Publisher {
- constructor() {
- this.subscriptions = new Map();
- }
-
- /**
- * Triggers a publication for the given subscription key.
- * It will run the callbacks for all subscribers with the provided value.
- * The subscribers' callbacks will be called in the order they were defined.
- *
- * @param {String} key Subscription key
- * @param {any} value Value that will be send to all subscribers
- */
- publish(key, value) {
- const subscriptions = this.subscriptions.get(key);
- if (subscriptions) {
- subscriptions.forEach(callback => callback(value));
- }
- }
-
- /**
- * Subscribe to a specific key, providing a callback that will run when the
- * publish occurs.
- *
- * @param {String} key Subscription key
- * @param {Function} callback Callback that will be registered for the key
- */
- subscribe(key, callback) {
- if (!this.subscriptions.get(key)) {
- this.subscriptions.set(key, [callback]);
- } else {
- this.subscriptions.get(key).push(callback);
- }
- }
-
- /**
- * Remove the subscription for the given key and callback, so the callback
- * will no longer run if a publish to key occurs.
- *
- * @param {String} key Subscription key
- * @param {Function} callback Callback that was registered for the key
- */
- unsubscribe(key, callback) {
- const subscriptions = this.subscriptions.get(key);
- const index = subscriptions.indexOf(callback);
- if (index > -1) {
- subscriptions.splice(index, 1);
- }
- }
-}
diff --git a/extensions/dicom-microscopy/src/utils/RoiAnnotation.js b/extensions/dicom-microscopy/src/utils/RoiAnnotation.js
index 34f9905f2b..cfc3794d26 100644
--- a/extensions/dicom-microscopy/src/utils/RoiAnnotation.js
+++ b/extensions/dicom-microscopy/src/utils/RoiAnnotation.js
@@ -1,6 +1,7 @@
-import Publisher from './Publisher';
import areaOfPolygon from './areaOfPolygon';
+import { PubSubService } from '@ohif/core';
+
const EVENTS = {
LABEL_UPDATED: 'labelUpdated',
GRAPHIC_UPDATED: 'graphicUpdated',
@@ -11,7 +12,7 @@ const EVENTS = {
/**
* Represents a single annotation for the Microscopy Viewer
*/
-class RoiAnnotation extends Publisher {
+class RoiAnnotation extends PubSubService {
constructor(
roiGraphic,
studyInstanceUID,
@@ -19,7 +20,7 @@ class RoiAnnotation extends Publisher {
label = '',
viewState = null
) {
- super();
+ super(EVENTS);
this.uid = roiGraphic.uid;
this.roiGraphic = roiGraphic;
this.studyInstanceUID = studyInstanceUID;
@@ -113,8 +114,8 @@ class RoiAnnotation extends Publisher {
case 'POLYLINE':
let len = 0;
- for (let i=1; i
Date: Tue, 18 Apr 2023 21:51:16 -0400
Subject: [PATCH 38/51] fix thumbnails
---
extensions/default/src/Panels/PanelStudyBrowser.tsx | 1 +
.../PanelStudyBrowserTracking.tsx | 1 +
platform/core/src/utils/isLowPriorityModality.ts | 8 +++++++-
3 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/extensions/default/src/Panels/PanelStudyBrowser.tsx b/extensions/default/src/Panels/PanelStudyBrowser.tsx
index 9eb75aa955..b647bed9f4 100644
--- a/extensions/default/src/Panels/PanelStudyBrowser.tsx
+++ b/extensions/default/src/Panels/PanelStudyBrowser.tsx
@@ -321,6 +321,7 @@ function _mapDisplaySets(displaySets, thumbnailImageSrcMap) {
const thumbnailNoImageModalities = [
'SR',
'SEG',
+ 'SM',
'RTSTRUCT',
'RTPLAN',
'RTDOSE',
diff --git a/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.tsx b/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.tsx
index ec92ce6a3c..e81309ce2f 100644
--- a/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.tsx
+++ b/extensions/measurement-tracking/src/panels/PanelStudyBrowserTracking/PanelStudyBrowserTracking.tsx
@@ -545,6 +545,7 @@ function _mapDisplaySets(
const thumbnailNoImageModalities = [
'SR',
'SEG',
+ 'SM',
'RTSTRUCT',
'RTPLAN',
'RTDOSE',
diff --git a/platform/core/src/utils/isLowPriorityModality.ts b/platform/core/src/utils/isLowPriorityModality.ts
index 54024d5a05..aebb450ffb 100644
--- a/platform/core/src/utils/isLowPriorityModality.ts
+++ b/platform/core/src/utils/isLowPriorityModality.ts
@@ -1,4 +1,10 @@
-const LOW_PRIORITY_MODALITIES = Object.freeze(['SEG', 'KO', 'PR', 'SR', 'RTSTRUCT']);
+const LOW_PRIORITY_MODALITIES = Object.freeze([
+ 'SEG',
+ 'KO',
+ 'PR',
+ 'SR',
+ 'RTSTRUCT',
+]);
export default function isLowPriorityModality(Modality) {
return LOW_PRIORITY_MODALITIES.includes(Modality);
From 23c7dfe742061f35a2121d30cd3b3abd01167264 Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Wed, 19 Apr 2023 11:08:16 -0400
Subject: [PATCH 39/51] [fix] microscopy - fix old publisher.publish() to
PubSubService._broadcastEvent()
---
.../src/DicomMicroscopyViewport.tsx | 16 +---------------
.../dicom-microscopy/src/getCommandsModule.ts | 14 +++++++++++++-
.../src/services/MicroscopyService.js | 10 +++++-----
.../dicom-microscopy/src/tools/viewerManager.js | 17 ++++++++++-------
.../dicom-microscopy/src/utils/RoiAnnotation.js | 8 ++++----
5 files changed, 33 insertions(+), 32 deletions(-)
diff --git a/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
index 9864c90238..fde5f802cd 100644
--- a/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
+++ b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
@@ -206,22 +206,8 @@ class DicomMicroscopyViewport extends Component {
);
this.managedViewer.addContextMenuCallback((event: Event) => {
- const roiAnnotationNearBy = this.getNearbyROI(event);
-
// TODO: refactor this after Bill's changes on ContextMenu feature get merged
- this.props.commandsManager.runCommand(
- 'showViewerContextMenu',
- {
- menuName: 'microscopyContextMenu',
- event,
- container,
- viewer: this.viewer,
- managedViewer: this.managedViewer,
- viewportIndex: this.props.viewportIndex,
- roiAnnotationNearBy,
- },
- 'MICROSCOPY'
- );
+ // const roiAnnotationNearBy = this.getNearbyROI(event);
});
};
diff --git a/extensions/dicom-microscopy/src/getCommandsModule.ts b/extensions/dicom-microscopy/src/getCommandsModule.ts
index 050a8a6155..96a3561326 100644
--- a/extensions/dicom-microscopy/src/getCommandsModule.ts
+++ b/extensions/dicom-microscopy/src/getCommandsModule.ts
@@ -61,6 +61,9 @@ export default function getCommandsModule({
geometryType: toolName,
vertexEnabled: true,
styleOptions: styles.default,
+ bindings: {
+ mouseButtons: ['left'],
+ },
} as any;
if ('line' === toolName) {
options.minPoints = 2;
@@ -72,7 +75,16 @@ export default function getCommandsModule({
microscopyService.activateInteractions([['draw', options]]);
} else if (toolName == 'dragPan') {
- microscopyService.activateInteractions([['dragPan']]);
+ microscopyService.activateInteractions([
+ [
+ 'dragPan',
+ {
+ bindings: {
+ mouseButtons: ['left', 'middle'],
+ },
+ },
+ ],
+ ]);
}
},
diff --git a/extensions/dicom-microscopy/src/services/MicroscopyService.js b/extensions/dicom-microscopy/src/services/MicroscopyService.js
index 83e31b3dd0..1a811a60a7 100644
--- a/extensions/dicom-microscopy/src/services/MicroscopyService.js
+++ b/extensions/dicom-microscopy/src/services/MicroscopyService.js
@@ -97,7 +97,7 @@ export default class MicroscopyService extends PubSubService {
this.annotations[roiGraphic.uid] = roiAnnotation;
roiAnnotation.subscribe(AnnotationEvents.LABEL_UPDATED, () => {
- this.publish(EVENTS.ANNOTATION_UPDATED, roiAnnotation);
+ this._broadcastEvent(EVENTS.ANNOTATION_UPDATED, roiAnnotation);
});
if (label !== undefined) {
@@ -153,7 +153,7 @@ export default class MicroscopyService extends PubSubService {
_onRoiUpdated(data) {
const { roiGraphic, managedViewer } = data;
this.synchronizeViewers(managedViewer);
- this.publish(EVENTS.ANNOTATION_UPDATED, this.getAnnotation(roiGraphic.uid));
+ this._broadcastEvent(EVENTS.ANNOTATION_UPDATED, this.getAnnotation(roiGraphic.uid));
}
/**
@@ -439,7 +439,7 @@ export default class MicroscopyService extends PubSubService {
if (this.selectedAnnotation) this.clearSelection();
this.selectedAnnotation = roiAnnotation;
- this.publish(EVENTS.ANNOTATION_SELECTED, roiAnnotation);
+ this._broadcastEvent(EVENTS.ANNOTATION_SELECTED, roiAnnotation);
this.setROIStyle(roiAnnotation.uid, styles.active);
}
@@ -564,7 +564,7 @@ export default class MicroscopyService extends PubSubService {
);
}
- this.publish(EVENTS.RELABEL, {
+ this._broadcastEvent(EVENTS.RELABEL, {
roiAnnotation,
deleteCallback: () => this.removeAnnotation(roiAnnotation),
successCallback: onRelabel,
@@ -579,7 +579,7 @@ export default class MicroscopyService extends PubSubService {
* @param {RoiAnnotation} roiAnnotation The instance to be deleted
*/
triggerDelete(roiAnnotation) {
- this.publish(EVENTS.DELETE, roiAnnotation);
+ this._broadcastEvent(EVENTS.DELETE, roiAnnotation);
}
/**
diff --git a/extensions/dicom-microscopy/src/tools/viewerManager.js b/extensions/dicom-microscopy/src/tools/viewerManager.js
index 3309b8bf30..aecc80c38a 100644
--- a/extensions/dicom-microscopy/src/tools/viewerManager.js
+++ b/extensions/dicom-microscopy/src/tools/viewerManager.js
@@ -64,14 +64,16 @@ class ViewerManager extends PubSubService {
}
/**
- * Overrides the publish method and always send the ROI graphic object and
- * this managed viewer instance
+ * This is to overrides the _broadcastEvent method of PubSubService and always
+ * send the ROI graphic object and this managed viewer instance.
+ * Due to the way that PubSubService is written, the same name override of the
+ * function doesn't work.
*
* @param {String} key key Subscription key
* @param {Object} roiGraphic ROI graphic object created by the third-party API
*/
publish(key, roiGraphic) {
- super.publish(key, {
+ this._broadcastEvent(key, {
roiGraphic,
managedViewer: this,
});
@@ -184,7 +186,7 @@ class ViewerManager extends PubSubService {
}
this.runSilently(() => this.viewer.addROI(roiGraphic, styles.default));
- super.publish(EVENTS.ADDED, {
+ this._broadcastEvent(EVENTS.ADDED, {
roiGraphic,
managedViewer: this,
label,
@@ -247,9 +249,10 @@ class ViewerManager extends PubSubService {
'contextmenu',
event => {
event.preventDefault();
- if (typeof this.contextMenuCallback === 'function') {
- this.contextMenuCallback(event);
- }
+ // comment out when context menu for microscopy is enabled
+ // if (typeof this.contextMenuCallback === 'function') {
+ // this.contextMenuCallback(event);
+ // }
},
false
);
diff --git a/extensions/dicom-microscopy/src/utils/RoiAnnotation.js b/extensions/dicom-microscopy/src/utils/RoiAnnotation.js
index cfc3794d26..e8ca04021f 100644
--- a/extensions/dicom-microscopy/src/utils/RoiAnnotation.js
+++ b/extensions/dicom-microscopy/src/utils/RoiAnnotation.js
@@ -57,7 +57,7 @@ class RoiAnnotation extends PubSubService {
* When called will trigger the REMOVED event
*/
destroy() {
- this.publish(EVENTS.REMOVED, this);
+ this._broadcastEvent(EVENTS.REMOVED, this);
}
/**
@@ -69,7 +69,7 @@ class RoiAnnotation extends PubSubService {
setRoiGraphic(roiGraphic) {
this.roiGraphic = roiGraphic;
this.setMeasurements();
- this.publish(EVENTS.GRAPHIC_UPDATED, this);
+ this._broadcastEvent(EVENTS.GRAPHIC_UPDATED, this);
}
/**
@@ -140,7 +140,7 @@ class RoiAnnotation extends PubSubService {
*/
setViewState(viewState) {
this.viewState = viewState;
- this.publish(EVENTS.VIEW_UPDATED, this);
+ this._broadcastEvent(EVENTS.VIEW_UPDATED, this);
}
/**
@@ -155,7 +155,7 @@ class RoiAnnotation extends PubSubService {
CodeValue: label,
CodeMeaning: label,
};
- this.publish(EVENTS.LABEL_UPDATED, this);
+ this._broadcastEvent(EVENTS.LABEL_UPDATED, this);
}
/**
From 3bf98084fc7ef047770851b46bc1afe8dd1c309e Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Wed, 19 Apr 2023 13:24:25 -0400
Subject: [PATCH 40/51] [fix] interactions, webpack config, roi selection
---
.../dicom-microscopy/.webpack/webpack.dev.js | 8 ++++
.../src/DicomMicroscopyViewport.tsx | 2 +-
.../dicom-microscopy/src/getCommandsModule.ts | 36 +++++++++++++++-
.../src/services/MicroscopyService.js | 22 ++++++++++
.../src/tools/viewerManager.js | 42 +++++++++++++++++++
modes/microscopy/.webpack/webpack.dev.js | 8 ++++
modes/microscopy/src/index.tsx | 1 +
modes/microscopy/src/toolbarButtons.js | 20 ++++++++-
platform/viewer/.webpack/webpack.pwa.js | 31 ++++++--------
9 files changed, 148 insertions(+), 22 deletions(-)
create mode 100644 extensions/dicom-microscopy/.webpack/webpack.dev.js
create mode 100644 modes/microscopy/.webpack/webpack.dev.js
diff --git a/extensions/dicom-microscopy/.webpack/webpack.dev.js b/extensions/dicom-microscopy/.webpack/webpack.dev.js
new file mode 100644
index 0000000000..db7c206b13
--- /dev/null
+++ b/extensions/dicom-microscopy/.webpack/webpack.dev.js
@@ -0,0 +1,8 @@
+const path = require('path');
+const webpackCommon = require('./../../../.webpack/webpack.base.js');
+const SRC_DIR = path.join(__dirname, '../src');
+const DIST_DIR = path.join(__dirname, '../dist');
+
+module.exports = (env, argv) => {
+ return webpackCommon(env, argv, { SRC_DIR, DIST_DIR });
+};
diff --git a/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
index fde5f802cd..59b2e7fc9a 100644
--- a/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
+++ b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
@@ -175,7 +175,7 @@ class DicomMicroscopyViewport extends Component {
client,
metadata: volumeImages,
retrieveRendered: false,
- controls: ['overview', 'position'],
+ controls: ['overview', 'position', 'zoom'],
};
this.viewer = new microscopyViewer(options);
diff --git a/extensions/dicom-microscopy/src/getCommandsModule.ts b/extensions/dicom-microscopy/src/getCommandsModule.ts
index 96a3561326..5bb568913c 100644
--- a/extensions/dicom-microscopy/src/getCommandsModule.ts
+++ b/extensions/dicom-microscopy/src/getCommandsModule.ts
@@ -45,6 +45,22 @@ export default function getCommandsModule({
},
setToolActive: ({ toolName, toolGroupId = 'MICROSCOPY' }) => {
+ const dragPanOnMiddle = [
+ 'dragPan',
+ {
+ bindings: {
+ mouseButtons: ['middle'],
+ },
+ },
+ ];
+ const dragZoomOnRight = [
+ 'dragZoom',
+ {
+ bindings: {
+ mouseButtons: ['right'],
+ },
+ },
+ ];
if (
[
'line',
@@ -73,7 +89,11 @@ export default function getCommandsModule({
delete options.vertexEnabled;
}
- microscopyService.activateInteractions([['draw', options]]);
+ microscopyService.activateInteractions([
+ ['draw', options],
+ dragPanOnMiddle,
+ dragZoomOnRight,
+ ]);
} else if (toolName == 'dragPan') {
microscopyService.activateInteractions([
[
@@ -84,6 +104,20 @@ export default function getCommandsModule({
},
},
],
+ dragZoomOnRight,
+ ]);
+ } else {
+ microscopyService.activateInteractions([
+ [
+ toolName,
+ {
+ bindings: {
+ mouseButtons: ['left'],
+ },
+ },
+ ],
+ dragPanOnMiddle,
+ dragZoomOnRight,
]);
}
},
diff --git a/extensions/dicom-microscopy/src/services/MicroscopyService.js b/extensions/dicom-microscopy/src/services/MicroscopyService.js
index 1a811a60a7..dce5b119da 100644
--- a/extensions/dicom-microscopy/src/services/MicroscopyService.js
+++ b/extensions/dicom-microscopy/src/services/MicroscopyService.js
@@ -44,6 +44,7 @@ export default class MicroscopyService extends PubSubService {
this._onRoiModified = this._onRoiModified.bind(this);
this._onRoiRemoved = this._onRoiRemoved.bind(this);
this._onRoiUpdated = this._onRoiUpdated.bind(this);
+ this._onRoiSelected = this._onRoiSelected.bind(this);
this.isROIsVisible = true;
}
@@ -156,6 +157,24 @@ export default class MicroscopyService extends PubSubService {
this._broadcastEvent(EVENTS.ANNOTATION_UPDATED, this.getAnnotation(roiGraphic.uid));
}
+ /**
+ * Observes when an ROI is selected.
+ * Also publishes an ANNOTATION_SELECTED event to notify the subscribers.
+ *
+ * @param {Object} data The published data
+ * @param {Object} data.roiGraphic The added ROI graphic object
+ * @param {ViewerManager} data.managedViewer The origin viewer for the event
+ */
+ _onRoiSelected(data) {
+ const { roiGraphic } = data;
+ const selectedAnnotation = this.getAnnotation(roiGraphic.uid);
+ if (selectedAnnotation && selectedAnnotation !== this.getSelectedAnnotation()) {
+ if (this.selectedAnnotation) this.clearSelection();
+ this.selectedAnnotation = selectedAnnotation;
+ this._broadcastEvent(EVENTS.ANNOTATION_SELECTED, selectedAnnotation);
+ }
+ }
+
/**
* Creates the subscriptions for the managed viewer being added
*
@@ -166,6 +185,7 @@ export default class MicroscopyService extends PubSubService {
managedViewer._roiModifiedSubscription = managedViewer.subscribe(ViewerEvents.MODIFIED, this._onRoiModified);
managedViewer._roiRemovedSubscription = managedViewer.subscribe(ViewerEvents.REMOVED, this._onRoiRemoved);
managedViewer._roiUpdatedSubscription = managedViewer.subscribe(ViewerEvents.UPDATED, this._onRoiUpdated);
+ managedViewer._roiSelectedSubscription = managedViewer.subscribe(ViewerEvents.UPDATED, this._onRoiSelected);
}
/**
@@ -178,11 +198,13 @@ export default class MicroscopyService extends PubSubService {
managedViewer._roiModifiedSubscription && managedViewer._roiModifiedSubscription.unsubscribe();
managedViewer._roiRemovedSubscription && managedViewer._roiRemovedSubscription.unsubscribe();
managedViewer._roiUpdatedSubscription && managedViewer._roiUpdatedSubscription.unsubscribe();
+ managedViewer._roiSelectedSubscription && managedViewer._roiSelectedSubscription.unsubscribe();
managedViewer._roiAddedSubscription = null;
managedViewer._roiModifiedSubscription = null;
managedViewer._roiRemovedSubscription = null;
managedViewer._roiUpdatedSubscription = null;
+ managedViewer._roiSelectedSubscription = null;
}
/**
diff --git a/extensions/dicom-microscopy/src/tools/viewerManager.js b/extensions/dicom-microscopy/src/tools/viewerManager.js
index aecc80c38a..01da1e8b83 100644
--- a/extensions/dicom-microscopy/src/tools/viewerManager.js
+++ b/extensions/dicom-microscopy/src/tools/viewerManager.js
@@ -5,9 +5,32 @@ import { PubSubService } from '@ohif/core';
// Events from the third-party viewer
const ApiEvents = {
+ /** Triggered when a ROI was added. */
ROI_ADDED: 'dicommicroscopyviewer_roi_added',
+ /** Triggered when a ROI was modified. */
ROI_MODIFIED: 'dicommicroscopyviewer_roi_modified',
+ /** Triggered when a ROI was removed. */
ROI_REMOVED: 'dicommicroscopyviewer_roi_removed',
+ /** Triggered when a ROI was drawn. */
+ ROI_DRAWN: `dicommicroscopyviewer_roi_drawn`,
+ /** Triggered when a ROI was selected. */
+ ROI_SELECTED: `dicommicroscopyviewer_roi_selected`,
+ /** Triggered when a viewport move has started. */
+ MOVE_STARTED: `dicommicroscopyviewer_move_started`,
+ /** Triggered when a viewport move has ended. */
+ MOVE_ENDED: `dicommicroscopyviewer_move_ended`,
+ /** Triggered when a loading of data has started. */
+ LOADING_STARTED: `dicommicroscopyviewer_loading_started`,
+ /** Triggered when a loading of data has ended. */
+ LOADING_ENDED: `dicommicroscopyviewer_loading_ended`,
+ /** Triggered when an error occurs during loading of data. */
+ LOADING_ERROR: `dicommicroscopyviewer_loading_error`,
+ /* Triggered when the loading of an image tile has started. */
+ FRAME_LOADING_STARTED: `dicommicroscopyviewer_frame_loading_started`,
+ /* Triggered when the loading of an image tile has ended. */
+ FRAME_LOADING_ENDED: `dicommicroscopyviewer_frame_loading_ended`,
+ /* Triggered when the error occurs during loading of an image tile. */
+ FRAME_LOADING_ERROR: `dicommicroscopyviewer_frame_loading_ended`,
};
const EVENTS = {
@@ -15,6 +38,7 @@ const EVENTS = {
MODIFIED: 'modified',
REMOVED: 'removed',
UPDATED: 'updated',
+ SELECTED: 'selected',
};
/**
@@ -39,6 +63,7 @@ class ViewerManager extends PubSubService {
this.onRoiAdded = this.roiAddedHandler.bind(this);
this.onRoiModified = this.roiModifiedHandler.bind(this);
this.onRoiRemoved = this.roiRemovedHandler.bind(this);
+ this.onRoiSelected = this.roiSelectedHandler.bind(this);
this.contextMenuCallback = () => {};
// init symbols
@@ -86,6 +111,7 @@ class ViewerManager extends PubSubService {
this.container.addEventListener(ApiEvents.ROI_ADDED, this.onRoiAdded);
this.container.addEventListener(ApiEvents.ROI_MODIFIED, this.onRoiModified);
this.container.addEventListener(ApiEvents.ROI_REMOVED, this.onRoiRemoved);
+ this.container.addEventListener(ApiEvents.ROI_SELECTED, this.onRoiSelected);
}
/**
@@ -101,6 +127,10 @@ class ViewerManager extends PubSubService {
ApiEvents.ROI_REMOVED,
this.onRoiRemoved
);
+ this.container.removeEventListener(
+ ApiEvents.ROI_SELECTED,
+ this.onRoiSelected
+ );
}
/**
@@ -136,6 +166,16 @@ class ViewerManager extends PubSubService {
this.publish(EVENTS.UPDATED, roiGraphic);
}
+ /**
+ * Handles the ROI_SELECTED event triggered by the third-party API
+ *
+ * @param {Event} event Event triggered by the third-party API
+ */
+ roiSelectedHandler(event) {
+ const roiGraphic = event.detail.payload;
+ this.publish(EVENTS.SELECTED, roiGraphic);
+ }
+
/**
* Run the given callback operation without triggering any events for this
* instance, so subscribers will not be affected
@@ -303,6 +343,8 @@ class ViewerManager extends PubSubService {
activate
? 'activateDragZoomInteraction'
: 'deactivateDragZoomInteraction',
+ select: activate =>
+ activate ? 'activateSelectInteraction' : 'deactivateSelectInteraction',
};
const availableInteractionsName = Object.keys(interactionsMap);
diff --git a/modes/microscopy/.webpack/webpack.dev.js b/modes/microscopy/.webpack/webpack.dev.js
new file mode 100644
index 0000000000..db7c206b13
--- /dev/null
+++ b/modes/microscopy/.webpack/webpack.dev.js
@@ -0,0 +1,8 @@
+const path = require('path');
+const webpackCommon = require('./../../../.webpack/webpack.base.js');
+const SRC_DIR = path.join(__dirname, '../src');
+const DIST_DIR = path.join(__dirname, '../dist');
+
+module.exports = (env, argv) => {
+ return webpackCommon(env, argv, { SRC_DIR, DIST_DIR });
+};
diff --git a/modes/microscopy/src/index.tsx b/modes/microscopy/src/index.tsx
index 9044ad6eb3..ee0acade71 100644
--- a/modes/microscopy/src/index.tsx
+++ b/modes/microscopy/src/index.tsx
@@ -54,6 +54,7 @@ function modeFactory() {
toolbarService.addButtons(toolbarButtons);
toolbarService.createButtonSection('primary', [
'MeasurementTools',
+ 'select',
'dragPan',
]);
},
diff --git a/modes/microscopy/src/toolbarButtons.js b/modes/microscopy/src/toolbarButtons.js
index 73fb430f94..94c502175b 100644
--- a/modes/microscopy/src/toolbarButtons.js
+++ b/modes/microscopy/src/toolbarButtons.js
@@ -21,7 +21,6 @@ const _createActionButton = _createButton.bind(null, 'action');
const _createToggleButton = _createButton.bind(null, 'toggle');
const _createToolButton = _createButton.bind(null, 'tool');
-
const toolbarButtons = [
// Measurement
{
@@ -161,6 +160,25 @@ const toolbarButtons = [
],
},
},
+ // Select...
+ {
+ id: 'select',
+ type: 'ohif.radioGroup',
+ props: {
+ type: 'tool',
+ icon: 'tool-probe',
+ label: 'Select',
+ commands: [
+ {
+ commandName: 'setToolActive',
+ commandOptions: {
+ toolName: 'select',
+ },
+ context: 'MICROSCOPY',
+ },
+ ],
+ },
+ },
// Pan...
{
id: 'dragPan',
diff --git a/platform/viewer/.webpack/webpack.pwa.js b/platform/viewer/.webpack/webpack.pwa.js
index 2999495077..3a97b1c205 100644
--- a/platform/viewer/.webpack/webpack.pwa.js
+++ b/platform/viewer/.webpack/webpack.pwa.js
@@ -10,7 +10,6 @@ const CopyWebpackPlugin = require('copy-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { InjectManifest } = require('workbox-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
-const CopyPlugin = require('copy-webpack-plugin');
// ~~ Directories
const SRC_DIR = path.join(__dirname, '../src');
const DIST_DIR = path.join(__dirname, '../dist');
@@ -101,6 +100,18 @@ module.exports = (env, argv) => {
from: `${PUBLIC_DIR}/${APP_CONFIG}`,
to: `${DIST_DIR}/app-config.js`,
},
+ // Copy CSWIL build files
+ // {
+ // from:
+ // '../../../node_modules/cornerstone-wado-image-loader/dist/dynamic-import',
+ // to: DIST_DIR,
+ // },
+ // Copy Dicom Microscopy Viewer build files
+ {
+ from:
+ '../../../node_modules/dicom-microscopy-viewer/dist/dynamic-import',
+ to: DIST_DIR,
+ },
],
}),
// Generate "index.html" w/ correct includes/imports
@@ -120,24 +131,6 @@ module.exports = (env, argv) => {
// Need to exclude the theme as it is updated independently
exclude: [/theme/],
}),
- // new CopyPlugin({
- // patterns: [
- // {
- // from:
- // '../../../node_modules/cornerstone-wado-image-loader/dist/dynamic-import',
- // to: DIST_DIR,
- // },
- // ],
- // }),
- new CopyPlugin({
- patterns: [
- {
- from:
- '../../../node_modules/dicom-microscopy-viewer/dist/dynamic-import',
- to: DIST_DIR,
- },
- ],
- }),
],
// https://webpack.js.org/configuration/dev-server/
devServer: {
From 66892dd912761761f24cbfa68c8ba2920ff2affa Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Wed, 19 Apr 2023 13:44:59 -0400
Subject: [PATCH 41/51] [feat] microscopy - remove select tool from UI
---
modes/microscopy/src/index.tsx | 1 -
modes/microscopy/src/toolbarButtons.js | 19 -------------------
2 files changed, 20 deletions(-)
diff --git a/modes/microscopy/src/index.tsx b/modes/microscopy/src/index.tsx
index ee0acade71..9044ad6eb3 100644
--- a/modes/microscopy/src/index.tsx
+++ b/modes/microscopy/src/index.tsx
@@ -54,7 +54,6 @@ function modeFactory() {
toolbarService.addButtons(toolbarButtons);
toolbarService.createButtonSection('primary', [
'MeasurementTools',
- 'select',
'dragPan',
]);
},
diff --git a/modes/microscopy/src/toolbarButtons.js b/modes/microscopy/src/toolbarButtons.js
index 94c502175b..40078c5de4 100644
--- a/modes/microscopy/src/toolbarButtons.js
+++ b/modes/microscopy/src/toolbarButtons.js
@@ -160,25 +160,6 @@ const toolbarButtons = [
],
},
},
- // Select...
- {
- id: 'select',
- type: 'ohif.radioGroup',
- props: {
- type: 'tool',
- icon: 'tool-probe',
- label: 'Select',
- commands: [
- {
- commandName: 'setToolActive',
- commandOptions: {
- toolName: 'select',
- },
- context: 'MICROSCOPY',
- },
- ],
- },
- },
// Pan...
{
id: 'dragPan',
From 2bedc7b7d666bb621616d667053cadd4c8902f25 Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Wed, 19 Apr 2023 14:04:10 -0400
Subject: [PATCH 42/51] microscopy author
---
extensions/dicom-microscopy/package.json | 2 +-
modes/microscopy/package.json | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/extensions/dicom-microscopy/package.json b/extensions/dicom-microscopy/package.json
index c06a70065f..d08fd1523b 100644
--- a/extensions/dicom-microscopy/package.json
+++ b/extensions/dicom-microscopy/package.json
@@ -2,7 +2,7 @@
"name": "@ohif/extension-dicom-microscopy",
"version": "3.0.0",
"description": "OHIF extension for DICOM microscopy",
- "author": "Bill Wallace, md-prog, Radical Imaging",
+ "author": "Bill Wallace, md-prog",
"license": "MIT",
"main": "dist/umd/@ohif/extension-dicom-microscopy/index.umd.js",
"files": [
diff --git a/modes/microscopy/package.json b/modes/microscopy/package.json
index f1593ae985..8a8443e42f 100644
--- a/modes/microscopy/package.json
+++ b/modes/microscopy/package.json
@@ -2,7 +2,7 @@
"name": "@ohif/mode-microscopy",
"version": "3.0.0",
"description": "OHIF mode for DICOM microscopy",
- "author": "Bill Wallace, md-prog, Radical Imaging",
+ "author": "Bill Wallace, md-prog",
"license": "MIT",
"main": "dist/umd/@ohif/mode-microscopy/index.umd.js",
"files": [
From 82a674667697c71be7c0e53ac29b9258a3438b44 Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Wed, 19 Apr 2023 20:14:20 -0400
Subject: [PATCH 43/51] [fix] saving and loading SR
---
.../src/DicomMicroscopyViewport.tsx | 18 +++++++++++++++---
.../MicroscopyPanel/MicroscopyPanel.tsx | 12 +++++++++++-
.../src/services/MicroscopyService.js | 10 ++++++++++
.../src/tools/viewerManager.js | 10 ++++++----
.../src/utils/getSourceDisplaySet.js | 4 ++--
.../dicom-microscopy/src/utils/loadSR.js | 14 +++++++++++++-
platform/viewer/.webpack/webpack.pwa.js | 3 +++
7 files changed, 60 insertions(+), 11 deletions(-)
diff --git a/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
index 59b2e7fc9a..3297606040 100644
--- a/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
+++ b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx
@@ -87,7 +87,7 @@ class DicomMicroscopyViewport extends Component {
// install the microscopy renderer into the web page.
// you should only do this once.
- installOpenLayersRenderer(container, displaySet) {
+ async installOpenLayersRenderer(container, displaySet) {
const loadViewer = async metadata => {
const {
viewer: DicomMicroscopyViewer,
@@ -211,8 +211,20 @@ class DicomMicroscopyViewport extends Component {
});
};
- console.debug('Loading viewer metadata', displaySet);
- loadViewer(displaySet.others);
+ this.microscopyService.clearAnnotations();
+
+ let smDisplaySet = displaySet;
+ if (displaySet.Modality === 'SR') {
+ // for SR displaySet, let's load the actual image displaySet
+ smDisplaySet = displaySet.getSourceDisplaySet();
+ }
+ console.log('Loading viewer metadata', smDisplaySet);
+
+ await loadViewer(smDisplaySet.others);
+
+ if (displaySet.Modality === 'SR') {
+ displaySet.load(smDisplaySet);
+ }
}
componentDidMount() {
diff --git a/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx b/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx
index 5759bfd5c4..27f7920b60 100644
--- a/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx
+++ b/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx
@@ -119,6 +119,10 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) {
setSelectedAnnotation(selectedAnnotation);
};
+ const onAnnotationRemoved = () => {
+ onAnnotationUpdated();
+ };
+
const {
unsubscribe: unsubscribeAnnotationUpdated,
} = microscopyService.subscribe(
@@ -131,6 +135,12 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) {
MicroscopyEvents.ANNOTATION_SELECTED,
onAnnotationSelected
);
+ const {
+ unsubscribe: unsubscribeAnnotationRemoved,
+ } = microscopyService.subscribe(
+ MicroscopyEvents.ANNOTATION_REMOVED,
+ onAnnotationRemoved
+ );
onAnnotationUpdated();
onAnnotationSelected();
@@ -138,6 +148,7 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) {
return () => {
unsubscribeAnnotationUpdated();
unsubscribeAnnotationSelected();
+ unsubscribeAnnotationRemoved();
};
}, [studyInstanceUID]);
@@ -337,7 +348,6 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) {
});
});
- debugger;
const identifier = `ROI #${i + 1}`;
const group = new dcmjs.sr.templates.PlanarROIMeasurementsAndQualitativeEvaluations(
{
diff --git a/extensions/dicom-microscopy/src/services/MicroscopyService.js b/extensions/dicom-microscopy/src/services/MicroscopyService.js
index dce5b119da..547b7f9205 100644
--- a/extensions/dicom-microscopy/src/services/MicroscopyService.js
+++ b/extensions/dicom-microscopy/src/services/MicroscopyService.js
@@ -8,6 +8,7 @@ import { DicomMetadataStore, PubSubService } from '@ohif/core';
const EVENTS = {
ANNOTATION_UPDATED: 'annotationUpdated',
ANNOTATION_SELECTED: 'annotationSelected',
+ ANNOTATION_REMOVED: 'annotationRemoved',
RELABEL: 'relabel',
DELETE: 'delete',
};
@@ -140,6 +141,7 @@ export default class MicroscopyService extends PubSubService {
this.roiUids.delete(roiGraphic.uid);
this.annotations[roiGraphic.uid].destroy();
delete this.annotations[roiGraphic.uid];
+ this._broadcastEvent(EVENTS.ANNOTATION_REMOVED, roiGraphic);
}
/**
@@ -498,6 +500,14 @@ export default class MicroscopyService extends PubSubService {
managedViewers.forEach(managedViewer =>
managedViewer.removeRoiGraphic(uid)
);
+
+ if (this.annotations[uid]) {
+ this.roiUids.delete(uid);
+ this.annotations[uid].destroy();
+ delete this.annotations[uid];
+
+ this.publish(EVENTS.ANNOTATION_REMOVED, roiAnnotation);
+ }
}
/**
diff --git a/extensions/dicom-microscopy/src/tools/viewerManager.js b/extensions/dicom-microscopy/src/tools/viewerManager.js
index 01da1e8b83..bff7318d7e 100644
--- a/extensions/dicom-microscopy/src/tools/viewerManager.js
+++ b/extensions/dicom-microscopy/src/tools/viewerManager.js
@@ -220,10 +220,12 @@ class ViewerManager extends PubSubService {
* @param {String} label The label of the annotation.
*/
addRoiGraphicWithLabel(roiGraphic, label) {
- if (label) {
- if (!roiGraphic.properties) roiGraphic.properties = {};
- roiGraphic.properties.label = label;
- }
+ // NOTE: Dicom Microscopy Viewer will override styles for "Text" evalutations
+ // to hide all other geometries, we are not going to use its label.
+ // if (label) {
+ // if (!roiGraphic.properties) roiGraphic.properties = {};
+ // roiGraphic.properties.label = label;
+ // }
this.runSilently(() => this.viewer.addROI(roiGraphic, styles.default));
this._broadcastEvent(EVENTS.ADDED, {
diff --git a/extensions/dicom-microscopy/src/utils/getSourceDisplaySet.js b/extensions/dicom-microscopy/src/utils/getSourceDisplaySet.js
index 0052f772c3..d1455b639f 100644
--- a/extensions/dicom-microscopy/src/utils/getSourceDisplaySet.js
+++ b/extensions/dicom-microscopy/src/utils/getSourceDisplaySet.js
@@ -25,13 +25,13 @@ export default function getSourceDisplaySet(
))
);
- if (!referencedDisplaySet && otherDisplaySets.length == 1) {
+ if (!referencedDisplaySet && otherDisplaySets.length >= 1) {
console.warn(
'No display set with FrameOfReferenceUID',
ReferencedFrameOfReferenceUID,
'single series, assuming data error, defaulting to only series.'
);
- return otherDisplaySets[0];
+ return otherDisplaySets.find(displaySet => displaySet.Modality === 'SM');
}
return referencedDisplaySet;
diff --git a/extensions/dicom-microscopy/src/utils/loadSR.js b/extensions/dicom-microscopy/src/utils/loadSR.js
index fe2e8176f6..c690028bb9 100644
--- a/extensions/dicom-microscopy/src/utils/loadSR.js
+++ b/extensions/dicom-microscopy/src/utils/loadSR.js
@@ -34,7 +34,19 @@ export default async function loadSR(
const managedViewer = managedViewers[0];
for (let i = 0; i < rois.length; i++) {
- managedViewer.addRoiGraphicWithLabel(rois[i], labels[i]);
+ // NOTE: When saving Microscopy SR, we are attaching identifier property
+ // to each ROI, and when read for display, it is coming in as "TEXT"
+ // evaluation.
+ // As the Dicom Microscopy Viewer will override styles for "Text" evalutations
+ // to hide all other geometries, we are going to manually remove that
+ // evaluation item.
+ const roi = rois[i];
+ const roiSymbols = Object.getOwnPropertySymbols(roi);
+ const _properties = roiSymbols.find(s => s.description === 'properties');
+ const properties = roi[_properties];
+ properties['evaluations'] = [];
+
+ managedViewer.addRoiGraphicWithLabel(roi, labels[i]);
}
}
diff --git a/platform/viewer/.webpack/webpack.pwa.js b/platform/viewer/.webpack/webpack.pwa.js
index 3a97b1c205..dc1be025aa 100644
--- a/platform/viewer/.webpack/webpack.pwa.js
+++ b/platform/viewer/.webpack/webpack.pwa.js
@@ -111,6 +111,9 @@ module.exports = (env, argv) => {
from:
'../../../node_modules/dicom-microscopy-viewer/dist/dynamic-import',
to: DIST_DIR,
+ globOptions: {
+ ignore: ['*.js.map'],
+ },
},
],
}),
From 6c279cccf3aaf0f19e23dcc86fbb51c2c768306a Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Wed, 19 Apr 2023 20:29:10 -0400
Subject: [PATCH 44/51] [bugfix] - missing publish() function in
MicroscopyService, replace with _broadcastEvent
---
extensions/dicom-microscopy/src/services/MicroscopyService.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/extensions/dicom-microscopy/src/services/MicroscopyService.js b/extensions/dicom-microscopy/src/services/MicroscopyService.js
index 547b7f9205..d1ff76b5b9 100644
--- a/extensions/dicom-microscopy/src/services/MicroscopyService.js
+++ b/extensions/dicom-microscopy/src/services/MicroscopyService.js
@@ -506,7 +506,7 @@ export default class MicroscopyService extends PubSubService {
this.annotations[uid].destroy();
delete this.annotations[uid];
- this.publish(EVENTS.ANNOTATION_REMOVED, roiAnnotation);
+ this._broadcastEvent(EVENTS.ANNOTATION_REMOVED, roiAnnotation);
}
}
From 9bf4e0a4d648f6f711c3602e1d6011dfd31d8178 Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Mon, 24 Apr 2023 09:35:11 -0400
Subject: [PATCH 45/51] remove author section from readme
---
extensions/dicom-microscopy/README.md | 2 --
modes/microscopy/README.md | 2 --
2 files changed, 4 deletions(-)
diff --git a/extensions/dicom-microscopy/README.md b/extensions/dicom-microscopy/README.md
index 572a528425..cecf00af24 100644
--- a/extensions/dicom-microscopy/README.md
+++ b/extensions/dicom-microscopy/README.md
@@ -6,8 +6,6 @@ Adapter for *DICOM Microscopy Viewer* to get it integrated into OHIF Viewer.
- [DICOM Microscopy Viewer](https://github.com/ImagingDataCommons/dicom-microscopy-viewer) is a Vanilla JS library for web-based visualization of DICOM VL Whole Slide Microscopy Image datasets and derived information.
- [SLIM Viewer](https://github.com/imagingdatacommons/slim) is a single-page application for interactive visualization and annotation of digital whole slide microscopy images and derived image analysis results in standard DICOM format. The application is based on the dicom-microscopy-viewer JavaScript library and runs fully client side without any custom server components.
-## Author
-Bill Wallace, md-prog, Radical Imaging
## License
MIT
diff --git a/modes/microscopy/README.md b/modes/microscopy/README.md
index dc63fd2915..e4bcca9d9b 100644
--- a/modes/microscopy/README.md
+++ b/modes/microscopy/README.md
@@ -8,8 +8,6 @@ This mode uses [OHIF extension for microscopy](../../extensions/dicom-microscopy
- [DICOM Microscopy Viewer](https://github.com/ImagingDataCommons/dicom-microscopy-viewer) is a Vanilla JS library for web-based visualization of DICOM VL Whole Slide Microscopy Image datasets and derived information.
- [SLIM Viewer](https://github.com/imagingdatacommons/slim) is a single-page application for interactive visualization and annotation of digital whole slide microscopy images and derived image analysis results in standard DICOM format. The application is based on the dicom-microscopy-viewer JavaScript library and runs fully client side without any custom server components.
-## Author
-Bill Wallace, md-prog, Radical Imaging
## License
MIT
From eeaaed86934de04e136712be2b0206d83e0f4a79 Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Thu, 27 Apr 2023 12:08:55 -0400
Subject: [PATCH 46/51] refactor SR saving feature
---
.../MicroscopyPanel/MicroscopyPanel.tsx | 217 +-----------------
.../dicom-microscopy/src/utils/constructSR.ts | 213 +++++++++++++++++
.../src/utils/saveByteArray.ts | 12 +
3 files changed, 235 insertions(+), 207 deletions(-)
create mode 100644 extensions/dicom-microscopy/src/utils/constructSR.ts
create mode 100644 extensions/dicom-microscopy/src/utils/saveByteArray.ts
diff --git a/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx b/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx
index 27f7920b60..7d62982785 100644
--- a/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx
+++ b/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx
@@ -8,11 +8,12 @@ import {
} from '@ohif/core';
import { MeasurementTable, Icon, ButtonGroup, Button } from '@ohif/ui';
import { withTranslation, WithTranslation } from 'react-i18next';
-import DEVICE_OBSERVER_UID from '../../utils/DEVICE_OBSERVER_UID';
import { EVENTS as MicroscopyEvents } from '../../services/MicroscopyService';
import dcmjs from 'dcmjs';
import styles from '../../utils/styles';
import callInputDialog from '../../utils/callInputDialog';
+import constructSR from '../../utils/constructSR';
+import { saveByteArray } from '../../utils/saveByteArray';
let saving = false;
const { datasetToBuffer } = dcmjs.data;
@@ -49,19 +50,6 @@ const formatLength = (length, unit) => {
return `${(length * mult).toFixed(2)} ${unit}`;
};
-/**
- * Trigger file download from an array buffer
- * @param buffer
- * @param filename
- */
-function saveByteArray(buffer: ArrayBuffer, filename: string) {
- var blob = new Blob([buffer], { type: 'application/dicom' });
- var link = document.createElement('a');
- link.href = window.URL.createObjectURL(blob);
- link.download = filename;
- link.click();
-}
-
interface IMicroscopyPanelProps extends WithTranslation {
viewports: PropTypes.array;
activeViewportIndex: PropTypes.number;
@@ -201,7 +189,6 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) {
const saveFunction = async (SeriesDescription: string) => {
const dataSource = extensionManager.getActiveDataSource()[0];
const { onSaveComplete } = props;
- const imagingMeasurements = [];
const annotations = microscopyService.getAnnotationsForStudy(
studyInstanceUID
);
@@ -225,205 +212,21 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) {
typeof ds.metadata.SeriesNumber === 'number'
);
+ // Generate next series number
const seriesNumbers = dsWithMetadata.map(ds => ds.metadata.SeriesNumber);
const maxSeriesNumber = Math.max(...seriesNumbers, 4700);
const SeriesNumber = maxSeriesNumber + 1;
const { instance: metadata } = smDisplaySet;
- // Handle malformed data
- if (!metadata.SpecimenDescriptionSequence) {
- metadata.SpecimenDescriptionSequence = {
- SpecimenUID: metadata.SeriesInstanceUID,
- SpecimenIdentifier: metadata.SeriesDescription,
- };
- }
- const { SpecimenDescriptionSequence } = metadata;
-
- const observationContext = new dcmjs.sr.templates.ObservationContext({
- observerPersonContext: new dcmjs.sr.templates.ObserverContext({
- observerType: new dcmjs.sr.coding.CodedConcept({
- value: '121006',
- schemeDesignator: 'DCM',
- meaning: 'Person',
- }),
- observerIdentifyingAttributes: new dcmjs.sr.templates.PersonObserverIdentifyingAttributes(
- {
- name: '@ohif/extension-dicom-microscopy',
- }
- ),
- }),
- observerDeviceContext: new dcmjs.sr.templates.ObserverContext({
- observerType: new dcmjs.sr.coding.CodedConcept({
- value: '121007',
- schemeDesignator: 'DCM',
- meaning: 'Device',
- }),
- observerIdentifyingAttributes: new dcmjs.sr.templates.DeviceObserverIdentifyingAttributes(
- {
- uid: DEVICE_OBSERVER_UID,
- }
- ),
- }),
- subjectContext: new dcmjs.sr.templates.SubjectContext({
- subjectClass: new dcmjs.sr.coding.CodedConcept({
- value: '121027',
- schemeDesignator: 'DCM',
- meaning: 'Specimen',
- }),
- subjectClassSpecificContext: new dcmjs.sr.templates.SubjectContextSpecimen(
- {
- uid: SpecimenDescriptionSequence.SpecimenUID,
- identifier:
- SpecimenDescriptionSequence.SpecimenIdentifier ||
- metadata.SeriesInstanceUID,
- containerIdentifier:
- metadata.ContainerIdentifier || metadata.SeriesInstanceUID,
- }
- ),
- }),
- });
-
- for (let i = 0; i < annotations.length; i++) {
- const { roiGraphic: roi, label } = annotations[i];
- let {
- measurements,
- evaluations,
- marker,
- presentationState,
- } = roi.properties;
-
- console.debug('[SR] storing marker...', marker);
- console.debug('[SR] storing measurements...', measurements);
- console.debug('[SR] storing evaluations...', evaluations);
- console.debug('[SR] storing presentation state...', presentationState);
-
- if (presentationState) presentationState.marker = marker;
-
- /** Avoid incompatibility with dcmjs */
- measurements = measurements.map((measurement: any) => {
- const ConceptName = Array.isArray(measurement.ConceptNameCodeSequence)
- ? measurement.ConceptNameCodeSequence[0]
- : measurement.ConceptNameCodeSequence;
-
- const MeasuredValue = Array.isArray(measurement.MeasuredValueSequence)
- ? measurement.MeasuredValueSequence[0]
- : measurement.MeasuredValueSequence;
-
- const MeasuredValueUnits = Array.isArray(
- MeasuredValue.MeasurementUnitsCodeSequence
- )
- ? MeasuredValue.MeasurementUnitsCodeSequence[0]
- : MeasuredValue.MeasurementUnitsCodeSequence;
-
- return new dcmjs.sr.valueTypes.NumContentItem({
- name: new dcmjs.sr.coding.CodedConcept({
- meaning: ConceptName.CodeMeaning,
- value: ConceptName.CodeValue,
- schemeDesignator: ConceptName.CodingSchemeDesignator,
- }),
- value: MeasuredValue.NumericValue,
- unit: new dcmjs.sr.coding.CodedConcept({
- value: MeasuredValueUnits.CodeValue,
- meaning: MeasuredValueUnits.CodeMeaning,
- schemeDesignator: MeasuredValueUnits.CodingSchemeDesignator,
- }),
- });
- });
-
- /** Avoid incompatibility with dcmjs */
- evaluations = evaluations.map((evaluation: any) => {
- const ConceptName = Array.isArray(evaluation.ConceptNameCodeSequence)
- ? evaluation.ConceptNameCodeSequence[0]
- : evaluation.ConceptNameCodeSequence;
-
- return new dcmjs.sr.valueTypes.TextContentItem({
- name: new dcmjs.sr.coding.CodedConcept({
- value: ConceptName.CodeValue,
- meaning: ConceptName.CodeMeaning,
- schemeDesignator: ConceptName.CodingSchemeDesignator,
- }),
- value: evaluation.TextValue,
- relationshipType: evaluation.RelationshipType,
- });
- });
-
- const identifier = `ROI #${i + 1}`;
- const group = new dcmjs.sr.templates.PlanarROIMeasurementsAndQualitativeEvaluations(
- {
- trackingIdentifier: new dcmjs.sr.templates.TrackingIdentifier({
- uid: roi.uid,
- identifier: presentationState
- ? identifier.concat(`(${JSON.stringify(presentationState)})`)
- : identifier,
- }),
- referencedRegion: new dcmjs.sr.contentItems.ImageRegion3D({
- graphicType: roi.scoord3d.graphicType,
- graphicData: roi.scoord3d.graphicData,
- frameOfReferenceUID: roi.scoord3d.frameOfReferenceUID,
- }),
- findingType: new dcmjs.sr.coding.CodedConcept({
- value: label,
- schemeDesignator: '@ohif/extension-dicom-microscopy',
- meaning: 'FREETEXT',
- }),
- /** Evaluations will conflict with current tracking identifier */
- /** qualitativeEvaluations: evaluations, */
- measurements,
- }
- );
- imagingMeasurements.push(...group);
- }
-
- const measurementReport = new dcmjs.sr.templates.MeasurementReport({
- languageOfContentItemAndDescendants: new dcmjs.sr.templates.LanguageOfContentItemAndDescendants(
- {}
- ),
- observationContext,
- procedureReported: new dcmjs.sr.coding.CodedConcept({
- value: '112703',
- schemeDesignator: 'DCM',
- meaning: 'Whole Slide Imaging',
- }),
- imagingMeasurements,
- });
-
- const dataset = new dcmjs.sr.documents.Comprehensive3DSR({
- content: measurementReport[0],
- evidence: [metadata],
- seriesInstanceUID: dcmjs.data.DicomMetaDictionary.uid(),
- seriesNumber: SeriesNumber,
- seriesDescription:
- SeriesDescription || 'Whole slide imaging structured report',
- sopInstanceUID: dcmjs.data.DicomMetaDictionary.uid(),
- instanceNumber: 1,
- manufacturer: 'dcmjs-org',
- });
- dataset.SpecificCharacterSet = 'ISO_IR 192';
- const fileMetaInformationVersionArray = new Uint8Array(2);
- fileMetaInformationVersionArray[1] = 1;
-
- dataset._meta = {
- FileMetaInformationVersion: {
- Value: [fileMetaInformationVersionArray.buffer], // TODO
- vr: 'OB',
- },
- MediaStorageSOPClassUID: dataset.sopClassUID,
- MediaStorageSOPInstanceUID: dataset.sopInstanceUID,
- TransferSyntaxUID: {
- Value: ['1.2.840.10008.1.2.1'],
- vr: 'UI',
- },
- ImplementationClassUID: {
- Value: [dcmjs.data.DicomMetaDictionary.uid()],
- vr: 'UI',
- },
- ImplementationVersionName: {
- Value: ['@ohif/extension-dicom-microscopy'],
- vr: 'SH',
- },
- };
+ // construct SR dataset
+ const dataset = constructSR(
+ metadata,
+ { SeriesDescription, SeriesNumber },
+ annotations
+ );
+ // Save in DICOM format
try {
if (dataSource) {
if (dataSource.wadoRoot == 'saveDicom') {
diff --git a/extensions/dicom-microscopy/src/utils/constructSR.ts b/extensions/dicom-microscopy/src/utils/constructSR.ts
new file mode 100644
index 0000000000..ff78f13fe3
--- /dev/null
+++ b/extensions/dicom-microscopy/src/utils/constructSR.ts
@@ -0,0 +1,213 @@
+import dcmjs from 'dcmjs';
+import DEVICE_OBSERVER_UID from './DEVICE_OBSERVER_UID';
+
+/**
+ *
+ * @param {*} metadata - Microscopy Image instance metadata
+ * @param {*} SeriesDescription - SR description
+ * @param {*} annotations - Annotations
+ *
+ * @return Comprehensive3DSR dataset
+ */
+export default function constructSR(
+ metadata,
+ { SeriesDescription, SeriesNumber },
+ annotations
+) {
+ // Handle malformed data
+ if (!metadata.SpecimenDescriptionSequence) {
+ metadata.SpecimenDescriptionSequence = {
+ SpecimenUID: metadata.SeriesInstanceUID,
+ SpecimenIdentifier: metadata.SeriesDescription,
+ };
+ }
+ const { SpecimenDescriptionSequence } = metadata;
+
+ // construct Comprehensive3DSR dataset
+ const observationContext = new dcmjs.sr.templates.ObservationContext({
+ observerPersonContext: new dcmjs.sr.templates.ObserverContext({
+ observerType: new dcmjs.sr.coding.CodedConcept({
+ value: '121006',
+ schemeDesignator: 'DCM',
+ meaning: 'Person',
+ }),
+ observerIdentifyingAttributes: new dcmjs.sr.templates.PersonObserverIdentifyingAttributes(
+ {
+ name: '@ohif/extension-dicom-microscopy',
+ }
+ ),
+ }),
+ observerDeviceContext: new dcmjs.sr.templates.ObserverContext({
+ observerType: new dcmjs.sr.coding.CodedConcept({
+ value: '121007',
+ schemeDesignator: 'DCM',
+ meaning: 'Device',
+ }),
+ observerIdentifyingAttributes: new dcmjs.sr.templates.DeviceObserverIdentifyingAttributes(
+ {
+ uid: DEVICE_OBSERVER_UID,
+ }
+ ),
+ }),
+ subjectContext: new dcmjs.sr.templates.SubjectContext({
+ subjectClass: new dcmjs.sr.coding.CodedConcept({
+ value: '121027',
+ schemeDesignator: 'DCM',
+ meaning: 'Specimen',
+ }),
+ subjectClassSpecificContext: new dcmjs.sr.templates.SubjectContextSpecimen(
+ {
+ uid: SpecimenDescriptionSequence.SpecimenUID,
+ identifier:
+ SpecimenDescriptionSequence.SpecimenIdentifier ||
+ metadata.SeriesInstanceUID,
+ containerIdentifier:
+ metadata.ContainerIdentifier || metadata.SeriesInstanceUID,
+ }
+ ),
+ }),
+ });
+
+ const imagingMeasurements = [];
+ for (let i = 0; i < annotations.length; i++) {
+ const { roiGraphic: roi, label } = annotations[i];
+ let {
+ measurements,
+ evaluations,
+ marker,
+ presentationState,
+ } = roi.properties;
+
+ console.debug('[SR] storing marker...', marker);
+ console.debug('[SR] storing measurements...', measurements);
+ console.debug('[SR] storing evaluations...', evaluations);
+ console.debug('[SR] storing presentation state...', presentationState);
+
+ if (presentationState) presentationState.marker = marker;
+
+ /** Avoid incompatibility with dcmjs */
+ measurements = measurements.map((measurement: any) => {
+ const ConceptName = Array.isArray(measurement.ConceptNameCodeSequence)
+ ? measurement.ConceptNameCodeSequence[0]
+ : measurement.ConceptNameCodeSequence;
+
+ const MeasuredValue = Array.isArray(measurement.MeasuredValueSequence)
+ ? measurement.MeasuredValueSequence[0]
+ : measurement.MeasuredValueSequence;
+
+ const MeasuredValueUnits = Array.isArray(
+ MeasuredValue.MeasurementUnitsCodeSequence
+ )
+ ? MeasuredValue.MeasurementUnitsCodeSequence[0]
+ : MeasuredValue.MeasurementUnitsCodeSequence;
+
+ return new dcmjs.sr.valueTypes.NumContentItem({
+ name: new dcmjs.sr.coding.CodedConcept({
+ meaning: ConceptName.CodeMeaning,
+ value: ConceptName.CodeValue,
+ schemeDesignator: ConceptName.CodingSchemeDesignator,
+ }),
+ value: MeasuredValue.NumericValue,
+ unit: new dcmjs.sr.coding.CodedConcept({
+ value: MeasuredValueUnits.CodeValue,
+ meaning: MeasuredValueUnits.CodeMeaning,
+ schemeDesignator: MeasuredValueUnits.CodingSchemeDesignator,
+ }),
+ });
+ });
+
+ /** Avoid incompatibility with dcmjs */
+ evaluations = evaluations.map((evaluation: any) => {
+ const ConceptName = Array.isArray(evaluation.ConceptNameCodeSequence)
+ ? evaluation.ConceptNameCodeSequence[0]
+ : evaluation.ConceptNameCodeSequence;
+
+ return new dcmjs.sr.valueTypes.TextContentItem({
+ name: new dcmjs.sr.coding.CodedConcept({
+ value: ConceptName.CodeValue,
+ meaning: ConceptName.CodeMeaning,
+ schemeDesignator: ConceptName.CodingSchemeDesignator,
+ }),
+ value: evaluation.TextValue,
+ relationshipType: evaluation.RelationshipType,
+ });
+ });
+
+ const identifier = `ROI #${i + 1}`;
+ const group = new dcmjs.sr.templates.PlanarROIMeasurementsAndQualitativeEvaluations(
+ {
+ trackingIdentifier: new dcmjs.sr.templates.TrackingIdentifier({
+ uid: roi.uid,
+ identifier: presentationState
+ ? identifier.concat(`(${JSON.stringify(presentationState)})`)
+ : identifier,
+ }),
+ referencedRegion: new dcmjs.sr.contentItems.ImageRegion3D({
+ graphicType: roi.scoord3d.graphicType,
+ graphicData: roi.scoord3d.graphicData,
+ frameOfReferenceUID: roi.scoord3d.frameOfReferenceUID,
+ }),
+ findingType: new dcmjs.sr.coding.CodedConcept({
+ value: label,
+ schemeDesignator: '@ohif/extension-dicom-microscopy',
+ meaning: 'FREETEXT',
+ }),
+ /** Evaluations will conflict with current tracking identifier */
+ /** qualitativeEvaluations: evaluations, */
+ measurements,
+ }
+ );
+ imagingMeasurements.push(...group);
+ }
+
+ const measurementReport = new dcmjs.sr.templates.MeasurementReport({
+ languageOfContentItemAndDescendants: new dcmjs.sr.templates.LanguageOfContentItemAndDescendants(
+ {}
+ ),
+ observationContext,
+ procedureReported: new dcmjs.sr.coding.CodedConcept({
+ value: '112703',
+ schemeDesignator: 'DCM',
+ meaning: 'Whole Slide Imaging',
+ }),
+ imagingMeasurements,
+ });
+
+ const dataset = new dcmjs.sr.documents.Comprehensive3DSR({
+ content: measurementReport[0],
+ evidence: [metadata],
+ seriesInstanceUID: dcmjs.data.DicomMetaDictionary.uid(),
+ seriesNumber: SeriesNumber,
+ seriesDescription:
+ SeriesDescription || 'Whole slide imaging structured report',
+ sopInstanceUID: dcmjs.data.DicomMetaDictionary.uid(),
+ instanceNumber: 1,
+ manufacturer: 'dcmjs-org',
+ });
+ dataset.SpecificCharacterSet = 'ISO_IR 192';
+ const fileMetaInformationVersionArray = new Uint8Array(2);
+ fileMetaInformationVersionArray[1] = 1;
+
+ dataset._meta = {
+ FileMetaInformationVersion: {
+ Value: [fileMetaInformationVersionArray.buffer], // TODO
+ vr: 'OB',
+ },
+ MediaStorageSOPClassUID: dataset.sopClassUID,
+ MediaStorageSOPInstanceUID: dataset.sopInstanceUID,
+ TransferSyntaxUID: {
+ Value: ['1.2.840.10008.1.2.1'],
+ vr: 'UI',
+ },
+ ImplementationClassUID: {
+ Value: [dcmjs.data.DicomMetaDictionary.uid()],
+ vr: 'UI',
+ },
+ ImplementationVersionName: {
+ Value: ['@ohif/extension-dicom-microscopy'],
+ vr: 'SH',
+ },
+ };
+
+ return dataset;
+}
diff --git a/extensions/dicom-microscopy/src/utils/saveByteArray.ts b/extensions/dicom-microscopy/src/utils/saveByteArray.ts
new file mode 100644
index 0000000000..d264fe7cda
--- /dev/null
+++ b/extensions/dicom-microscopy/src/utils/saveByteArray.ts
@@ -0,0 +1,12 @@
+/**
+ * Trigger file download from an array buffer
+ * @param buffer
+ * @param filename
+ */
+export function saveByteArray(buffer: ArrayBuffer, filename: string) {
+ var blob = new Blob([buffer], { type: 'application/dicom' });
+ var link = document.createElement('a');
+ link.href = window.URL.createObjectURL(blob);
+ link.download = filename;
+ link.click();
+}
From 22a50854030f4d73e5efc97d9740d84978c999f1 Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Thu, 27 Apr 2023 12:28:23 -0400
Subject: [PATCH 47/51] fix webpack config after merge
---
platform/viewer/.webpack/webpack.pwa.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/platform/viewer/.webpack/webpack.pwa.js b/platform/viewer/.webpack/webpack.pwa.js
index 55666da97a..653fe7cbae 100644
--- a/platform/viewer/.webpack/webpack.pwa.js
+++ b/platform/viewer/.webpack/webpack.pwa.js
@@ -134,7 +134,7 @@ module.exports = (env, argv) => {
// Need to exclude the theme as it is updated independently
exclude: [/theme/],
}),
- new CopyPlugin({
+ new CopyWebpackPlugin({
patterns: [
{
from:
From eda7edb768251d9b87a730bfaf30143847c42283 Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Thu, 27 Apr 2023 12:31:20 -0400
Subject: [PATCH 48/51] [bugfix] repeated import
---
platform/ui/src/components/Icon/getIcon.js | 1 -
1 file changed, 1 deletion(-)
diff --git a/platform/ui/src/components/Icon/getIcon.js b/platform/ui/src/components/Icon/getIcon.js
index 7297d58f92..3627697af5 100644
--- a/platform/ui/src/components/Icon/getIcon.js
+++ b/platform/ui/src/components/Icon/getIcon.js
@@ -97,7 +97,6 @@ import toolRectangle from './../../assets/icons/tool-rectangle.svg';
import toolFusionColor from './../../assets/icons/tool-fusion-color.svg';
import toolCreateThreshold from './../../assets/icons/tool-create-threshold.svg';
import toolCalibration from './../../assets/icons/tool-calibration.svg';
-import toolCircle from './../../assets/icons/tool-circle.svg';
import toolFreehand from './../../assets/icons/tool-freehand.svg';
import toolFreehandPolygon from './../../assets/icons/tool-freehand-polygon.svg';
import toolPolygon from './../../assets/icons/tool-polygon.svg';
From 3e5abf7daf8a69b868321e58956f4fc496828898 Mon Sep 17 00:00:00 2001
From: Alireza
Date: Thu, 27 Apr 2023 13:11:55 -0400
Subject: [PATCH 49/51] fix e2e
---
.../dicom-microscopy/src/getCommandsModule.ts | 47 -------------------
1 file changed, 47 deletions(-)
diff --git a/extensions/dicom-microscopy/src/getCommandsModule.ts b/extensions/dicom-microscopy/src/getCommandsModule.ts
index 5bb568913c..db82106237 100644
--- a/extensions/dicom-microscopy/src/getCommandsModule.ts
+++ b/extensions/dicom-microscopy/src/getCommandsModule.ts
@@ -122,13 +122,6 @@ export default function getCommandsModule({
}
},
- rotateViewport: ({ rotation }) => {},
- flipViewportHorizontal: () => {},
- flipViewportVertical: () => {},
- invertViewport: ({ element }) => {},
- resetViewport: () => {},
- scaleViewport: ({ direction }) => {},
- scroll: ({ direction }) => {},
incrementActiveViewport: () => {
const { activeViewportIndex, viewports } = viewportGridService.getState();
const nextViewportIndex = (activeViewportIndex + 1) % viewports.length;
@@ -177,16 +170,6 @@ export default function getCommandsModule({
storeContexts: [] as any[],
options: {},
},
- rotateViewportCW: {
- commandFn: actions.rotateViewport,
- storeContexts: [] as any[],
- options: { rotation: 90 },
- },
- rotateViewportCCW: {
- commandFn: actions.rotateViewport,
- storeContexts: [] as any[],
- options: { rotation: -90 },
- },
incrementActiveViewport: {
commandFn: actions.incrementActiveViewport,
storeContexts: [] as any[],
@@ -195,36 +178,6 @@ export default function getCommandsModule({
commandFn: actions.decrementActiveViewport,
storeContexts: [] as any[],
},
- flipViewportHorizontal: {
- commandFn: actions.flipViewportHorizontal,
- storeContexts: [] as any[],
- options: {},
- },
- flipViewportVertical: {
- commandFn: actions.flipViewportVertical,
- storeContexts: [] as any[],
- options: {},
- },
- resetViewport: {
- commandFn: actions.resetViewport,
- storeContexts: [] as any[],
- options: {},
- },
- scaleUpViewport: {
- commandFn: actions.scaleViewport,
- storeContexts: [] as any[],
- options: { direction: 1 },
- },
- scaleDownViewport: {
- commandFn: actions.scaleViewport,
- storeContexts: [] as any[],
- options: { direction: -1 },
- },
- fitViewportToWindow: {
- commandFn: actions.scaleViewport,
- storeContexts: [] as any[],
- options: { direction: 0 },
- },
toggleOverlays: {
commandFn: actions.toggleOverlays,
storeContexts: [] as any[],
From bf9bd7a0b6c191af136e0048dfc25f086fb3d238 Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Thu, 27 Apr 2023 14:02:56 -0400
Subject: [PATCH 50/51] webpack configuration
---
platform/viewer/.webpack/webpack.pwa.js | 21 ++++++---------------
1 file changed, 6 insertions(+), 15 deletions(-)
diff --git a/platform/viewer/.webpack/webpack.pwa.js b/platform/viewer/.webpack/webpack.pwa.js
index 653fe7cbae..fa403148cb 100644
--- a/platform/viewer/.webpack/webpack.pwa.js
+++ b/platform/viewer/.webpack/webpack.pwa.js
@@ -100,12 +100,6 @@ module.exports = (env, argv) => {
from: `${PUBLIC_DIR}/${APP_CONFIG}`,
to: `${DIST_DIR}/app-config.js`,
},
- // Copy CSWIL build files
- // {
- // from:
- // '../../../node_modules/cornerstone-wado-image-loader/dist/dynamic-import',
- // to: DIST_DIR,
- // },
// Copy Dicom Microscopy Viewer build files
{
from:
@@ -115,6 +109,12 @@ module.exports = (env, argv) => {
ignore: ['*.js.map'],
},
},
+ // Copy dicom-image-loader build files
+ {
+ from:
+ '../../../node_modules/@cornerstonejs/dicom-image-loader/dist/dynamic-import',
+ to: DIST_DIR,
+ },
],
}),
// Generate "index.html" w/ correct includes/imports
@@ -134,15 +134,6 @@ module.exports = (env, argv) => {
// Need to exclude the theme as it is updated independently
exclude: [/theme/],
}),
- new CopyWebpackPlugin({
- patterns: [
- {
- from:
- '../../../node_modules/@cornerstonejs/dicom-image-loader/dist/dynamic-import',
- to: DIST_DIR,
- },
- ],
- }),
],
// https://webpack.js.org/configuration/dev-server/
devServer: {
From 2693c5d3255a4b609963beff500ac34abb6a39c2 Mon Sep 17 00:00:00 2001
From: md-prog <26860200+md-prog@users.noreply.github.com>
Date: Thu, 27 Apr 2023 14:03:09 -0400
Subject: [PATCH 51/51] hide "Create report" button for microscopy
---
.../src/components/MicroscopyPanel/MicroscopyPanel.tsx | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx b/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx
index 7d62982785..88d5f43923 100644
--- a/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx
+++ b/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx
@@ -381,7 +381,8 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) {
- {promptSave && (
+ {/* Let's hide the save button for now, as export SR for SM is a proof of concept */}
+ {/*{promptSave && (
{props.t('Create Report')}
- )}
+ )} */}
{/*