diff --git a/.circleci/config.yml b/.circleci/config.yml index ed92514f70..f3a6a7ce7f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -40,7 +40,8 @@ jobs: - run: bun install - persist_to_workspace: root: ~/repo - paths: . + paths: + - "." BUILD: <<: *defaults @@ -64,22 +65,6 @@ jobs: - commit.txt - version.json - API_CHECK: - <<: *defaults - steps: - - attach_workspace: - at: ~/repo - - install_bun - - run: bun install -d ajv - - run: bun run api-check - FORMAT_CHECK: - <<: *defaults - steps: - - attach_workspace: - at: ~/repo - - install_bun - - run: bun run format-check - # https://circleci.com/docs/2.0/collect-test-data/#karma TEST: <<: *defaults @@ -117,7 +102,8 @@ jobs: mkdir ~/.ssh/ && echo -e "Host github.com\n\tStrictHostKeyChecking no\n" > ~/.ssh/config - add_ssh_keys: - fingerprints: 7e:0f:5b:bb:e3:7a:2e:2f:b4:85:bd:66:09:69:cb:f2 + fingerprints: + - "7e:0f:5b:bb:e3:7a:2e:2f:b4:85:bd:66:09:69:cb:f2" - run: echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc - run: git config --global user.email "contact@ohif.org" - run: git config --global user.name "ohif-bot" @@ -150,12 +136,6 @@ workflows: - BUILD: requires: - CHECKOUT - - FORMAT_CHECK: - requires: - - BUILD - - API_CHECK: - requires: - - BUILD - TEST: requires: - CHECKOUT # TODO: Also require build? diff --git a/.eslintrc.json b/.eslintrc.json index d17eb9bf44..a52bf493f6 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -55,7 +55,15 @@ "tsconfigRootDir": "./", "warnOnUnsupportedTypeScriptVersion": false }, - "ignorePatterns": ["packages/docs", "dist", "**/*_test.js", "**/*_jest.js", "**/*babel*", "**/*.d.ts", "**/*_types.ts"], + "ignorePatterns": [ + "packages/docs", + "dist", + "**/*_test.js", + "**/*_jest.js", + "**/*babel*", + "**/*.d.ts", + "**/*_types.ts" + ], "rules": { "import/no-cycle": ["error", { "maxDepth": 15 }], // Enforce consistent brace style for all control statements for readability diff --git a/.github/workflows/format-check.yml b/.github/workflows/format-check.yml new file mode 100644 index 0000000000..8337b29e2e --- /dev/null +++ b/.github/workflows/format-check.yml @@ -0,0 +1,38 @@ +name: Format Check + +on: + pull_request: + branches: [ '*' ] + push: + branches: [ main, beta ] + +jobs: + format-check: + timeout-minutes: 30 + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20.18.1' + cache: 'npm' + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Install dependencies + run: bun install + + - name: Build dependencies + run: bun run build:esm + + - name: Run format check + run: bun run format-check + env: + NODE_OPTIONS: --max_old_space_size=32896 diff --git a/bun.lock b/bun.lock index ae1b7e7bbf..e323a97252 100644 --- a/bun.lock +++ b/bun.lock @@ -43,6 +43,7 @@ "autoprefixer": "^10.4.14", "babel-loader": "9.1.2", "babel-plugin-istanbul": "^6.1.1", + "babel-plugin-transform-import-meta": "2.3.2", "chai": "^5.1.2", "clean-webpack-plugin": "^4.0.0", "commander": "^10.0.1", @@ -130,7 +131,7 @@ }, "packages/adapters": { "name": "@cornerstonejs/adapters", - "version": "3.0.0-beta.4", + "version": "3.0.0-beta.5", "dependencies": { "@babel/runtime-corejs2": "^7.17.8", "buffer": "^6.0.3", @@ -139,13 +140,13 @@ "ndarray": "^1.0.19", }, "peerDependencies": { - "@cornerstonejs/core": "^3.0.0-beta.4", - "@cornerstonejs/tools": "^3.0.0-beta.4", + "@cornerstonejs/core": "^3.0.0-beta.5", + "@cornerstonejs/tools": "^3.0.0-beta.5", }, }, "packages/ai": { "name": "@cornerstonejs/ai", - "version": "3.0.0-beta.4", + "version": "3.0.0-beta.5", "dependencies": { "@babel/runtime-corejs2": "^7.17.8", "buffer": "^6.0.3", @@ -157,13 +158,13 @@ "onnxruntime-web": "1.17.1", }, "peerDependencies": { - "@cornerstonejs/core": "^2.19.14", - "@cornerstonejs/tools": "^2.19.14", + "@cornerstonejs/core": "^3.0.0-beta.5", + "@cornerstonejs/tools": "^3.0.0-beta.5", }, }, "packages/core": { "name": "@cornerstonejs/core", - "version": "3.0.0-beta.4", + "version": "3.0.0-beta.5", "dependencies": { "@kitware/vtk.js": "32.9.0", "comlink": "^4.4.1", @@ -172,7 +173,7 @@ }, "packages/dicomImageLoader": { "name": "@cornerstonejs/dicom-image-loader", - "version": "3.0.0-beta.4", + "version": "3.0.0-beta.5", "dependencies": { "@cornerstonejs/codec-charls": "^1.2.3", "@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2", @@ -184,7 +185,7 @@ "uuid": "^9.0.0", }, "peerDependencies": { - "@cornerstonejs/core": "^3.0.0-beta.4", + "@cornerstonejs/core": "^3.0.0-beta.5", "dicom-parser": "^1.8.9", }, }, @@ -192,11 +193,11 @@ "name": "docs", "version": "2.1.10", "dependencies": { - "@cornerstonejs/adapters": "^3.0.0-beta.4", - "@cornerstonejs/core": "^3.0.0-beta.4", - "@cornerstonejs/dicom-image-loader": "^3.0.0-beta.4", - "@cornerstonejs/nifti-volume-loader": "^3.0.0-beta.4", - "@cornerstonejs/tools": "^3.0.0-beta.4", + "@cornerstonejs/adapters": "^3.0.0-beta.5", + "@cornerstonejs/core": "^3.0.0-beta.5", + "@cornerstonejs/dicom-image-loader": "^3.0.0-beta.5", + "@cornerstonejs/nifti-volume-loader": "^3.0.0-beta.5", + "@cornerstonejs/tools": "^3.0.0-beta.5", "@docusaurus/core": "3.6.3", "@docusaurus/faster": "3.6.3", "@docusaurus/module-type-aliases": "3.6.3", @@ -234,7 +235,7 @@ }, "packages/labelmap-interpolation": { "name": "@cornerstonejs/labelmap-interpolation", - "version": "3.0.0-beta.4", + "version": "3.0.0-beta.5", "dependencies": { "@itk-wasm/morphological-contour-interpolation": "1.1.0", "itk-wasm": "1.0.0-b.165", @@ -247,17 +248,17 @@ }, "packages/nifti-volume-loader": { "name": "@cornerstonejs/nifti-volume-loader", - "version": "3.0.0-beta.4", + "version": "3.0.0-beta.5", "dependencies": { "nifti-reader-js": "^0.6.8", }, "peerDependencies": { - "@cornerstonejs/core": "^3.0.0-beta.4", + "@cornerstonejs/core": "^3.0.0-beta.5", }, }, "packages/polymorphic-segmentation": { "name": "@cornerstonejs/polymorphic-segmentation", - "version": "3.0.0-beta.4", + "version": "3.0.0-beta.5", "dependencies": { "@icr/polyseg-wasm": "0.4.0", }, @@ -269,7 +270,7 @@ }, "packages/tools": { "name": "@cornerstonejs/tools", - "version": "3.0.0-beta.4", + "version": "3.0.0-beta.5", "dependencies": { "@types/offscreencanvas": "2019.7.3", "comlink": "^4.4.1", @@ -279,7 +280,7 @@ "canvas": "^2.11.2", }, "peerDependencies": { - "@cornerstonejs/core": "^3.0.0-beta.4", + "@cornerstonejs/core": "^3.0.0-beta.5", "@kitware/vtk.js": "32.9.0", "@types/d3-array": "^3.0.4", "@types/d3-interpolate": "^3.0.1", @@ -542,7 +543,7 @@ "@babel/plugin-transform-reserved-words": ["@babel/plugin-transform-reserved-words@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg=="], - "@babel/plugin-transform-runtime": ["@babel/plugin-transform-runtime@7.25.9", "", { "dependencies": { "@babel/helper-module-imports": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9", "babel-plugin-polyfill-corejs2": "^0.4.10", "babel-plugin-polyfill-corejs3": "^0.10.6", "babel-plugin-polyfill-regenerator": "^0.6.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-nZp7GlEl+yULJrClz0SwHPqir3lc0zsPrDHQUcxGspSL7AKrexNSEfTbfqnDNJUO13bgKyfuOLMF8Xqtu8j3YQ=="], + "@babel/plugin-transform-runtime": ["@babel/plugin-transform-runtime@7.26.9", "", { "dependencies": { "@babel/helper-module-imports": "^7.25.9", "@babel/helper-plugin-utils": "^7.26.5", "babel-plugin-polyfill-corejs2": "^0.4.10", "babel-plugin-polyfill-corejs3": "^0.10.6", "babel-plugin-polyfill-regenerator": "^0.6.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Jf+8y9wXQbbxvVYTM8gO5oEF2POdNji0NMltEkG7FtmzD9PVz7/lxpqSdTvwsjTMU5HIHuDVNf2SOxLkWi+wPQ=="], "@babel/plugin-transform-shorthand-properties": ["@babel/plugin-transform-shorthand-properties@7.25.9", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng=="], @@ -574,9 +575,9 @@ "@babel/runtime": ["@babel/runtime@7.21.5", "", { "dependencies": { "regenerator-runtime": "^0.13.11" } }, "sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q=="], - "@babel/runtime-corejs2": ["@babel/runtime-corejs2@7.26.7", "", { "dependencies": { "core-js": "^2.6.12", "regenerator-runtime": "^0.14.0" } }, "sha512-C7fo97gUfsUP54j6GcQ+rJXyW6vgRRqF7J1ZxXesWcQtSRyzH1+eYrqFGzmU2JSUGFV0hQA2zLY/Z8AMrEx0qg=="], + "@babel/runtime-corejs2": ["@babel/runtime-corejs2@7.26.9", "", { "dependencies": { "core-js": "^2.6.12", "regenerator-runtime": "^0.14.0" } }, "sha512-DHEUkm9RD4RfIThlLTemmHNFVTj9Z/augVFMuyheFjwoFv1jFjauncHrcSW6Kv1TpzTupB01zFk2iRFAh2iE9A=="], - "@babel/runtime-corejs3": ["@babel/runtime-corejs3@7.26.7", "", { "dependencies": { "core-js-pure": "^3.30.2", "regenerator-runtime": "^0.14.0" } }, "sha512-55gRV8vGrCIYZnaQHQrD92Lo/hYE3Sj5tmbuf0hhHR7sj2CWhEhHU89hbq+UVDXvFG1zUVXJhUkEq1eAfqXtFw=="], + "@babel/runtime-corejs3": ["@babel/runtime-corejs3@7.26.9", "", { "dependencies": { "core-js-pure": "^3.30.2", "regenerator-runtime": "^0.14.0" } }, "sha512-5EVjbTegqN7RSJle6hMWYxO4voo4rI+9krITk+DWR+diJgGrjZjrIBnJhjrHYYQsFgI7j1w1QnrvV7YSKBfYGg=="], "@babel/template": ["@babel/template@7.25.9", "", { "dependencies": { "@babel/code-frame": "^7.25.9", "@babel/parser": "^7.25.9", "@babel/types": "^7.25.9" } }, "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg=="], @@ -1840,6 +1841,8 @@ "babel-plugin-polyfill-regenerator": ["babel-plugin-polyfill-regenerator@0.6.3", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.3" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q=="], + "babel-plugin-transform-import-meta": ["babel-plugin-transform-import-meta@2.3.2", "", { "dependencies": { "@babel/template": "^7.25.9", "tslib": "^2.8.1" }, "peerDependencies": { "@babel/core": "^7.10.0" } }, "sha512-902o4GiQqI1GqAXfD5rEoz0PJamUfJ3VllpdWaNsFTwdaNjFSFHawvBO+cp5K2j+g2h3bZ4lnM1Xb6yFYGihtA=="], + "babel-preset-current-node-syntax": ["babel-preset-current-node-syntax@1.1.0", "", { "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-import-attributes": "^7.24.7", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-numeric-separator": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw=="], "babel-preset-jest": ["babel-preset-jest@29.6.3", "", { "dependencies": { "babel-plugin-jest-hoist": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA=="], @@ -5448,8 +5451,12 @@ "@docsearch/react/algoliasearch": ["algoliasearch@5.20.0", "", { "dependencies": { "@algolia/client-abtesting": "5.20.0", "@algolia/client-analytics": "5.20.0", "@algolia/client-common": "5.20.0", "@algolia/client-insights": "5.20.0", "@algolia/client-personalization": "5.20.0", "@algolia/client-query-suggestions": "5.20.0", "@algolia/client-search": "5.20.0", "@algolia/ingestion": "1.20.0", "@algolia/monitoring": "1.20.0", "@algolia/recommend": "5.20.0", "@algolia/requester-browser-xhr": "5.20.0", "@algolia/requester-fetch": "5.20.0", "@algolia/requester-node-http": "5.20.0" } }, "sha512-groO71Fvi5SWpxjI9Ia+chy0QBwT61mg6yxJV27f5YFf+Mw+STT75K6SHySpP8Co5LsCrtsbCH5dJZSRtkSKaQ=="], + "@docusaurus/babel/@babel/plugin-transform-runtime": ["@babel/plugin-transform-runtime@7.25.9", "", { "dependencies": { "@babel/helper-module-imports": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9", "babel-plugin-polyfill-corejs2": "^0.4.10", "babel-plugin-polyfill-corejs3": "^0.10.6", "babel-plugin-polyfill-regenerator": "^0.6.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-nZp7GlEl+yULJrClz0SwHPqir3lc0zsPrDHQUcxGspSL7AKrexNSEfTbfqnDNJUO13bgKyfuOLMF8Xqtu8j3YQ=="], + "@docusaurus/babel/@babel/runtime": ["@babel/runtime@7.26.7", "", { "dependencies": { "regenerator-runtime": "^0.14.0" } }, "sha512-AOPI3D+a8dXnja+iwsUqGRjr1BbZIe771sXdapOtYI531gSqpi92vXivKcq2asu/DFpdl1ceFAKZyRzK2PCVcQ=="], + "@docusaurus/babel/@babel/runtime-corejs3": ["@babel/runtime-corejs3@7.26.7", "", { "dependencies": { "core-js-pure": "^3.30.2", "regenerator-runtime": "^0.14.0" } }, "sha512-55gRV8vGrCIYZnaQHQrD92Lo/hYE3Sj5tmbuf0hhHR7sj2CWhEhHU89hbq+UVDXvFG1zUVXJhUkEq1eAfqXtFw=="], + "@docusaurus/babel/fs-extra": ["fs-extra@11.3.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew=="], "@docusaurus/bundler/babel-loader": ["babel-loader@9.2.1", "", { "dependencies": { "find-cache-dir": "^4.0.0", "schema-utils": "^4.0.0" }, "peerDependencies": { "@babel/core": "^7.12.0", "webpack": ">=5" } }, "sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA=="], @@ -6020,6 +6027,8 @@ "cssstyle/cssom": ["cssom@0.3.8", "", {}, "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg=="], + "dcmjs/@babel/runtime-corejs3": ["@babel/runtime-corejs3@7.26.7", "", { "dependencies": { "core-js-pure": "^3.30.2", "regenerator-runtime": "^0.14.0" } }, "sha512-55gRV8vGrCIYZnaQHQrD92Lo/hYE3Sj5tmbuf0hhHR7sj2CWhEhHU89hbq+UVDXvFG1zUVXJhUkEq1eAfqXtFw=="], + "decamelize-keys/decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="], "decamelize-keys/map-obj": ["map-obj@1.0.1", "", {}, "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg=="], @@ -7058,8 +7067,12 @@ "@docsearch/react/algoliasearch/@algolia/requester-node-http": ["@algolia/requester-node-http@5.20.0", "", { "dependencies": { "@algolia/client-common": "5.20.0" } }, "sha512-kmtQClq/w3vtPteDSPvaW9SPZL/xrIgMrxZyAgsFwrJk0vJxqyC5/hwHmrCraDnStnGSADnLpBf4SpZnwnkwWw=="], + "@docusaurus/babel/@babel/plugin-transform-runtime/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "@docusaurus/babel/@babel/runtime/regenerator-runtime": ["regenerator-runtime@0.14.1", "", {}, "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="], + "@docusaurus/babel/@babel/runtime-corejs3/regenerator-runtime": ["regenerator-runtime@0.14.1", "", {}, "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="], + "@docusaurus/bundler/babel-loader/find-cache-dir": ["find-cache-dir@4.0.0", "", { "dependencies": { "common-path-prefix": "^3.0.0", "pkg-dir": "^7.0.0" } }, "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg=="], "@docusaurus/bundler/html-minifier-terser/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], @@ -7616,6 +7629,8 @@ "csso/css-tree/mdn-data": ["mdn-data@2.0.28", "", {}, "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="], + "dcmjs/@babel/runtime-corejs3/regenerator-runtime": ["regenerator-runtime@0.14.1", "", {}, "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="], + "decompress-tar/tar-stream/bl": ["bl@1.2.3", "", { "dependencies": { "readable-stream": "^2.3.5", "safe-buffer": "^5.1.1" } }, "sha512-pvcNpa0UU69UT341rO6AYy4FVAIkUHuZXRIWbq+zHnsVcRzDDjIAhGuuYoi0d//cwIwtt4pkpKycWEfjdV+vww=="], "decompress/make-dir/pify": ["pify@3.0.0", "", {}, "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg=="], @@ -7646,6 +7661,8 @@ "detective-typescript/@typescript-eslint/typescript-estree/globby": ["globby@11.1.0", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.2.9", "ignore": "^5.2.0", "merge2": "^1.4.1", "slash": "^3.0.0" } }, "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g=="], + "docs/dcmjs/@babel/runtime-corejs3": ["@babel/runtime-corejs3@7.26.7", "", { "dependencies": { "core-js-pure": "^3.30.2", "regenerator-runtime": "^0.14.0" } }, "sha512-55gRV8vGrCIYZnaQHQrD92Lo/hYE3Sj5tmbuf0hhHR7sj2CWhEhHU89hbq+UVDXvFG1zUVXJhUkEq1eAfqXtFw=="], + "docs/typedoc/markdown-it": ["markdown-it@14.1.0", "", { "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", "linkify-it": "^5.0.0", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" }, "bin": { "markdown-it": "bin/markdown-it.mjs" } }, "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg=="], "docs/typedoc/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], @@ -8600,6 +8617,8 @@ "detective-typescript/@typescript-eslint/typescript-estree/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + "docs/dcmjs/@babel/runtime-corejs3/regenerator-runtime": ["regenerator-runtime@0.14.1", "", {}, "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="], + "docs/typedoc/markdown-it/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], "docs/typedoc/markdown-it/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], diff --git a/common/reviews/api/tools.api.md b/common/reviews/api/tools.api.md index 5cb6177629..44e8aa903d 100644 --- a/common/reviews/api/tools.api.md +++ b/common/reviews/api/tools.api.md @@ -405,10 +405,10 @@ type AnnotationLockChangeEventType = Types_2.CustomEventType void; // (undocumented) - editData: { - annotation: Annotation; - } | null; - // (undocumented) _endCallback: (evt: EventTypes_2.InteractionEventType) => void; // (undocumented) _filterAnnotationsByUniqueViewportOrientations: (enabledElement: any, annotations: any) => any[]; @@ -3446,38 +3460,47 @@ type KeyDownEventDetail = { // @public (undocumented) type KeyDownEventType = Types_2.CustomEventType; +// @public (undocumented) +type KeyImageAnnotation = ProbeAnnotation & { + data: { + isPoint: boolean; + seriesLevel: boolean; + }; +}; + // @public (undocumented) export class KeyImageTool extends AnnotationTool { constructor(toolProps?: PublicToolProps, defaultToolProps?: ToolProps); // (undocumented) - _activateModify: (element: HTMLDivElement) => void; + _activateModify: (element: any) => void; // (undocumented) addNewAnnotation: (evt: EventTypes_2.InteractionEventType) => Annotation; // (undocumented) - cancel(): void; + cancel(element: HTMLDivElement): string; // (undocumented) - _deactivateModify: (element: HTMLDivElement) => void; + static dataPoint: { + data: { + isPoint: boolean; + }; + }; + // (undocumented) + static dataSeries: { + data: { + seriesLevel: boolean; + }; + }; + // (undocumented) + _deactivateModify: (element: any) => void; // (undocumented) _doneChangingTextCallback(element: any, annotation: any, updatedText: any): void; // (undocumented) doubleClickCallback: (evt: EventTypes_2.TouchTapEventType) => void; // (undocumented) - editData: { - annotation: Annotation; - viewportIdsToRender: string[]; - handleIndex?: number; - movingTextBox?: boolean; - newAnnotation?: boolean; - hasMoved?: boolean; - } | null; + _dragCallback: (evt: any) => void; // (undocumented) _endCallback: (evt: EventTypes_2.InteractionEventType) => void; // (undocumented) - handleSelectedCallback(evt: EventTypes_2.InteractionEventType, annotation: Annotation, handle: ToolHandle): void; - // (undocumented) - isDrawing: boolean; - // (undocumented) - isHandleOutsideImage: boolean; + handleSelectedCallback(evt: EventTypes_2.InteractionEventType, annotation: KeyImageAnnotation): void; // (undocumented) _isInsideVolume(index1: any, index2: any, dimensions: any): boolean; // (undocumented) @@ -3485,6 +3508,8 @@ export class KeyImageTool extends AnnotationTool { // (undocumented) renderAnnotation: (enabledElement: Types_2.IEnabledElement, svgDrawingHelper: SVGDrawingHelper) => boolean; // (undocumented) + static setPoint(annotation: any, isPoint?: boolean, element?: any): void; + // (undocumented) _throttledCalculateCachedStats: Function; // (undocumented) static toolName: string; @@ -4506,19 +4531,8 @@ export class ProbeTool extends AnnotationTool { // (undocumented) _dragCallback: (evt: any) => void; // (undocumented) - editData: { - annotation: Annotation; - viewportIdsToRender: string[]; - newAnnotation?: boolean; - } | null; - // (undocumented) _endCallback: (evt: EventTypes_2.InteractionEventType) => void; // (undocumented) - eventDispatchDetail: { - viewportId: string; - renderingEngineId: string; - }; - // (undocumented) getHandleNearImagePoint(element: HTMLDivElement, annotation: ProbeAnnotation, canvasCoords: Types_2.Point2, proximity: number): ToolHandle | undefined; // (undocumented) handleSelectedCallback(evt: EventTypes_2.InteractionEventType, annotation: ProbeAnnotation): void; @@ -4527,10 +4541,6 @@ export class ProbeTool extends AnnotationTool { annotationUID?: string; }) => ProbeAnnotation; // (undocumented) - isDrawing: boolean; - // (undocumented) - isHandleOutsideImage: boolean; - // (undocumented) isPointNearTool(element: HTMLDivElement, annotation: ProbeAnnotation, canvasCoords: Types_2.Point2, proximity: number): boolean; // (undocumented) static probeDefaults: { @@ -6388,6 +6398,7 @@ declare namespace ToolSpecificAnnotationTypes { ROICachedStats, RectangleROIAnnotation, ProbeAnnotation, + KeyImageAnnotation, LengthAnnotation, AdvancedMagnifyAnnotation, CircleROIAnnotation, diff --git a/jest.config.base.js b/jest.config.base.js index 944a17e4b7..cac2667b25 100644 --- a/jest.config.base.js +++ b/jest.config.base.js @@ -18,6 +18,14 @@ module.exports = { // Setup // setupFiles: ["jest-canvas-mock/lib/index.js"], // Coverage + transform: { + '^.+\\.(js|jsx|ts|tsx)$': [ + 'babel-jest', + { + plugins: ['babel-plugin-transform-import-meta'], + }, + ], + }, collectCoverageFrom: [ '/src/**/*.{js,jsx}', // Not diff --git a/package.json b/package.json index 85796aed7f..a1a1575346 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ }, "scripts": { "api-check": "npx lerna run api-check", - "format-check": "npx lerna run format-check", + "format-check": "cross-env NODE_OPTIONS=--max_old_space_size=32896 npx lerna run format-check", "build": "npx lerna run build --stream && npx lerna run build:loader", "build:esm": "npx lerna run build:esm --stream", "watch": "npx lerna watch -- lerna run build --scope=$LERNA_PACKAGE_NAME --include-dependents", @@ -26,7 +26,7 @@ "build:update-api:tools": "cd packages/tools && npm run build:update-api", "build:update-api:nifti": "cd packages/nifti-volume-loader && npm run build:update-api", "build:update-api:dicomImageLoader": "cd packages/dicomImageLoader && npm run build:update-api", - "build:update-api": "npm run build && npm run build:update-api:ai && npm run build:update-api:core && npm run build:update-api:tools && npm run build:update-api:nifti && npm run build:update-api:dicomImageLoader && npm run build:update-api:labelmap-interpolation && npm run build:update-api:polymorphic-segmentation", + "build:update-api": "npm run build && npm run build:update-api:ai && npm run build:update-api:core && npm run build:update-api:tools && npm run build:update-api:nifti && npm run build:update-api:dicomImageLoader", "clean": "npx lerna run clean --stream", "clean:deep": "npx lerna run clean:deep --stream", "example": "node ./utils/ExampleRunner/example-runner-cli.js", @@ -97,6 +97,7 @@ "autoprefixer": "^10.4.14", "babel-loader": "9.1.2", "babel-plugin-istanbul": "^6.1.1", + "babel-plugin-transform-import-meta": "2.3.2", "chai": "^5.1.2", "clean-webpack-plugin": "^4.0.0", "commander": "^10.0.1", diff --git a/packages/adapters/.babelrc b/packages/adapters/.babelrc deleted file mode 100644 index d15bf49ffb..0000000000 --- a/packages/adapters/.babelrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "presets": [ - ["@babel/preset-env", { - "targets": { - "browsers": ["ie 11"] - } - }] - ] -} \ No newline at end of file diff --git a/packages/adapters/CHANGELOG.md b/packages/adapters/CHANGELOG.md index 878dcbd635..2aa7f69472 100644 --- a/packages/adapters/CHANGELOG.md +++ b/packages/adapters/CHANGELOG.md @@ -3,136 +3,6 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. -# [3.0.0-beta.5](https://github.com/cornerstonejs/cornerstone3D/compare/v3.0.0-beta.4...v3.0.0-beta.5) (2025-02-25) - -**Note:** Version bump only for package @cornerstonejs/adapters - -# [3.0.0-beta.4](https://github.com/cornerstonejs/cornerstone3D/compare/v3.0.0-beta.3...v3.0.0-beta.4) (2025-02-25) - -**Note:** Version bump only for package @cornerstonejs/adapters - -# [3.0.0-beta.3](https://github.com/cornerstonejs/cornerstone3D/compare/v3.0.0-beta.2...v3.0.0-beta.3) (2025-02-25) - -**Note:** Version bump only for package @cornerstonejs/adapters - -# [3.0.0-beta.2](https://github.com/dcmjs-org/dcmjs/compare/v2.19.14...v3.0.0-beta.2) (2025-02-24) - -### Bug Fixes - -- publish beta for 3.0 ([8bf65df](https://github.com/dcmjs-org/dcmjs/commit/8bf65df9bec5f52459de1c49c4834b316f680f1b)) - -## [2.19.14](https://github.com/dcmjs-org/dcmjs/compare/v2.19.13...v2.19.14) (2025-02-24) - -**Note:** Version bump only for package @cornerstonejs/adapters - -## [2.19.13](https://github.com/dcmjs-org/dcmjs/compare/v2.19.12...v2.19.13) (2025-02-21) - -**Note:** Version bump only for package @cornerstonejs/adapters - -## [2.19.12](https://github.com/dcmjs-org/dcmjs/compare/v2.19.11...v2.19.12) (2025-02-20) - -**Note:** Version bump only for package @cornerstonejs/adapters - -## [2.19.11](https://github.com/dcmjs-org/dcmjs/compare/v2.19.10...v2.19.11) (2025-02-18) - -**Note:** Version bump only for package @cornerstonejs/adapters - -## [2.19.10](https://github.com/dcmjs-org/dcmjs/compare/v2.19.9...v2.19.10) (2025-02-18) - -**Note:** Version bump only for package @cornerstonejs/adapters - -## [2.19.9](https://github.com/dcmjs-org/dcmjs/compare/v2.19.8...v2.19.9) (2025-02-18) - -**Note:** Version bump only for package @cornerstonejs/adapters - -## [2.19.8](https://github.com/dcmjs-org/dcmjs/compare/v2.19.7...v2.19.8) (2025-02-18) - -**Note:** Version bump only for package @cornerstonejs/adapters - -## [2.19.7](https://github.com/dcmjs-org/dcmjs/compare/v2.19.6...v2.19.7) (2025-02-06) - -**Note:** Version bump only for package @cornerstonejs/adapters - -## [2.19.6](https://github.com/dcmjs-org/dcmjs/compare/v2.19.5...v2.19.6) (2025-02-05) - -**Note:** Version bump only for package @cornerstonejs/adapters - -## [2.19.5](https://github.com/dcmjs-org/dcmjs/compare/v2.19.4...v2.19.5) (2025-02-04) - -**Note:** Version bump only for package @cornerstonejs/adapters - -## [2.19.4](https://github.com/dcmjs-org/dcmjs/compare/v2.19.3...v2.19.4) (2025-02-04) - -**Note:** Version bump only for package @cornerstonejs/adapters - -## [2.19.3](https://github.com/dcmjs-org/dcmjs/compare/v2.19.2...v2.19.3) (2025-02-04) - -**Note:** Version bump only for package @cornerstonejs/adapters - -## [2.19.2](https://github.com/dcmjs-org/dcmjs/compare/v2.19.1...v2.19.2) (2025-02-03) - -**Note:** Version bump only for package @cornerstonejs/adapters - -## [2.19.1](https://github.com/dcmjs-org/dcmjs/compare/v2.19.0...v2.19.1) (2025-02-03) - -**Note:** Version bump only for package @cornerstonejs/adapters - -# [2.19.0](https://github.com/dcmjs-org/dcmjs/compare/v2.18.9...v2.19.0) (2025-02-03) - -**Note:** Version bump only for package @cornerstonejs/adapters - -## [2.18.9](https://github.com/dcmjs-org/dcmjs/compare/v2.18.8...v2.18.9) (2025-02-03) - -**Note:** Version bump only for package @cornerstonejs/adapters - -## [2.18.8](https://github.com/dcmjs-org/dcmjs/compare/v2.18.7...v2.18.8) (2025-02-03) - -**Note:** Version bump only for package @cornerstonejs/adapters - -## [2.18.7](https://github.com/dcmjs-org/dcmjs/compare/v2.18.6...v2.18.7) (2025-02-03) - -**Note:** Version bump only for package @cornerstonejs/adapters - -## [2.18.6](https://github.com/dcmjs-org/dcmjs/compare/v2.18.5...v2.18.6) (2025-02-03) - -**Note:** Version bump only for package @cornerstonejs/adapters - -## [2.18.5](https://github.com/dcmjs-org/dcmjs/compare/v2.18.4...v2.18.5) (2025-02-03) - -**Note:** Version bump only for package @cornerstonejs/adapters - -## [2.18.4](https://github.com/dcmjs-org/dcmjs/compare/v2.18.3...v2.18.4) (2025-02-03) - -**Note:** Version bump only for package @cornerstonejs/adapters - -## [2.18.3](https://github.com/dcmjs-org/dcmjs/compare/v2.18.2...v2.18.3) (2025-01-31) - -**Note:** Version bump only for package @cornerstonejs/adapters - -## [2.18.2](https://github.com/dcmjs-org/dcmjs/compare/v2.18.1...v2.18.2) (2025-01-31) - -**Note:** Version bump only for package @cornerstonejs/adapters - -## [2.18.1](https://github.com/dcmjs-org/dcmjs/compare/v2.18.0...v2.18.1) (2025-01-31) - -**Note:** Version bump only for package @cornerstonejs/adapters - -# [2.18.0](https://github.com/dcmjs-org/dcmjs/compare/v2.17.6...v2.18.0) (2025-01-30) - -**Note:** Version bump only for package @cornerstonejs/adapters - -## [2.17.6](https://github.com/dcmjs-org/dcmjs/compare/v2.17.5...v2.17.6) (2025-01-29) - -**Note:** Version bump only for package @cornerstonejs/adapters - -## [2.17.5](https://github.com/dcmjs-org/dcmjs/compare/v2.17.4...v2.17.5) (2025-01-27) - -**Note:** Version bump only for package @cornerstonejs/adapters - -## [2.17.4](https://github.com/dcmjs-org/dcmjs/compare/v2.17.3...v2.17.4) (2025-01-24) - -**Note:** Version bump only for package @cornerstonejs/adapters - ## [2.17.3](https://github.com/dcmjs-org/dcmjs/compare/v2.17.2...v2.17.3) (2025-01-22) **Note:** Version bump only for package @cornerstonejs/adapters diff --git a/packages/adapters/babel.config.js b/packages/adapters/babel.config.js new file mode 100644 index 0000000000..fed6f05fec --- /dev/null +++ b/packages/adapters/babel.config.js @@ -0,0 +1 @@ +module.exports = require("../../babel.config.js"); diff --git a/packages/adapters/jest.config.js b/packages/adapters/jest.config.js new file mode 100644 index 0000000000..bd1f3dad96 --- /dev/null +++ b/packages/adapters/jest.config.js @@ -0,0 +1,11 @@ +/* eslint-disable */ +const base = require("../../jest.config.base.js"); +const path = require("path"); + +module.exports = { + ...base, + displayName: "adapters", + moduleNameMapper: { + "^@cornerstonejs/(.*)$": path.resolve(__dirname, "../$1/src") + } +}; diff --git a/packages/adapters/rollup.config.mjs b/packages/adapters/rollup.config.mjs index 30bac3ad56..ac7da0b622 100644 --- a/packages/adapters/rollup.config.mjs +++ b/packages/adapters/rollup.config.mjs @@ -49,7 +49,7 @@ export default [ }), babel({ exclude: "node_modules/**", - babelHelpers: "bundled", + babelHelpers: "runtime", extensions: [".js", ".ts"] }), json() diff --git a/packages/adapters/src/adapters/Cornerstone/ArrowAnnotate.js b/packages/adapters/src/adapters/Cornerstone/ArrowAnnotate.js index bf37e4edd3..46d7a026bc 100644 --- a/packages/adapters/src/adapters/Cornerstone/ArrowAnnotate.js +++ b/packages/adapters/src/adapters/Cornerstone/ArrowAnnotate.js @@ -61,7 +61,8 @@ class ArrowAnnotate { static getTID300RepresentationArguments(tool) { const points = [tool.handles.start, tool.handles.end]; - let { finding, findingSites } = tool; + const { findingSites } = tool; + let { finding } = tool; const TID300RepresentationArguments = { points, diff --git a/packages/adapters/src/adapters/Cornerstone/MeasurementReport.js b/packages/adapters/src/adapters/Cornerstone/MeasurementReport.js index 929980af68..6c8a9aea6a 100644 --- a/packages/adapters/src/adapters/Cornerstone/MeasurementReport.js +++ b/packages/adapters/src/adapters/Cornerstone/MeasurementReport.js @@ -31,16 +31,11 @@ const codeValueMatch = (group, code, oldCode) => { ); }; -function getTID300ContentItem( - tool, - toolType, - ReferencedSOPSequence, - toolClass -) { - const args = toolClass.getTID300RepresentationArguments(tool); +function getTID300ContentItem(tool, ReferencedSOPSequence, adapterClass) { + const args = adapterClass.getTID300RepresentationArguments(tool); args.ReferencedSOPSequence = ReferencedSOPSequence; - const TID300Measurement = new toolClass.TID300Representation(args); + const TID300Measurement = new adapterClass.TID300Representation(args); return TID300Measurement; } @@ -61,12 +56,7 @@ function getMeasurementGroup(toolType, toolData, ReferencedSOPSequence) { // Loop through the array of tool instances // for this tool const Measurements = toolTypeData.data.map(tool => { - return getTID300ContentItem( - tool, - toolType, - ReferencedSOPSequence, - toolClass - ); + return getTID300ContentItem(tool, ReferencedSOPSequence, toolClass); }); return new TID1501MeasurementGroup(Measurements); diff --git a/packages/adapters/src/adapters/Cornerstone3D/Angle.ts b/packages/adapters/src/adapters/Cornerstone3D/Angle.ts index 4464d80a13..327e09599a 100644 --- a/packages/adapters/src/adapters/Cornerstone3D/Angle.ts +++ b/packages/adapters/src/adapters/Cornerstone3D/Angle.ts @@ -1,29 +1,14 @@ import { utilities } from "dcmjs"; -import CORNERSTONE_3D_TAG from "./cornerstone3DTag"; import MeasurementReport from "./MeasurementReport"; +import BaseAdapter3D from "./BaseAdapter3D"; const { CobbAngle: TID300CobbAngle } = utilities.TID300; -const MEASUREMENT_TYPE = "Angle"; -const trackingIdentifierTextValue = `${CORNERSTONE_3D_TAG}:${MEASUREMENT_TYPE}`; - -class Angle { - public static toolType = MEASUREMENT_TYPE; - public static utilityToolType = MEASUREMENT_TYPE; - public static TID300Representation = TID300CobbAngle; - public static isValidCornerstoneTrackingIdentifier = TrackingIdentifier => { - if (!TrackingIdentifier.includes(":")) { - return false; - } - - const [cornerstone3DTag, toolType] = TrackingIdentifier.split(":"); - - if (cornerstone3DTag !== CORNERSTONE_3D_TAG) { - return false; - } - - return toolType === MEASUREMENT_TYPE; - }; +class Angle extends BaseAdapter3D { + static { + this.init("Angle", TID300CobbAngle); + this.registerLegacy(); + } // TODO: this function is required for all Cornerstone Tool Adapters, since it is called by MeasurementReport. public static getMeasurementData( @@ -107,13 +92,11 @@ class Angle { point3, point4, rAngle: angle, - trackingIdentifierTextValue, + trackingIdentifierTextValue: this.trackingIdentifierTextValue, finding, findingSites: findingSites || [] }; } } -MeasurementReport.registerTool(Angle); - export default Angle; diff --git a/packages/adapters/src/adapters/Cornerstone3D/ArrowAnnotate.js b/packages/adapters/src/adapters/Cornerstone3D/ArrowAnnotate.ts similarity index 77% rename from packages/adapters/src/adapters/Cornerstone3D/ArrowAnnotate.js rename to packages/adapters/src/adapters/Cornerstone3D/ArrowAnnotate.ts index 7514a181d0..736af13519 100644 --- a/packages/adapters/src/adapters/Cornerstone3D/ArrowAnnotate.js +++ b/packages/adapters/src/adapters/Cornerstone3D/ArrowAnnotate.ts @@ -1,21 +1,23 @@ import MeasurementReport from "./MeasurementReport"; import { utilities } from "dcmjs"; -import CORNERSTONE_3D_TAG from "./cornerstone3DTag"; +import BaseAdapter3D from "./BaseAdapter3D"; import CodingScheme from "./CodingScheme"; const { Point: TID300Point } = utilities.TID300; +const { codeValues } = CodingScheme; -const ARROW_ANNOTATE = "ArrowAnnotate"; -const trackingIdentifierTextValue = `${CORNERSTONE_3D_TAG}:${ARROW_ANNOTATE}`; - -const { codeValues, CodingSchemeDesignator } = CodingScheme; +class ArrowAnnotate extends BaseAdapter3D { + static { + this.init("ArrowAnnotate", TID300Point); + this.registerLegacy(); + } -class ArrowAnnotate { static getMeasurementData( MeasurementGroup, sopInstanceUIDToImageIdMap, imageToWorldCoords, - metadata + metadata, + _trackingIdentifier ) { const { defaultState, SCOORDGroup, ReferencedFrameNumber } = MeasurementReport.getSetupMeasurementData( @@ -85,8 +87,8 @@ class ArrowAnnotate { } static getTID300RepresentationArguments(tool, worldToImageCoords) { - const { data, metadata } = tool; - let { finding, findingSites } = tool; + const { data, metadata, findingSites } = tool; + let { finding } = tool; const { referencedImageId } = metadata; if (!referencedImageId) { @@ -122,42 +124,22 @@ class ArrowAnnotate { y: pointImage2[1] } ], - trackingIdentifierTextValue, - findingSites: findingSites || [] + trackingIdentifierTextValue: this.trackingIdentifierTextValue, + findingSites: findingSites || [], + finding }; // If freetext finding isn't present, add it from the tool text. if (!finding || finding.CodeValue !== codeValues.CORNERSTONEFREETEXT) { finding = { CodeValue: codeValues.CORNERSTONEFREETEXT, - CodingSchemeDesignator, + CodingSchemeDesignator: CodingScheme.CodingSchemeDesignator, CodeMeaning: data.text }; } - TID300RepresentationArguments.finding = finding; - return TID300RepresentationArguments; } } -ArrowAnnotate.toolType = ARROW_ANNOTATE; -ArrowAnnotate.utilityToolType = ARROW_ANNOTATE; -ArrowAnnotate.TID300Representation = TID300Point; -ArrowAnnotate.isValidCornerstoneTrackingIdentifier = TrackingIdentifier => { - if (!TrackingIdentifier.includes(":")) { - return false; - } - - const [cornerstone3DTag, toolType] = TrackingIdentifier.split(":"); - - if (cornerstone3DTag !== CORNERSTONE_3D_TAG) { - return false; - } - - return toolType === ARROW_ANNOTATE; -}; - -MeasurementReport.registerTool(ArrowAnnotate); - export default ArrowAnnotate; diff --git a/packages/adapters/src/adapters/Cornerstone3D/BaseAdapter3D.ts b/packages/adapters/src/adapters/Cornerstone3D/BaseAdapter3D.ts new file mode 100644 index 0000000000..40c33c28d0 --- /dev/null +++ b/packages/adapters/src/adapters/Cornerstone3D/BaseAdapter3D.ts @@ -0,0 +1,178 @@ +import CORNERSTONE_3D_TAG from "./cornerstone3DTag"; +import MeasurementReport, { + type AdapterOptions, + type MeasurementAdapter +} from "./MeasurementReport"; + +export type Point = { + x: number; + y: number; + z?: number; +}; + +export type TID300Arguments = { + points?: Point[]; + point1?: Point; + point2?: Point; + trackingIdentifierTextValue: string; + findingSites: []; + finding; + + [key: string]: unknown; +}; + +/** + * This is a basic definition of adapters to be inherited for other adapters. + */ +export default class BaseAdapter3D { + public static toolType: string; + public static TID300Representation; + public static trackingIdentifierTextValue: string; + public static trackingIdentifiers: Set; + + /** + * The parent type is another type which could be used to parse this instance, + * but for which this sub-class has a better representation. For example, + * key images are parseable as Probe instances, but are represented as a different tool + * Thus, the name for the key image is `Cornerstone3DTag:Probe:KeyImage` so that + * a prefix query testing just the Probe could parse this object and display it, + * but a better/full path key could also be done. + */ + public static parentType: string; + + public static init( + toolType: string, + representation, + options?: AdapterOptions + ) { + this.toolType = toolType; + if (BaseAdapter3D.toolType) { + throw new Error( + `Base adapter tool type set to ${this.toolType} while setting ${toolType}` + ); + } + this.parentType = options?.parentType; + this.trackingIdentifiers = new Set(); + + this.TID300Representation = representation; + if (this.parentType) { + this.trackingIdentifierTextValue = `${CORNERSTONE_3D_TAG}:${this.parentType}:${this.toolType}`; + const alternateTrackingIdentifier = `${CORNERSTONE_3D_TAG}:${this.toolType}`; + this.trackingIdentifiers.add(alternateTrackingIdentifier); + } else { + this.trackingIdentifierTextValue = `${CORNERSTONE_3D_TAG}:${toolType}`; + } + this.trackingIdentifiers.add(this.trackingIdentifierTextValue); + MeasurementReport.registerTool(this); + } + + /** + * registerLegacy registers the given tool adapter using the legacy/old + * cornerstone 1.x adapter names so that the adapter class will be used to load + * those older adapters. + */ + public static registerLegacy() { + this.trackingIdentifiers.add( + `cornerstoneTools@^4.0.0:${this.toolType}` + ); + } + + /** Registers a new copy of the given type that has the prefix path the + * same as that of adapter, but adds the toolType to this path to create + * a new tool instance of the given type. This preserves compatibility + * with parsing in other versions, without need to replace the original parent + * type. + */ + public static registerSubType( + adapter: MeasurementAdapter, + toolType: string, + replace? + ) { + const subAdapter = Object.create(adapter); + subAdapter.init(toolType, adapter.TID300Representation, { + parentType: adapter.parentType || adapter.toolType, + replace + }); + return subAdapter; + } + + /** + * @returns true if the tool is of the given tool type based on the tracking identifier + */ + public static isValidCornerstoneTrackingIdentifier( + trackingIdentifier: string + ) { + if (this.trackingIdentifiers.has(trackingIdentifier)) { + return true; + } + if (!trackingIdentifier.includes(":")) { + return false; + } + + return trackingIdentifier.startsWith(this.trackingIdentifierTextValue); + } + + /** + * Returns annotation data for CS3D to use based on the underlying + * DICOM SR annotation data. + */ + public static getMeasurementData( + MeasurementGroup, + sopInstanceUIDToImageIdMap, + _imageToWorldCoords, + metadata, + trackingIdentifier?: string + ) { + const { defaultState: state, ReferencedFrameNumber } = + MeasurementReport.getSetupMeasurementData( + MeasurementGroup, + sopInstanceUIDToImageIdMap, + metadata, + this.toolType + ); + + state.annotation.data = { + cachedStats: {}, + frameNumber: ReferencedFrameNumber, + seriesLevel: trackingIdentifier?.indexOf(":Series") > 0 + }; + + return state; + } + + public static getTID300RepresentationArguments( + tool, + worldToImageCoords + ): TID300Arguments { + const { data, metadata } = tool; + const { finding, findingSites } = tool; + const { referencedImageId } = metadata; + + if (!referencedImageId) { + throw new Error( + "Probe.getTID300RepresentationArguments: referencedImageId is not defined" + ); + } + + const { + handles: { points = [] } + } = data; + + const pointsImage = points.map(point => { + const pointImage = worldToImageCoords(referencedImageId, point); + return { + x: pointImage[0], + y: pointImage[1] + }; + }); + + const tidArguments = { + points: pointsImage, + trackingIdentifierTextValue: this.trackingIdentifierTextValue, + findingSites: findingSites || [], + finding + }; + + return tidArguments; + } +} diff --git a/packages/adapters/src/adapters/Cornerstone3D/Bidirectional.ts b/packages/adapters/src/adapters/Cornerstone3D/Bidirectional.ts index 5253bfeffa..a19bfe9f57 100644 --- a/packages/adapters/src/adapters/Cornerstone3D/Bidirectional.ts +++ b/packages/adapters/src/adapters/Cornerstone3D/Bidirectional.ts @@ -2,31 +2,18 @@ import { utilities } from "dcmjs"; import CORNERSTONE_3D_TAG from "./cornerstone3DTag"; import MeasurementReport from "./MeasurementReport"; import { toArray } from "../helpers"; +import BaseAdapter3D from "./BaseAdapter3D"; const { Bidirectional: TID300Bidirectional } = utilities.TID300; -const BIDIRECTIONAL = "Bidirectional"; const LONG_AXIS = "Long Axis"; const SHORT_AXIS = "Short Axis"; -const trackingIdentifierTextValue = `${CORNERSTONE_3D_TAG}:${BIDIRECTIONAL}`; - -class Bidirectional { - public static toolType = BIDIRECTIONAL; - public static utilityToolType = BIDIRECTIONAL; - public static TID300Representation = TID300Bidirectional; - public static isValidCornerstoneTrackingIdentifier = TrackingIdentifier => { - if (!TrackingIdentifier.includes(":")) { - return false; - } - - const [cornerstone3DTag, toolType] = TrackingIdentifier.split(":"); - - if (cornerstone3DTag !== CORNERSTONE_3D_TAG) { - return false; - } - return toolType === BIDIRECTIONAL; - }; +class Bidirectional extends BaseAdapter3D { + static { + this.init("Bidirectional", TID300Bidirectional); + this.registerLegacy(); + } public static getMeasurementData( MeasurementGroup, @@ -184,13 +171,11 @@ class Bidirectional { }, longAxisLength: length, shortAxisLength: width, - trackingIdentifierTextValue, + trackingIdentifierTextValue: this.trackingIdentifierTextValue, finding: finding, findingSites: findingSites || [] }; } } -MeasurementReport.registerTool(Bidirectional); - export default Bidirectional; diff --git a/packages/adapters/src/adapters/Cornerstone3D/CircleROI.ts b/packages/adapters/src/adapters/Cornerstone3D/CircleROI.ts index a48d388481..29ecf85fa9 100644 --- a/packages/adapters/src/adapters/Cornerstone3D/CircleROI.ts +++ b/packages/adapters/src/adapters/Cornerstone3D/CircleROI.ts @@ -1,19 +1,14 @@ import { utilities } from "dcmjs"; import MeasurementReport from "./MeasurementReport"; -import CORNERSTONE_3D_TAG from "./cornerstone3DTag"; -import isValidCornerstoneTrackingIdentifier from "./isValidCornerstoneTrackingIdentifier"; +import BaseAdapter3D from "./BaseAdapter3D"; const { Circle: TID300Circle } = utilities.TID300; -const CIRCLEROI = "CircleROI"; - -class CircleROI { - static trackingIdentifierTextValue = `${CORNERSTONE_3D_TAG}:${CIRCLEROI}`; - static toolType = CIRCLEROI; - static utilityToolType = CIRCLEROI; - static TID300Representation = TID300Circle; - static isValidCornerstoneTrackingIdentifier = - isValidCornerstoneTrackingIdentifier; +class CircleROI extends BaseAdapter3D { + static { + this.init("CircleROI", TID300Circle); + this.registerLegacy(); + } /** Gets the measurement data for cornerstone, given DICOM SR measurement data. */ static getMeasurementData( @@ -113,6 +108,4 @@ class CircleROI { } } -MeasurementReport.registerTool(CircleROI); - export default CircleROI; diff --git a/packages/adapters/src/adapters/Cornerstone3D/CobbAngle.ts b/packages/adapters/src/adapters/Cornerstone3D/CobbAngle.ts index 2e13b64978..6f40cdbf22 100644 --- a/packages/adapters/src/adapters/Cornerstone3D/CobbAngle.ts +++ b/packages/adapters/src/adapters/Cornerstone3D/CobbAngle.ts @@ -1,29 +1,15 @@ import { utilities } from "dcmjs"; -import CORNERSTONE_3D_TAG from "./cornerstone3DTag"; import MeasurementReport from "./MeasurementReport"; +import BaseAdapter3D from "./BaseAdapter3D"; const { CobbAngle: TID300CobbAngle } = utilities.TID300; -const MEASUREMENT_TYPE = "CobbAngle"; -const trackingIdentifierTextValue = `${CORNERSTONE_3D_TAG}:${MEASUREMENT_TYPE}`; - -class CobbAngle { - public static toolType = MEASUREMENT_TYPE; - public static utilityToolType = MEASUREMENT_TYPE; - public static TID300Representation = TID300CobbAngle; - public static isValidCornerstoneTrackingIdentifier = TrackingIdentifier => { - if (!TrackingIdentifier.includes(":")) { - return false; - } - - const [cornerstone3DTag, toolType] = TrackingIdentifier.split(":"); - - if (cornerstone3DTag !== CORNERSTONE_3D_TAG) { - return false; - } - - return toolType === MEASUREMENT_TYPE; - }; +class CobbAngle extends BaseAdapter3D { + static { + this.init("CobbAngle", TID300CobbAngle); + // Register using the Cornerstone 1.x name so this tool is used to load it + this.registerLegacy(); + } // TODO: this function is required for all Cornerstone Tool Adapters, since it is called by MeasurementReport. public static getMeasurementData( @@ -112,13 +98,11 @@ class CobbAngle { point3, point4, rAngle: angle, - trackingIdentifierTextValue, + trackingIdentifierTextValue: this.trackingIdentifierTextValue, finding, findingSites: findingSites || [] }; } } -MeasurementReport.registerTool(CobbAngle); - export default CobbAngle; diff --git a/packages/adapters/src/adapters/Cornerstone3D/EllipticalROI.ts b/packages/adapters/src/adapters/Cornerstone3D/EllipticalROI.ts index 1a910201cf..7628023ce8 100644 --- a/packages/adapters/src/adapters/Cornerstone3D/EllipticalROI.ts +++ b/packages/adapters/src/adapters/Cornerstone3D/EllipticalROI.ts @@ -1,23 +1,18 @@ import { vec3 } from "gl-matrix"; import { utilities } from "dcmjs"; -import CORNERSTONE_3D_TAG from "./cornerstone3DTag"; import MeasurementReport from "./MeasurementReport"; -import isValidCornerstoneTrackingIdentifier from "./isValidCornerstoneTrackingIdentifier"; +import BaseAdapter3D from "./BaseAdapter3D"; type Point3 = [number, number, number]; const { Ellipse: TID300Ellipse } = utilities.TID300; -const ELLIPTICALROI = "EllipticalROI"; const EPSILON = 1e-4; -class EllipticalROI { - static trackingIdentifierTextValue = `${CORNERSTONE_3D_TAG}:${ELLIPTICALROI}`; - static toolType = ELLIPTICALROI; - static utilityToolType = ELLIPTICALROI; - static TID300Representation = TID300Ellipse; - static isValidCornerstoneTrackingIdentifier = - isValidCornerstoneTrackingIdentifier; +class EllipticalROI extends BaseAdapter3D { + static { + this.init("EllipticalROI", TID300Ellipse); + } static getMeasurementData( MeasurementGroup, @@ -199,6 +194,4 @@ class EllipticalROI { } } -MeasurementReport.registerTool(EllipticalROI); - export default EllipticalROI; diff --git a/packages/adapters/src/adapters/Cornerstone3D/KeyImage.ts b/packages/adapters/src/adapters/Cornerstone3D/KeyImage.ts new file mode 100644 index 0000000000..57e59b20d4 --- /dev/null +++ b/packages/adapters/src/adapters/Cornerstone3D/KeyImage.ts @@ -0,0 +1,59 @@ +import { utilities } from "dcmjs"; +import Probe from "./Probe"; +const { Point: TID300Point } = utilities.TID300; + +export default class KeyImage extends Probe { + static { + this.init("KeyImage", TID300Point, { parentType: Probe.toolType }); + } + static trackingSeriesIdentifier = `${this.trackingIdentifierTextValue}:Series`; + static trackingPointIdentifier = `${this.trackingIdentifierTextValue}:Point`; + static trackingSeriesPointIdentifier = `${this.trackingIdentifierTextValue}:SeriesPoint`; + + static getMeasurementData( + measurementGroup, + sopInstanceUIDToImageIdMap, + imageToWorldCoords, + metadata, + trackingIdentifier + ) { + const baseData = super.getMeasurementData( + measurementGroup, + sopInstanceUIDToImageIdMap, + imageToWorldCoords, + metadata, + trackingIdentifier + ); + const { data } = baseData.annotation; + data.isPoint = trackingIdentifier.indexOf("Point") !== -1; + + return baseData; + } + + public static getTID300RepresentationArguments(tool, worldToImageCoords) { + const tid300Arguments = super.getTID300RepresentationArguments( + tool, + worldToImageCoords + ); + const { data } = tool; + if (data.isPoint) { + if (data.seriesLevel) { + tid300Arguments.trackingIdentifierTextValue = + this.trackingSeriesPointIdentifier; + } else { + tid300Arguments.trackingIdentifierTextValue = + this.trackingPointIdentifier; + } + } + if (data.seriesLevel) { + tid300Arguments.trackingIdentifierTextValue = + this.trackingSeriesIdentifier; + } + if (!tid300Arguments.points.length) { + tid300Arguments.points.push({ x: 0, y: 0 }); + } + return tid300Arguments; + } +} + +export { KeyImage }; diff --git a/packages/adapters/src/adapters/Cornerstone3D/Length.js b/packages/adapters/src/adapters/Cornerstone3D/Length.ts similarity index 79% rename from packages/adapters/src/adapters/Cornerstone3D/Length.js rename to packages/adapters/src/adapters/Cornerstone3D/Length.ts index 4d409ed264..733265409d 100644 --- a/packages/adapters/src/adapters/Cornerstone3D/Length.js +++ b/packages/adapters/src/adapters/Cornerstone3D/Length.ts @@ -1,13 +1,18 @@ import { utilities } from "dcmjs"; -import CORNERSTONE_3D_TAG from "./cornerstone3DTag"; import MeasurementReport from "./MeasurementReport"; +import BaseAdapter3D from "./BaseAdapter3D"; const { Length: TID300Length } = utilities.TID300; const LENGTH = "Length"; -const trackingIdentifierTextValue = `${CORNERSTONE_3D_TAG}:${LENGTH}`; -class Length { +export default class Length extends BaseAdapter3D { + static { + this.init(LENGTH, TID300Length); + // Register using the Cornerstone 1.x name so this tool is used to load it + this.registerLegacy(); + } + // TODO: this function is required for all Cornerstone Tool Adapters, since it is called by MeasurementReport. static getMeasurementData( MeasurementGroup, @@ -20,7 +25,7 @@ class Length { MeasurementGroup, sopInstanceUIDToImageIdMap, metadata, - Length.toolType + this.toolType ); const referencedImageId = @@ -84,30 +89,9 @@ class Length { point1, point2, distance, - trackingIdentifierTextValue, + trackingIdentifierTextValue: this.trackingIdentifierTextValue, finding, findingSites: findingSites || [] }; } } - -Length.toolType = LENGTH; -Length.utilityToolType = LENGTH; -Length.TID300Representation = TID300Length; -Length.isValidCornerstoneTrackingIdentifier = TrackingIdentifier => { - if (!TrackingIdentifier.includes(":")) { - return false; - } - - const [cornerstone3DTag, toolType] = TrackingIdentifier.split(":"); - - if (cornerstone3DTag !== CORNERSTONE_3D_TAG) { - return false; - } - - return toolType === LENGTH; -}; - -MeasurementReport.registerTool(Length); - -export default Length; diff --git a/packages/adapters/src/adapters/Cornerstone3D/MeasurementReport.ts b/packages/adapters/src/adapters/Cornerstone3D/MeasurementReport.ts index d7e5fd28c4..2067df144f 100644 --- a/packages/adapters/src/adapters/Cornerstone3D/MeasurementReport.ts +++ b/packages/adapters/src/adapters/Cornerstone3D/MeasurementReport.ts @@ -18,77 +18,136 @@ const FINDING = { CodingSchemeDesignator: "DCM", CodeValue: "121071" }; const FINDING_SITE = { CodingSchemeDesignator: "SCT", CodeValue: "363698007" }; const FINDING_SITE_OLD = { CodingSchemeDesignator: "SRT", CodeValue: "G-C0E3" }; -const codeValueMatch = (group, code, oldCode?) => { - const { ConceptNameCodeSequence } = group; - if (!ConceptNameCodeSequence) { - return; - } - const { CodingSchemeDesignator, CodeValue } = ConceptNameCodeSequence; - return ( - (CodingSchemeDesignator == code.CodingSchemeDesignator && - CodeValue == code.CodeValue) || - (oldCode && - CodingSchemeDesignator == oldCode.CodingSchemeDesignator && - CodeValue == oldCode.CodeValue) - ); +export type AdapterOptions = { + /** + * The parent type is another type which could be used to parse this instance, + * but for which this sub-class has a better representation. For example, + * key images are parseable as Probe instances, but are represented as a different tool + * Thus, the name for the key image is `Cornerstone3DTag:Probe:KeyImage` so that + * a prefix query testing just the Probe could parse this object and display it, + * but a better/full path key could also be done. + */ + parentType?: string; + + /** + * If set, then replace this + */ + replace?: boolean | ((original: MeasurementAdapter) => void); }; -function getTID300ContentItem( - tool, - toolType, - ReferencedSOPSequence, - toolClass, - worldToImageCoords -) { - const args = toolClass.getTID300RepresentationArguments( - tool, - worldToImageCoords +/** + * A measurement adapter parses/creates data for DICOM SR measurements + */ +export interface MeasurementAdapter { + toolType: string; + TID300Representation; + trackingIdentifierTextValue: string; + trackingIdentifiers: Set; + + /** + * The parent type is the base type of the adapter that is used for the + * identifier, being compatible with older versions to read that subtype. + */ + parentType: string; + + /** + * Applies the options and registers this tool + */ + init(toolType: string, representation, options?: AdapterOptions); + + getMeasurementData( + measurementGroup, + sopInstanceUIDToImageIdMap, + imageToWorldCoords, + metadata, + trackingIdentifier: string ); - args.ReferencedSOPSequence = ReferencedSOPSequence; - const TID300Measurement = new toolClass.TID300Representation(args); + isValidCornerstoneTrackingIdentifier(trackingIdentifier: string): boolean; - return TID300Measurement; + getTID300RepresentationArguments( + tool, + worldToImageCoords + ): Record; } -function getMeasurementGroup( - toolType, - toolData, - ReferencedSOPSequence, - worldToImageCoords -) { - const toolTypeData = toolData[toolType]; - const toolClass = - MeasurementReport.CORNERSTONE_TOOL_CLASSES_BY_TOOL_TYPE[toolType]; - if ( - !toolTypeData || - !toolTypeData.data || - !toolTypeData.data.length || - !toolClass - ) { - return; - } +export default class MeasurementReport { + public static CORNERSTONE_3D_TAG = CORNERSTONE_3D_TAG; + + /** Maps tool type to the adapter name used to serialize this item to SR */ + public static measurementAdapterByToolType = new Map< + string, + MeasurementAdapter + >(); - // Loop through the array of tool instances - // for this tool - const Measurements = toolTypeData.data.map(tool => { - return getTID300ContentItem( + /** Maps tracking identifier to tool class to deserialize from SR into a tool instance */ + public static measurementAdapterByTrackingIdentifier = new Map< + string, + MeasurementAdapter + >(); + + public static getTID300ContentItem( + tool, + ReferencedSOPSequence, + toolClass, + worldToImageCoords + ) { + const args = toolClass.getTID300RepresentationArguments( tool, - toolType, - ReferencedSOPSequence, - toolClass, worldToImageCoords ); - }); + args.ReferencedSOPSequence = ReferencedSOPSequence; - return new TID1501MeasurementGroup(Measurements); -} + const TID300Measurement = new toolClass.TID300Representation(args); -export default class MeasurementReport { - public static CORNERSTONE_3D_TAG = CORNERSTONE_3D_TAG; - public static MEASUREMENT_BY_TOOLTYPE = {}; - public static CORNERSTONE_TOOL_CLASSES_BY_UTILITY_TYPE = {}; - public static CORNERSTONE_TOOL_CLASSES_BY_TOOL_TYPE = {}; + return TID300Measurement; + } + + public static codeValueMatch = (group, code, oldCode?) => { + const { ConceptNameCodeSequence } = group; + if (!ConceptNameCodeSequence) { + return; + } + const { CodingSchemeDesignator, CodeValue } = ConceptNameCodeSequence; + return ( + (CodingSchemeDesignator == code.CodingSchemeDesignator && + CodeValue == code.CodeValue) || + (oldCode && + CodingSchemeDesignator == oldCode.CodingSchemeDesignator && + CodeValue == oldCode.CodeValue) + ); + }; + + public static getMeasurementGroup( + toolType, + toolData, + ReferencedSOPSequence, + worldToImageCoords + ) { + const toolTypeData = toolData[toolType]; + const toolClass = this.measurementAdapterByToolType.get(toolType); + if ( + !toolTypeData || + !toolTypeData.data || + !toolTypeData.data.length || + !toolClass + ) { + return; + } + + // Loop through the array of tool instances + // for this tool + const Measurements = toolTypeData.data.map(tool => { + return this.getTID300ContentItem( + tool, + ReferencedSOPSequence, + toolClass, + worldToImageCoords + ); + }); + + return new TID1501MeasurementGroup(Measurements); + } static getCornerstoneLabelFromDefaultState(defaultState) { const { findingSites = [], finding } = defaultState; @@ -156,7 +215,7 @@ export default class MeasurementReport { return derivationSourceDataset; }; - static getSetupMeasurementData( + public static getSetupMeasurementData( MeasurementGroup, sopInstanceUIDToImageIdMap, metadata, @@ -166,11 +225,11 @@ export default class MeasurementReport { const contentSequenceArr = toArray(ContentSequence); const findingGroup = contentSequenceArr.find(group => - codeValueMatch(group, FINDING) + this.codeValueMatch(group, FINDING) ); const findingSiteGroups = contentSequenceArr.filter(group => - codeValueMatch(group, FINDING_SITE, FINDING_SITE_OLD) + this.codeValueMatch(group, FINDING_SITE, FINDING_SITE_OLD) ) || []; const NUMGroup = contentSequenceArr.find( group => group.ValueType === "NUM" @@ -300,7 +359,7 @@ export default class MeasurementReport { const measurementGroups = []; toolTypes.forEach(toolType => { - const group = getMeasurementGroup( + const group = this.getMeasurementGroup( toolType, toolData, ReferencedSOPSequence, @@ -370,69 +429,58 @@ export default class MeasurementReport { // For each of the supported measurement types, compute the measurement data const measurementData = {}; - const cornerstoneToolClasses = - MeasurementReport.CORNERSTONE_TOOL_CLASSES_BY_UTILITY_TYPE; - - const registeredToolClasses = []; - - Object.keys(cornerstoneToolClasses).forEach(key => { - registeredToolClasses.push(cornerstoneToolClasses[key]); - measurementData[key] = []; - }); - measurementGroups.forEach(measurementGroup => { try { const measurementGroupContentSequence = toArray( measurementGroup.ContentSequence ); - const TrackingIdentifierGroup = + const trackingIdentifierGroup = measurementGroupContentSequence.find( contentItem => contentItem.ConceptNameCodeSequence.CodeMeaning === TRACKING_IDENTIFIER ); - const TrackingIdentifierValue = - TrackingIdentifierGroup.TextValue; + const { TextValue: trackingIdentifierValue } = + trackingIdentifierGroup; - const TrackingUniqueIdentifierGroup = + const trackingUniqueIdentifierGroup = measurementGroupContentSequence.find( contentItem => contentItem.ConceptNameCodeSequence.CodeMeaning === TRACKING_UNIQUE_IDENTIFIER ); - const TrackingUniqueIdentifierValue = - TrackingUniqueIdentifierGroup?.UID; + const trackingUniqueIdentifierValue = + trackingUniqueIdentifierGroup?.UID; - const toolClass = + const toolAdapter = hooks?.getToolClass?.( measurementGroup, dataset, - registeredToolClasses + this.measurementAdapterByToolType ) || - registeredToolClasses.find(tc => - tc.isValidCornerstoneTrackingIdentifier( - TrackingIdentifierValue - ) + this.getAdapterForTrackingIdentifier( + trackingIdentifierValue ); - if (toolClass) { - const measurement = toolClass.getMeasurementData( + if (toolAdapter) { + const measurement = toolAdapter.getMeasurementData( measurementGroup, sopInstanceUIDToImageIdMap, imageToWorldCoords, - metadata + metadata, + trackingIdentifierValue ); measurement.TrackingUniqueIdentifier = - TrackingUniqueIdentifierValue; + trackingUniqueIdentifierValue; - console.log(`=== ${toolClass.toolType} ===`); + console.log(`=== ${toolAdapter.toolType} ===`); console.log(measurement); - - measurementData[toolClass.toolType].push(measurement); + measurementData[toolAdapter.toolType] ||= []; + measurementData[toolAdapter.toolType].push(measurement); } } catch (e) { console.warn( @@ -450,16 +498,66 @@ export default class MeasurementReport { /** * Register a new tool type. - * @param toolClass to perform I/O to DICOM for this tool + * @param toolAdapter to perform I/O to DICOM for this tool */ - public static registerTool(toolClass) { - MeasurementReport.CORNERSTONE_TOOL_CLASSES_BY_UTILITY_TYPE[ - toolClass.utilityToolType - ] = toolClass; - MeasurementReport.CORNERSTONE_TOOL_CLASSES_BY_TOOL_TYPE[ - toolClass.toolType - ] = toolClass; - MeasurementReport.MEASUREMENT_BY_TOOLTYPE[toolClass.toolType] = - toolClass.utilityToolType; + public static registerTool( + toolAdapter: MeasurementAdapter, + replace: boolean | ((original) => void) = false + ) { + const registerName = toolAdapter.toolType; + if (this.measurementAdapterByToolType.has(registerName)) { + if (!replace) { + throw new Error( + `The registered tool name ${registerName} already exists in adapters, use a different toolType or use replace` + ); + } + if (typeof replace === "function") { + // Call the function so it can call parent output + replace(this.measurementAdapterByToolType.get(registerName)); + } + } + this.measurementAdapterByToolType.set( + toolAdapter.toolType, + toolAdapter + ); + this.measurementAdapterByTrackingIdentifier.set( + toolAdapter.trackingIdentifierTextValue, + toolAdapter + ); + } + + public static registerTrackingIdentifier( + toolClass, + ...trackingIdentifiers: string[] + ) { + for (const identifier of trackingIdentifiers) { + this.measurementAdapterByTrackingIdentifier.set( + identifier, + toolClass + ); + } + } + + public static getAdapterForTrackingIdentifier(trackingIdentifier: string) { + const adapter = + this.measurementAdapterByTrackingIdentifier.get(trackingIdentifier); + if (adapter) { + return adapter; + } + for (const adapterTest of [ + ...this.measurementAdapterByToolType.values() + ]) { + if ( + adapterTest.isValidCornerstoneTrackingIdentifier( + trackingIdentifier + ) + ) { + this.measurementAdapterByTrackingIdentifier.set( + trackingIdentifier, + adapterTest + ); + return adapterTest; + } + } } } diff --git a/packages/adapters/src/adapters/Cornerstone3D/PlanarFreehandROI.ts b/packages/adapters/src/adapters/Cornerstone3D/PlanarFreehandROI.ts index 25abb55488..4ea174d9bd 100644 --- a/packages/adapters/src/adapters/Cornerstone3D/PlanarFreehandROI.ts +++ b/packages/adapters/src/adapters/Cornerstone3D/PlanarFreehandROI.ts @@ -1,31 +1,16 @@ import MeasurementReport from "./MeasurementReport"; import { utilities } from "dcmjs"; -import CORNERSTONE_3D_TAG from "./cornerstone3DTag"; import { vec3 } from "gl-matrix"; +import BaseAdapter3D from "./BaseAdapter3D"; const { Polyline: TID300Polyline } = utilities.TID300; -const PLANARFREEHANDROI = "PlanarFreehandROI"; -const trackingIdentifierTextValue = `${CORNERSTONE_3D_TAG}:${PLANARFREEHANDROI}`; -const closedContourThreshold = 1e-5; - -class PlanarFreehandROI { - public static toolType = PLANARFREEHANDROI; - public static utilityToolType = PLANARFREEHANDROI; - public static TID300Representation = TID300Polyline; - public static isValidCornerstoneTrackingIdentifier = TrackingIdentifier => { - if (!TrackingIdentifier.includes(":")) { - return false; - } - - const [cornerstone3DTag, toolType] = TrackingIdentifier.split(":"); - - if (cornerstone3DTag !== CORNERSTONE_3D_TAG) { - return false; - } +class PlanarFreehandROI extends BaseAdapter3D { + public static closedContourThreshold = 1e-5; - return toolType === PLANARFREEHANDROI; - }; + static { + this.init("PlanarFreehandROI", TID300Polyline); + } static getMeasurementData( MeasurementGroup, @@ -64,7 +49,7 @@ class PlanarFreehandROI { let isOpenContour = true; // If the contour is closed, this should have been encoded as exactly the same point, so check for a very small difference. - if (distanceBetweenFirstAndLastPoint < closedContourThreshold) { + if (distanceBetweenFirstAndLastPoint < this.closedContourThreshold) { worldCoords.pop(); // Remove the last element which is duplicated. isOpenContour = false; @@ -140,13 +125,11 @@ class PlanarFreehandROI { max, stdDev, /** Other */ - trackingIdentifierTextValue, + trackingIdentifierTextValue: this.trackingIdentifierTextValue, finding, findingSites: findingSites || [] }; } } -MeasurementReport.registerTool(PlanarFreehandROI); - export default PlanarFreehandROI; diff --git a/packages/adapters/src/adapters/Cornerstone3D/Probe.js b/packages/adapters/src/adapters/Cornerstone3D/Probe.js deleted file mode 100644 index 553ae3797c..0000000000 --- a/packages/adapters/src/adapters/Cornerstone3D/Probe.js +++ /dev/null @@ -1,106 +0,0 @@ -import { utilities } from "dcmjs"; -import CORNERSTONE_3D_TAG from "./cornerstone3DTag"; -import MeasurementReport from "./MeasurementReport"; - -const { Point: TID300Point } = utilities.TID300; - -const PROBE = "Probe"; -const trackingIdentifierTextValue = `${CORNERSTONE_3D_TAG}:${PROBE}`; - -class Probe { - static getMeasurementData( - MeasurementGroup, - sopInstanceUIDToImageIdMap, - imageToWorldCoords, - metadata - ) { - const { defaultState, SCOORDGroup, ReferencedFrameNumber } = - MeasurementReport.getSetupMeasurementData( - MeasurementGroup, - sopInstanceUIDToImageIdMap, - metadata, - Probe.toolType - ); - - const referencedImageId = - defaultState.annotation.metadata.referencedImageId; - - const { GraphicData } = SCOORDGroup; - - const worldCoords = []; - for (let i = 0; i < GraphicData.length; i += 2) { - const point = imageToWorldCoords(referencedImageId, [ - GraphicData[i], - GraphicData[i + 1] - ]); - worldCoords.push(point); - } - - const state = defaultState; - - state.annotation.data = { - handles: { - points: worldCoords, - activeHandleIndex: null, - textBox: { - hasMoved: false - } - }, - frameNumber: ReferencedFrameNumber - }; - - return state; - } - - static getTID300RepresentationArguments(tool, worldToImageCoords) { - const { data, metadata } = tool; - let { finding, findingSites } = tool; - const { referencedImageId } = metadata; - - if (!referencedImageId) { - throw new Error( - "Probe.getTID300RepresentationArguments: referencedImageId is not defined" - ); - } - - const { points } = data.handles; - - const pointsImage = points.map(point => { - const pointImage = worldToImageCoords(referencedImageId, point); - return { - x: pointImage[0], - y: pointImage[1] - }; - }); - - const TID300RepresentationArguments = { - points: pointsImage, - trackingIdentifierTextValue, - findingSites: findingSites || [], - finding - }; - - return TID300RepresentationArguments; - } -} - -Probe.toolType = PROBE; -Probe.utilityToolType = PROBE; -Probe.TID300Representation = TID300Point; -Probe.isValidCornerstoneTrackingIdentifier = TrackingIdentifier => { - if (!TrackingIdentifier.includes(":")) { - return false; - } - - const [cornerstone3DTag, toolType] = TrackingIdentifier.split(":"); - - if (cornerstone3DTag !== CORNERSTONE_3D_TAG) { - return false; - } - - return toolType === PROBE; -}; - -MeasurementReport.registerTool(Probe); - -export default Probe; diff --git a/packages/adapters/src/adapters/Cornerstone3D/Probe.ts b/packages/adapters/src/adapters/Cornerstone3D/Probe.ts new file mode 100644 index 0000000000..e881dbf21d --- /dev/null +++ b/packages/adapters/src/adapters/Cornerstone3D/Probe.ts @@ -0,0 +1,65 @@ +import { utilities } from "dcmjs"; +import MeasurementReport from "./MeasurementReport"; +import BaseAdapter3D from "./BaseAdapter3D"; + +const { Point: TID300Point } = utilities.TID300; + +class Probe extends BaseAdapter3D { + static { + this.init("Probe", TID300Point); + this.registerLegacy(); + } + + static getMeasurementData( + MeasurementGroup, + sopInstanceUIDToImageIdMap, + imageToWorldCoords, + metadata, + trackingIdentifier + ) { + const state = super.getMeasurementData( + MeasurementGroup, + sopInstanceUIDToImageIdMap, + imageToWorldCoords, + metadata, + trackingIdentifier + ); + + const { defaultState, SCOORDGroup } = + MeasurementReport.getSetupMeasurementData( + MeasurementGroup, + sopInstanceUIDToImageIdMap, + metadata, + Probe.toolType + ); + + const referencedImageId = + defaultState.annotation.metadata.referencedImageId; + + const { GraphicData } = SCOORDGroup; + + const worldCoords = []; + for (let i = 0; i < GraphicData.length; i += 2) { + const point = imageToWorldCoords(referencedImageId, [ + GraphicData[i], + GraphicData[i + 1] + ]); + worldCoords.push(point); + } + + state.annotation.data = { + ...state.annotation.data, + handles: { + points: worldCoords, + activeHandleIndex: null, + textBox: { + hasMoved: false + } + } + }; + + return state; + } +} + +export default Probe; diff --git a/packages/adapters/src/adapters/Cornerstone3D/RectangleROI.ts b/packages/adapters/src/adapters/Cornerstone3D/RectangleROI.ts index e26e247918..f659a62e47 100644 --- a/packages/adapters/src/adapters/Cornerstone3D/RectangleROI.ts +++ b/packages/adapters/src/adapters/Cornerstone3D/RectangleROI.ts @@ -1,31 +1,15 @@ import { utilities } from "dcmjs"; -import CORNERSTONE_3D_TAG from "./cornerstone3DTag"; import MeasurementReport from "./MeasurementReport"; +import BaseAdapter3D from "./BaseAdapter3D"; const { Polyline: TID300Polyline } = utilities.TID300; -const TOOLTYPE = "RectangleROI"; -const trackingIdentifierTextValue = `${CORNERSTONE_3D_TAG}:${TOOLTYPE}`; - -class RectangleROI { - public static toolType = TOOLTYPE; - public static utilityToolType = TOOLTYPE; - public static TID300Representation = TID300Polyline; - - public static isValidCornerstoneTrackingIdentifier = TrackingIdentifier => { - if (!TrackingIdentifier.includes(":")) { - return false; - } - - const [cornerstone3DTag, toolType] = TrackingIdentifier.split(":"); - - if (cornerstone3DTag !== CORNERSTONE_3D_TAG) { - return false; - } - - return toolType === TOOLTYPE; - }; - +class RectangleROI extends BaseAdapter3D { + static { + this.init("RectangleROI", TID300Polyline); + // Register using the Cornerstone 1.x name so this tool is used to load it + this.registerLegacy(); + } public static getMeasurementData( MeasurementGroup, sopInstanceUIDToImageIdMap, @@ -109,13 +93,11 @@ class RectangleROI { ], area, perimeter, - trackingIdentifierTextValue, + trackingIdentifierTextValue: this.trackingIdentifierTextValue, finding, findingSites: findingSites || [] }; } } -MeasurementReport.registerTool(RectangleROI); - export default RectangleROI; diff --git a/packages/adapters/src/adapters/Cornerstone3D/UltrasoundDirectional.ts b/packages/adapters/src/adapters/Cornerstone3D/UltrasoundDirectional.ts index d502c8063e..affef4a1d2 100644 --- a/packages/adapters/src/adapters/Cornerstone3D/UltrasoundDirectional.ts +++ b/packages/adapters/src/adapters/Cornerstone3D/UltrasoundDirectional.ts @@ -1,30 +1,13 @@ import { utilities } from "dcmjs"; -import CORNERSTONE_3D_TAG from "./cornerstone3DTag"; import MeasurementReport from "./MeasurementReport"; +import BaseAdapter3D from "./BaseAdapter3D"; const { Length: TID300Length } = utilities.TID300; -const ULTRASOUND_DIRECTIONAL = "UltrasoundDirectionalTool"; -const trackingIdentifierTextValue = `${CORNERSTONE_3D_TAG}:${ULTRASOUND_DIRECTIONAL}`; - -class UltrasoundDirectional { - public static toolType = ULTRASOUND_DIRECTIONAL; - public static utilityToolType = ULTRASOUND_DIRECTIONAL; - public static TID300Representation = TID300Length; - public static isValidCornerstoneTrackingIdentifier = TrackingIdentifier => { - if (!TrackingIdentifier.includes(":")) { - return false; - } - - const [cornerstone3DTag, toolType] = TrackingIdentifier.split(":"); - - if (cornerstone3DTag !== CORNERSTONE_3D_TAG) { - return false; - } - - return toolType === ULTRASOUND_DIRECTIONAL; - }; - +class UltrasoundDirectional extends BaseAdapter3D { + static { + this.init("UltrasoundDirectionalTool", TID300Length); + } // TODO: this function is required for all Cornerstone Tool Adapters, since it is called by MeasurementReport. static getMeasurementData( MeasurementGroup, @@ -91,13 +74,11 @@ class UltrasoundDirectional { return { point1, point2, - trackingIdentifierTextValue, + trackingIdentifierTextValue: this.trackingIdentifierTextValue, finding, findingSites: findingSites || [] }; } } -MeasurementReport.registerTool(UltrasoundDirectional); - export default UltrasoundDirectional; diff --git a/packages/adapters/src/adapters/Cornerstone3D/index.ts b/packages/adapters/src/adapters/Cornerstone3D/index.ts index ef0e057a1c..a2a6489489 100644 --- a/packages/adapters/src/adapters/Cornerstone3D/index.ts +++ b/packages/adapters/src/adapters/Cornerstone3D/index.ts @@ -13,12 +13,15 @@ import Length from "./Length"; import PlanarFreehandROI from "./PlanarFreehandROI"; import Probe from "./Probe"; import UltrasoundDirectional from "./UltrasoundDirectional"; +import BaseAdapter3D from "./BaseAdapter3D"; import * as Segmentation from "./Segmentation"; import * as ParametricMap from "./ParametricMap"; import * as RTSS from "./RTStruct"; +import KeyImage from "./KeyImage"; const Cornerstone3DSR = { + BaseAdapter3D, Bidirectional, CobbAngle, Angle, @@ -30,6 +33,7 @@ const Cornerstone3DSR = { Probe, PlanarFreehandROI, UltrasoundDirectional, + KeyImage, MeasurementReport, CodeScheme, CORNERSTONE_3D_TAG diff --git a/packages/adapters/test/KeyImage.jest.js b/packages/adapters/test/KeyImage.jest.js new file mode 100644 index 0000000000..1396fc6753 --- /dev/null +++ b/packages/adapters/test/KeyImage.jest.js @@ -0,0 +1,28 @@ +import { describe, it, expect, beforeEach } from "@jest/globals"; +import { Cornerstone3DSR } from "../src/adapters/Cornerstone3D"; + +const { KeyImage, Probe } = Cornerstone3DSR; + +describe("KeyImage", () => { + beforeEach(() => { + // Setup adapters + }); + + it("Must define tool type", () => { + expect(KeyImage.toolType).toBe("KeyImage"); + // Even after registering a sub-type of Probe, the Probe type should be correct + expect(Probe.toolType).toBe("Probe"); + }); + + it("Must define tracking identifiers", () => { + expect(KeyImage.trackingIdentifierTextValue).toBe( + "Cornerstone3DTools@^0.1.0:Probe:KeyImage" + ); + // Even after registering a sub-type of Probe, the Probe tracking identifier should be unchanged + expect(Probe.trackingIdentifierTextValue).toBe( + "Cornerstone3DTools@^0.1.0:Probe" + ); + }); +}); + +// diff --git a/packages/adapters/test/Probe.jest.js b/packages/adapters/test/Probe.jest.js new file mode 100644 index 0000000000..c2c4f99968 --- /dev/null +++ b/packages/adapters/test/Probe.jest.js @@ -0,0 +1,16 @@ +import { describe, it, expect, beforeEach } from "@jest/globals"; +import { Cornerstone3DSR } from "../src/adapters/Cornerstone3D"; + +const { Probe } = Cornerstone3DSR; + +describe("Probe", () => { + beforeEach(() => { + // Setup adapters + }); + + it("Must define tool type", () => { + expect(Probe.toolType).toBe("Probe"); + }); +}); + +// diff --git a/packages/core/jest.config.js b/packages/core/jest.config.js index 10f5cc4456..be56fc7a06 100644 --- a/packages/core/jest.config.js +++ b/packages/core/jest.config.js @@ -5,6 +5,7 @@ const path = require('path'); module.exports = { ...base, displayName: 'core', + setupFiles: ['jest-canvas-mock'], moduleNameMapper: { '^@cornerstonejs/(.*)$': path.resolve(__dirname, '../$1/src'), }, diff --git a/packages/core/test/stackViewport_node_render.jest.js b/packages/core/test/stackViewport_node_render.jest.js new file mode 100644 index 0000000000..3175697d65 --- /dev/null +++ b/packages/core/test/stackViewport_node_render.jest.js @@ -0,0 +1,117 @@ +import * as cornerstone3D from '../src/index'; +import { + fakeImageLoader, + fakeMetaDataProvider, +} from '../../../utils/test/testUtilsImageLoader'; + +import { describe, it, expect } from '@jest/globals'; +import { render } from 'react-dom'; + +const { + utilities, + setUseCPURendering, + RenderingEngine, + Enums, + imageLoader, + metaData, + init, +} = cornerstone3D; +const { ViewportType, Events } = Enums; + +const renderingEngineId = utilities.uuidv4(); +const viewportId = 'VIEWPORT'; + +function encodeImageIdInfo(info) { + return `fakeImageLoader:${encodeURIComponent(JSON.stringify(info))}`; +} + +const imageInfo = { + loader: 'fakeImageLoader', + name: 'imageURI', + rows: 64, + columns: 64, + barStart: 20, + barWidth: 5, + xSpacing: 1, + ySpacing: 1, + sliceIndex: 0, +}; + +const imageId = encodeImageIdInfo(imageInfo); + +function setSize(element, width, height) { + Object.defineProperty(element, 'offsetWidth', { value: width }); + Object.defineProperty(element, 'offsetHeight', { value: height }); + Object.defineProperty(element, 'clientWidth', { value: width }); + Object.defineProperty(element, 'clientHeight', { value: height }); + Object.defineProperty(element, 'getBoundingClientRect', { + value: () => ({ width, height }), + }); +} + +function initCore() { + init({}); + imageLoader.registerImageLoader('fakeImageLoader', fakeImageLoader); + + metaData.addProvider(utilities.calibratedPixelSpacingMetadataProvider.get); + metaData.addProvider(utilities.genericMetadataProvider.get); + metaData.addProvider(fakeMetaDataProvider, 10000); + + document.body.innerHTML = `
+
+ +
+
`; + const renderingEngine = new RenderingEngine(renderingEngineId); + const element = document.getElementById('elementId'); + setSize(element, 100, 100); + const viewportElement = document.getElementById('viewport-element'); + setSize(viewportElement, 100, 100); + const canvasElement = document.getElementById('cornerstone-canvas'); + setSize(canvasElement, 100, 100); + return { renderingEngine, element }; +} + +describe('stackViewport_node_render', function () { + let viewport, element, renderingEngine; + + beforeEach(() => { + setUseCPURendering(true); + window.devicePixelRatio = 1; + + const initData = initCore(); + element = initData.element; + renderingEngine = initData.renderingEngine; + + const viewportInput = { + viewportId, + type: ViewportType.STACK, + element, + defaultOptions: { + background: [0.2, 0, 0.2], + }, + }; + + renderingEngine.enableElement(viewportInput); + viewport = renderingEngine.getViewport(viewportId); + }); + + it('Basic Viewport Creation', () => { + expect(viewport).not.toBeUndefined(); + }); + + it('Should render simple stack', () => { + let promise = new Promise((resolve) => { + element.addEventListener(Events.IMAGE_RENDERED, () => { + const canvas = viewport.getCanvas(); + const image = canvas.toDataURL('image/png'); + // console.error('Rendered image', image); + expect(image).toBeTruthy(); + resolve(image); + }); + }); + viewport.setStack([imageId], 0); + viewport.render(); + return promise; + }); +}); diff --git a/packages/docs/docs/getting-started/vue-angular-react-vite.md b/packages/docs/docs/getting-started/vue-angular-react-vite.md index 7ac910ce56..9e9c61e509 100644 --- a/packages/docs/docs/getting-started/vue-angular-react-vite.md +++ b/packages/docs/docs/getting-started/vue-angular-react-vite.md @@ -69,10 +69,9 @@ initialize({ Next, you'll need to edit the Vite configuration to include the following. Keep in mind that we're including the WASM files in the build and excluding them from dependency optimization. There is an ongoing issue in vite with `import.meta.url` ([check their GitHub issue](https://github.com/vitejs/vite/issues/8427)), which force us to exclude the wasm files from optimization of dependencies. - ```js export default defineConfig({ - assetsInclude: ["**/*.wasm"], + assetsInclude: ['**/*.wasm'], plugins: [ react(), // for dicom-parser @@ -81,16 +80,15 @@ export default defineConfig({ // seems like only required in dev mode optimizeDeps: { exclude: [ - "@cornerstonejs/dicom-image-loader", - "@cornerstonejs/polymorphic-segmentation", + '@cornerstonejs/dicom-image-loader', + '@cornerstonejs/polymorphic-segmentation', ], - include: ["dicom-parser"], + include: ['dicom-parser'], }, worker: { - format: "es", + format: 'es', }, -}) - +}); ``` #### Labelmap Interpolation @@ -104,12 +102,12 @@ yarn add @cornerstonejs/labelmap-interpolation and then you need to edit the vite config to include the following: ```js -import { defineConfig } from "vite" -import react from "@vitejs/plugin-react" -import { viteCommonjs } from "@originjs/vite-plugin-commonjs" +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import { viteCommonjs } from '@originjs/vite-plugin-commonjs'; export default defineConfig({ - assetsInclude: ["**/*.wasm"], + assetsInclude: ['**/*.wasm'], plugins: [ react(), // for dicom-parser @@ -118,26 +116,18 @@ export default defineConfig({ // seems like only required in dev mode optimizeDeps: { exclude: [ - "@cornerstonejs/dicom-image-loader", - "@cornerstonejs/polymorphic-segmentation", - "@cornerstonejs/labelmap-interpolation", + '@cornerstonejs/dicom-image-loader', + '@cornerstonejs/polymorphic-segmentation', + '@cornerstonejs/labelmap-interpolation', ], - include: ["dicom-parser"], + include: ['dicom-parser'], }, worker: { - format: "es", + format: 'es', }, -}) +}); ``` - - - - - - - - ## Webpack ### Basic Setup @@ -166,12 +156,8 @@ You might need to add ```js - ``` - - - ## Troubleshooting ### 1. Rollup Options @@ -188,7 +174,6 @@ worker: { }, ``` - ### 2. Path Resolution Issues with @cornerstonejs/core If you encounter the error "No known conditions for "./types" specifier in "@cornerstonejs/core" package" during build (while development works fine), add the following alias to your Vite configuration: @@ -236,7 +221,6 @@ Also since we are using wasm, you will need to add the following to your webpack }, ``` - ### 5. Svelte + Vite Similar to the configuration above, use the CommonJS plugin converting commonjs to esm. Otherwise, it will be pending at `await viewport.setStack(stack);`, the image will not be rendered. diff --git a/packages/docs/docs/migration-guides/3x/5-adapters.md b/packages/docs/docs/migration-guides/3x/5-adapters.md new file mode 100644 index 0000000000..4160751e23 --- /dev/null +++ b/packages/docs/docs/migration-guides/3x/5-adapters.md @@ -0,0 +1,64 @@ +--- +id: adapters +title: 'Adapters API' +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + + +## Key Changes: + +* MeasurementsReport has two maps instead of objects for setting the + adapter classes, mapping the tool type to adapter class and the + tracking id to adapter class. +* A new register additional tracking id method exists to allow adding + custom adapter methods. +* Adapter implementations now have a base class to handle some of the + definition. This allows calling into the base class to handle some of the + definition such as the is tracking handling. +* The MeasurementsReport class is now extensible to create a new class with + completely different default handling. To do this, the two map attributes + need to be redeclared, and the new instance registered for the handlers. +* There is now an init method to create tracking identifiers and register a new + handler. +* The annotation changed event no longer requires the viewport id/rendering id + * This change is done so that measurements can be updated when not visible + +## Migration Steps: + +### 1. Replace MeasurementsReports.CORNERSTONE_TOOL_CLASSES_BY_UTILITY_TYPE + +**Before:** +```diff +- const toolClass = MeasurementReports.CORNERSTONE_TOOL_CLASSES_BY_UTILITY_TYPE[toolType]; +``` + +**After:** +```diff +- const toolClass = MeasurementReports.measurementAdapterByToolType.get(toolType); +``` + +### 2. Replace Tool instance adapter registration which is identical to existing registration + +**Before:** +```diff +- class MyNewToolAdapter { ... identical to eg Probe Adapter } +``` + +**After:** +```diff +- const MyNewToolAdapter = Probe.initCopy('MyNewTool'); +``` + +### 3. Replace old tool registration with registerTrackingIdentifier + +**Before:** +```diff +- class OldToolAdapter { ... identical to eg Length v1.0 except has :v1.0 at end of tracking identifier } +``` + +**After:** +```diff +- MeasurementReport.registerTrackingIdentifier(Length, `${Length.trackingIdentifierTextValue}:v1.0`); +``` diff --git a/packages/docs/package.json b/packages/docs/package.json index e3f90d6d5c..e2fa860b6a 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -1,6 +1,6 @@ { "name": "docs", - "version": "2.1.10", + "version": "3.0.0-beta.5", "private": true, "repository": "https://github.com/cornerstonejs/cornerstone3D", "scripts": { diff --git a/packages/tools/examples/stackRange/index.ts b/packages/tools/examples/stackRange/index.ts index bb6e5dfe1b..02b7be9c2c 100644 --- a/packages/tools/examples/stackRange/index.ts +++ b/packages/tools/examples/stackRange/index.ts @@ -180,6 +180,17 @@ addButtonToToolbar({ }, }); +addButtonToToolbar({ + id: 'Toggle Point', + title: 'Toggle Point', + onClick() { + const annotation = getActiveAnnotation(); + if (annotation) { + KeyImageTool.setPoint(annotation); + } + }, +}); + function annotationModifiedListener(evt) { updateAnnotationDiv( evt.detail.annotation?.annotationUID || diff --git a/packages/tools/src/stateManagement/annotation/helpers/state.ts b/packages/tools/src/stateManagement/annotation/helpers/state.ts index ce96dd7119..4347c51821 100644 --- a/packages/tools/src/stateManagement/annotation/helpers/state.ts +++ b/packages/tools/src/stateManagement/annotation/helpers/state.ts @@ -95,14 +95,16 @@ function triggerAnnotationRemoved( /** * Triggers an annotation modified event. + * Note this no longer requires the element, which should be handled by testing + * to see which viewports this element is shown on. */ function triggerAnnotationModified( annotation: Annotation, - element: HTMLDivElement, + element?: HTMLDivElement, changeType = ChangeTypes.HandlesUpdated ): void { - const enabledElement = getEnabledElement(element); - const { viewportId, renderingEngineId } = enabledElement; + const enabledElement = element && getEnabledElement(element); + const { viewportId, renderingEngineId } = enabledElement || {}; const eventType = Events.ANNOTATION_MODIFIED; const eventDetail: AnnotationModifiedEventDetail = { annotation, diff --git a/packages/tools/src/tools/CrosshairsTool.ts b/packages/tools/src/tools/CrosshairsTool.ts index ed2244c8a2..869dabb0dd 100644 --- a/packages/tools/src/tools/CrosshairsTool.ts +++ b/packages/tools/src/tools/CrosshairsTool.ts @@ -109,9 +109,6 @@ class CrosshairsTool extends AnnotationTool { _getReferenceLineControllable?: (viewportId: string) => boolean; _getReferenceLineDraggableRotatable?: (viewportId: string) => boolean; _getReferenceLineSlabThicknessControlsOn?: (viewportId: string) => boolean; - editData: { - annotation: Annotation; - } | null; constructor( toolProps: PublicToolProps = {}, diff --git a/packages/tools/src/tools/annotation/KeyImageTool.ts b/packages/tools/src/tools/annotation/KeyImageTool.ts index 61dbd7982a..8705f3adc4 100644 --- a/packages/tools/src/tools/annotation/KeyImageTool.ts +++ b/packages/tools/src/tools/annotation/KeyImageTool.ts @@ -13,21 +13,27 @@ import { triggerAnnotationCompleted, triggerAnnotationModified, } from '../../stateManagement/annotation/helpers/state'; -import { drawArrow as drawArrowSvg } from '../../drawingSvg'; +import { + drawArrow as drawArrowSvg, + drawHandles as drawHandlesSvg, +} from '../../drawingSvg'; import { state } from '../../store/state'; import { getViewportIdsWithToolToRender } from '../../utilities/viewportFilters'; import triggerAnnotationRenderForViewportIds from '../../utilities/triggerAnnotationRenderForViewportIds'; -import { resetElementCursor } from '../../cursors/elementCursor'; +import { + resetElementCursor, + hideElementCursor, +} from '../../cursors/elementCursor'; import type { EventTypes, - ToolHandle, PublicToolProps, ToolProps, SVGDrawingHelper, Annotation, } from '../../types'; +import type { KeyImageAnnotation } from '../../types/ToolSpecificAnnotationTypes'; import type { StyleSpecifier } from '../../types/AnnotationStyle'; type Point2 = Types.Point2; @@ -35,17 +41,23 @@ type Point2 = Types.Point2; class KeyImageTool extends AnnotationTool { static toolName = 'KeyImage'; + /** A mix in data element to set the series level annotation */ + public static dataSeries = { + data: { + seriesLevel: true, + }, + }; + + /** A mix in data element to set the point to be true. That renders as a point + * on the image rather than just selecting the image itself + */ + public static dataPoint = { + data: { + isPoint: true, + }, + }; + _throttledCalculateCachedStats: Function; - editData: { - annotation: Annotation; - viewportIdsToRender: string[]; - handleIndex?: number; - movingTextBox?: boolean; - newAnnotation?: boolean; - hasMoved?: boolean; - } | null; - isDrawing: boolean; - isHandleOutsideImage: boolean; constructor( toolProps: PublicToolProps = {}, @@ -56,6 +68,11 @@ class KeyImageTool extends AnnotationTool { changeTextCallback, canvasPosition: [10, 10], canvasSize: 10, + handleRadius: '6', + /** If true, this selects the entire series/display set */ + seriesLevel: false, + /** If true, shows the point selected */ + isPoint: false, }, } ) { @@ -72,13 +89,20 @@ class KeyImageTool extends AnnotationTool { */ addNewAnnotation = (evt: EventTypes.InteractionEventType) => { const eventDetail = evt.detail; - const { element } = eventDetail; + const { element, currentPoints } = eventDetail; const enabledElement = getEnabledElement(element); const { viewport } = enabledElement; + const worldPos = currentPoints.world; const annotation = (( this.constructor - )).createAnnotationForViewport(viewport); + )).createAnnotationForViewport(viewport, { + data: { + handles: { points: [[...worldPos]] }, + seriesLevel: this.configuration.seriesLevel, + isPoint: this.configuration.isPoint, + }, + }); addAnnotation(annotation, element); @@ -110,10 +134,6 @@ class KeyImageTool extends AnnotationTool { return annotation; }; - public cancel() { - // No op - the annotation can't be in a partial state - } - /** * It returns if the canvas point is near the provided length annotation in the provided * element or not. A proximity is passed to the function to determine the @@ -135,6 +155,10 @@ class KeyImageTool extends AnnotationTool { const { viewport } = enabledElement; const { data } = annotation; + if (!data?.isPoint) { + return false; + } + const { canvasPosition, canvasSize } = this.configuration; if (!canvasPosition?.length) { return false; @@ -161,20 +185,79 @@ class KeyImageTool extends AnnotationTool { handleSelectedCallback( evt: EventTypes.InteractionEventType, - annotation: Annotation, - handle: ToolHandle + annotation: KeyImageAnnotation ): void { - // Nothing special to do here. + const eventDetail = evt.detail; + const { element } = eventDetail; + + annotation.highlighted = true; + + const viewportIdsToRender = getViewportIdsWithToolToRender( + element, + this.getToolName() + ); + + // Find viewports to render on drag. + + this.editData = { + //handle, // This would be useful for other tools with more than one handle + annotation, + viewportIdsToRender, + }; + this._activateModify(element); + + hideElementCursor(element); + + triggerAnnotationRenderForViewportIds(viewportIdsToRender); + + evt.preventDefault(); + } + + public static setPoint( + annotation, + isPoint: boolean = !annotation.data.isPoint, + element? + ) { + annotation.data.isPoint = isPoint; + triggerAnnotationModified(annotation, element); } _endCallback = (evt: EventTypes.InteractionEventType): void => { const eventDetail = evt.detail; const { element } = eventDetail; - this.doneEditMemo(); + const { annotation, viewportIdsToRender, newAnnotation } = this.editData; + + const { viewportId, renderingEngine } = getEnabledElement(element); + this.eventDispatchDetail = { + viewportId, + renderingEngineId: renderingEngine.id, + }; this._deactivateModify(element); + resetElementCursor(element); + + if (newAnnotation) { + this.createMemo(element, annotation, { newAnnotation }); + } + + this.editData = null; + this.isDrawing = false; + this.doneEditMemo(); + + if ( + this.isHandleOutsideImage && + this.configuration.preventHandleOutsideImage + ) { + removeAnnotation(annotation.annotationUID); + } + + triggerAnnotationRenderForViewportIds(viewportIdsToRender); + + if (newAnnotation) { + triggerAnnotationCompleted(annotation); + } }; doubleClickCallback = (evt: EventTypes.TouchTapEventType): void => { @@ -226,9 +309,6 @@ class KeyImageTool extends AnnotationTool { _doneChangingTextCallback(element, annotation, updatedText): void { annotation.data.text = updatedText; - const enabledElement = getEnabledElement(element); - const { renderingEngine } = enabledElement; - const viewportIdsToRender = getViewportIdsWithToolToRender( element, this.getToolName() @@ -239,48 +319,69 @@ class KeyImageTool extends AnnotationTool { triggerAnnotationModified(annotation, element); } - _activateModify = (element: HTMLDivElement) => { + _dragCallback = (evt) => { + this.isDrawing = true; + const eventDetail = evt.detail; + const { currentPoints, element } = eventDetail; + const worldPos = currentPoints.world; + + const { annotation, viewportIdsToRender, newAnnotation } = this.editData; + const { data } = annotation; + + this.createMemo(element, annotation, { newAnnotation }); + + data.handles.points[0] = [...worldPos] as Types.Point3; + annotation.invalidated = true; + + triggerAnnotationRenderForViewportIds(viewportIdsToRender); + }; + + public cancel(element: HTMLDivElement) { + // If it is mid-draw or mid-modify + if (this.isDrawing) { + this.isDrawing = false; + this._deactivateModify(element); + resetElementCursor(element); + + const { annotation, viewportIdsToRender, newAnnotation } = this.editData; + const { data } = annotation; + + annotation.highlighted = false; + data.handles.activeHandleIndex = null; + + triggerAnnotationRenderForViewportIds(viewportIdsToRender); + + if (newAnnotation) { + triggerAnnotationCompleted(annotation); + } + + this.editData = null; + return annotation.annotationUID; + } + } + + _activateModify = (element) => { state.isInteractingWithTool = true; - element.addEventListener( - Events.MOUSE_UP, - this._endCallback as EventListener - ); - element.addEventListener( - Events.MOUSE_CLICK, - this._endCallback as EventListener - ); + element.addEventListener(Events.MOUSE_UP, this._endCallback); + element.addEventListener(Events.MOUSE_DRAG, this._dragCallback); + element.addEventListener(Events.MOUSE_CLICK, this._endCallback); - element.addEventListener( - Events.TOUCH_TAP, - this._endCallback as EventListener - ); - element.addEventListener( - Events.TOUCH_END, - this._endCallback as EventListener - ); + element.addEventListener(Events.TOUCH_END, this._endCallback); + element.addEventListener(Events.TOUCH_DRAG, this._dragCallback); + element.addEventListener(Events.TOUCH_TAP, this._endCallback); }; - _deactivateModify = (element: HTMLDivElement) => { + _deactivateModify = (element) => { state.isInteractingWithTool = false; - element.removeEventListener( - Events.MOUSE_UP, - this._endCallback as EventListener - ); - element.removeEventListener( - Events.MOUSE_CLICK, - this._endCallback as EventListener - ); + element.removeEventListener(Events.MOUSE_UP, this._endCallback); + element.removeEventListener(Events.MOUSE_DRAG, this._dragCallback); + element.removeEventListener(Events.MOUSE_CLICK, this._endCallback); - element.removeEventListener( - Events.TOUCH_TAP, - this._endCallback as EventListener - ); - element.removeEventListener( - Events.TOUCH_END, - this._endCallback as EventListener - ); + element.removeEventListener(Events.TOUCH_END, this._endCallback); + element.removeEventListener(Events.TOUCH_DRAG, this._dragCallback); + element.removeEventListener(Events.TOUCH_TAP, this._endCallback); }; /** @@ -324,18 +425,33 @@ class KeyImageTool extends AnnotationTool { // Draw SVG for (let i = 0; i < annotations.length; i++) { const annotation = annotations[i]; - const { annotationUID } = annotation; + const { annotationUID, data } = annotation; styleSpecifier.annotationUID = annotationUID; - const { color } = this.getAnnotationStyle({ + const { color, lineWidth } = this.getAnnotationStyle({ annotation, styleSpecifier, }); const { canvasPosition, canvasSize } = this.configuration; - if (canvasPosition?.length) { - const arrowUID = '1'; + const arrowUID = '1'; + if (data?.isPoint) { + const point = data.handles.points[0]; + const canvasCoordinates = viewport.worldToCanvas(point); + + drawHandlesSvg( + svgDrawingHelper, + annotationUID, + arrowUID, + [canvasCoordinates], + { + color, + lineWidth, + handleRadius: this.configuration.handleRadius, + } + ); + } else if (canvasPosition?.length) { drawArrowSvg( svgDrawingHelper, annotationUID, diff --git a/packages/tools/src/tools/annotation/ProbeTool.ts b/packages/tools/src/tools/annotation/ProbeTool.ts index 013ea23c6f..fdf047b1ec 100644 --- a/packages/tools/src/tools/annotation/ProbeTool.ts +++ b/packages/tools/src/tools/annotation/ProbeTool.ts @@ -39,7 +39,6 @@ import type { ToolHandle, PublicToolProps, SVGDrawingHelper, - Annotation, } from '../../types'; import type { ProbeAnnotation } from '../../types/ToolSpecificAnnotationTypes'; import type { StyleSpecifier } from '../../types/AnnotationStyle'; @@ -94,18 +93,6 @@ const { transformWorldToIndex } = csUtils; class ProbeTool extends AnnotationTool { static toolName = 'Probe'; - editData: { - annotation: Annotation; - viewportIdsToRender: string[]; - newAnnotation?: boolean; - } | null; - eventDispatchDetail: { - viewportId: string; - renderingEngineId: string; - }; - isDrawing: boolean; - isHandleOutsideImage: boolean; - public static probeDefaults = { supportedInteractionTypes: ['Mouse', 'Touch'], configuration: { diff --git a/packages/tools/src/tools/base/AnnotationTool.ts b/packages/tools/src/tools/base/AnnotationTool.ts index 12e82f77ef..db9d22a616 100644 --- a/packages/tools/src/tools/base/AnnotationTool.ts +++ b/packages/tools/src/tools/base/AnnotationTool.ts @@ -51,6 +51,21 @@ const { PointsManager } = csUtils; * abstract methods. */ abstract class AnnotationTool extends AnnotationDisplayTool { + protected eventDispatchDetail: { + viewportId: string; + renderingEngineId: string; + }; + isDrawing: boolean; + isHandleOutsideImage: boolean; + editData: { + annotation: Annotation; + viewportIdsToRender?: string[]; + newAnnotation?: boolean; + handleIndex?: number; + movingTextBox?: boolean; + hasMoved?: boolean; + } | null; + /** * Creates a base annotation object, adding in any annotation base data provided */ diff --git a/packages/tools/src/types/EventTypes.ts b/packages/tools/src/types/EventTypes.ts index c5d893a8b9..8850f50385 100644 --- a/packages/tools/src/types/EventTypes.ts +++ b/packages/tools/src/types/EventTypes.ts @@ -128,14 +128,17 @@ type AnnotationCompletedEventDetail = { * The data that is passed to the event handler when an annotation is modified. */ type AnnotationModifiedEventDetail = { - /** unique id of the viewport */ - viewportId: string; - /** unique id of the rendering engine */ - renderingEngineId: string; /** The annotation that is being added to the annotations manager. */ annotation: Annotation; /** The type of this change */ changeType?: ChangeTypes; + /** + * unique id of the viewport. Note this is optional and only included if the + * element is known/being modified on a given viewport + */ + viewportId?: string; + /** unique id of the rendering engine */ + renderingEngineId?: string; }; /** diff --git a/packages/tools/src/types/ToolSpecificAnnotationTypes.ts b/packages/tools/src/types/ToolSpecificAnnotationTypes.ts index c8a73f2d93..c8b5d1e6d9 100644 --- a/packages/tools/src/types/ToolSpecificAnnotationTypes.ts +++ b/packages/tools/src/types/ToolSpecificAnnotationTypes.ts @@ -56,6 +56,15 @@ export interface ProbeAnnotation extends Annotation { }; } +export type KeyImageAnnotation = ProbeAnnotation & { + data: { + /** Indicates that the point selected is relevant rather than just the image */ + isPoint: boolean; + /** Indicates that this key image selects the entire stack/volume (series) */ + seriesLevel: boolean; + }; +}; + export interface LengthAnnotation extends Annotation { data: { handles: { diff --git a/utils/ExampleRunner/example-info.json b/utils/ExampleRunner/example-info.json index 95ddd48971..83a1d1a7b1 100644 --- a/utils/ExampleRunner/example-info.json +++ b/utils/ExampleRunner/example-info.json @@ -175,6 +175,10 @@ "name": "Stack Annotation Tools", "description": "Demonstrates usage of various annotation tools (Probe, Rectangle ROI, Elliptical ROI, Bidirectional measurements) on a Stack Viewport." }, + "stackRange": { + "name": "Stack Range", + "description": "Demonstrates use of a selection range for key image and other tools" + }, "calibrationTools": { "name": "Calibration Tools", "description": "Demonstrates usage of calibration tools on a Stack Viewport." @@ -258,10 +262,6 @@ "livewireContour": { "name": "Livewire", "description": "Demonstrates how to use the livewire tool to create ROIs" - }, - "stackRange": { - "name": "Stack Range", - "description": "Demonstrates use of a selection range for key image and other tools" } }, "viewports": { diff --git a/yarn.lock b/yarn.lock index 166bbcf28a..539058be4b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -444,6 +444,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz#9cbdd63a9443a2c92a725cca7ebca12cc8dd9f46" integrity sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw== +"@babel/helper-plugin-utils@^7.26.5": + version "7.26.5" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz#18580d00c9934117ad719392c4f6585c9333cc35" + integrity sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg== + "@babel/helper-remap-async-to-generator@^7.24.7", "@babel/helper-remap-async-to-generator@^7.25.0": version "7.25.0" resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.0.tgz#d2f0fbba059a42d68e5e378feaf181ef6055365e" @@ -1607,14 +1612,14 @@ "@babel/helper-plugin-utils" "^7.25.9" "@babel/plugin-transform-runtime@^7.21.4": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.7.tgz#00a5bfaf8c43cf5c8703a8a6e82b59d9c58f38ca" - integrity sha512-YqXjrk4C+a1kZjewqt+Mmu2UuV1s07y8kqcUf4qYLnoqemhR4gRQikhdAhSVJioMjVTu6Mo6pAbaypEA3jY6fw== + version "7.26.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.26.9.tgz#ea8be19ef134668e98f7b54daf7c4f853859dc44" + integrity sha512-Jf+8y9wXQbbxvVYTM8gO5oEF2POdNji0NMltEkG7FtmzD9PVz7/lxpqSdTvwsjTMU5HIHuDVNf2SOxLkWi+wPQ== dependencies: - "@babel/helper-module-imports" "^7.24.7" - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-module-imports" "^7.25.9" + "@babel/helper-plugin-utils" "^7.26.5" babel-plugin-polyfill-corejs2 "^0.4.10" - babel-plugin-polyfill-corejs3 "^0.10.1" + babel-plugin-polyfill-corejs3 "^0.10.6" babel-plugin-polyfill-regenerator "^0.6.1" semver "^6.3.1" @@ -2011,14 +2016,22 @@ integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== "@babel/runtime-corejs2@^7.17.8": - version "7.25.0" - resolved "https://registry.yarnpkg.com/@babel/runtime-corejs2/-/runtime-corejs2-7.25.0.tgz#c1e677774a618f48caff129a8e2a066f1ecd0384" - integrity sha512-aoYVE3tm+vgAoezmXFWmVcp+NlSdsUqQMPL7c6zRxq8KDHCf570pamC7005Q/UkSlTuoL6oeE16zIw/9J3YFyw== + version "7.26.9" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs2/-/runtime-corejs2-7.26.9.tgz#1936c9847aa09faaa941e06f48d3e391e5ca5e1b" + integrity sha512-DHEUkm9RD4RfIThlLTemmHNFVTj9Z/augVFMuyheFjwoFv1jFjauncHrcSW6Kv1TpzTupB01zFk2iRFAh2iE9A== dependencies: core-js "^2.6.12" regenerator-runtime "^0.14.0" -"@babel/runtime-corejs3@^7.15.4", "@babel/runtime-corejs3@^7.22.5": +"@babel/runtime-corejs3@^7.15.4": + version "7.26.9" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.26.9.tgz#8b73bae47453aa3dd2839ac52598581a7dd8332f" + integrity sha512-5EVjbTegqN7RSJle6hMWYxO4voo4rI+9krITk+DWR+diJgGrjZjrIBnJhjrHYYQsFgI7j1w1QnrvV7YSKBfYGg== + dependencies: + core-js-pure "^3.30.2" + regenerator-runtime "^0.14.0" + +"@babel/runtime-corejs3@^7.22.5": version "7.25.0" resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.25.0.tgz#0a318b66dfc765ad10562d829fea372ed7e1eb7d" integrity sha512-BOehWE7MgQ8W8Qn0CQnMtg2tHPHPulcS/5AVpFvs2KCK1ET+0WqZqPvnpRpFN81gYoFopdIEJX9Sgjw3ZBccPg== @@ -8115,7 +8128,7 @@ babel-plugin-polyfill-corejs2@^0.4.10: "@babel/helper-define-polyfill-provider" "^0.6.2" semver "^6.3.1" -babel-plugin-polyfill-corejs3@^0.10.1, babel-plugin-polyfill-corejs3@^0.10.4, babel-plugin-polyfill-corejs3@^0.10.6: +babel-plugin-polyfill-corejs3@^0.10.4, babel-plugin-polyfill-corejs3@^0.10.6: version "0.10.6" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz#2deda57caef50f59c525aeb4964d3b2f867710c7" integrity sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA== @@ -8130,6 +8143,14 @@ babel-plugin-polyfill-regenerator@^0.6.1: dependencies: "@babel/helper-define-polyfill-provider" "^0.6.2" +babel-plugin-transform-import-meta@2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-import-meta/-/babel-plugin-transform-import-meta-2.3.2.tgz#320bd0cd878fe38f39e136edb198c535391bf156" + integrity sha512-902o4GiQqI1GqAXfD5rEoz0PJamUfJ3VllpdWaNsFTwdaNjFSFHawvBO+cp5K2j+g2h3bZ4lnM1Xb6yFYGihtA== + dependencies: + "@babel/template" "^7.25.9" + tslib "^2.8.1" + babel-preset-current-node-syntax@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz#9a929eafece419612ef4ae4f60b1862ebad8ef30" @@ -9679,7 +9700,12 @@ core-js@^2.6.12: resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== -core-js@^3.26.1, core-js@^3.31.1: +core-js@^3.26.1: + version "3.40.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.40.0.tgz#2773f6b06877d8eda102fc42f828176437062476" + integrity sha512-7vsMc/Lty6AGnn7uFpYT56QesI5D2Y/UkgKounk87OP9Z2H9Z8kj6jzcSGAxFmUtDOS0ntK6lbQz+Nsa0Jj6mQ== + +core-js@^3.31.1: version "3.38.0" resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.38.0.tgz#8acb7c050bf2ccbb35f938c0d040132f6110f636" integrity sha512-XPpwqEodRljce9KswjZShh95qJ1URisBeKCjUdq27YdenkslVe7OO0ZJhlYXAChW7OhXaRLl8AAba7IBfoIHug== @@ -23720,7 +23746,7 @@ tslib@^1.8.1, tslib@^1.9.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.0: +tslib@^2.0.0, tslib@^2.8.1: version "2.8.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==