Compare commits

..
396 Commits
Author SHA1 Message Date
ohif-bot 1ad5acae9d chore(version): Update package versions [skip ci] 2025-06-09 15:06:36 +00:00
ohif-bot c19e32e0d1 chore(version): version.json [skip ci] 2025-06-09 15:06:30 +00:00
36813e0c2f chore: recovery 2 (#5014)
Co-authored-by: Dan Rukas <dan.rukas@gmail.com>
Co-authored-by: Tang Cheng <45505657+tctco@users.noreply.github.com>
2025-06-09 10:58:06 -04:00
ohif-bot 9a4a40468f chore(version): Update package versions [skip ci] 2025-04-11 20:02:21 +00:00
ohif-bot 466a867e6a chore(version): version.json [skip ci] 2025-04-11 20:02:14 +00:00
aaba8bf1b0 fix: make datasource selection work and UI fixes (#4952)
Co-authored-by: Pedro Köhler <pedrokohlerbh@gmail.com>
Co-authored-by: Dan Rukas <dan.rukas@gmail.com>
2025-04-11 15:57:29 -04:00
ohif-bot 0b6e9cba76 chore(version): Update package versions [skip ci] 2025-04-10 02:48:32 +00:00
ohif-bot 963664cb63 chore(version): version.json [skip ci] 2025-04-10 02:48:27 +00:00
sedghi 12fe1e107d chore: release 3p10 2025-04-09 22:41:26 -04:00
ohif-bot e1e9b5ccb1 chore(version): Update package versions [skip ci] 2025-04-09 20:54:34 +00:00
ohif-bot 6b7e73b78a chore(version): version.json [skip ci] 2025-04-09 20:54:27 +00:00
AlirezaandJoe Boccanfuso 43bc142439 fix(ui): Enhance UI Components and Update CornerstoneJS Dependencies and resizable panels (#4938)
Co-authored-by: Joe Boccanfuso <joe.boccanfuso@radicalimaging.com>
2025-04-09 16:49:55 -04:00
ohif-bot c05ec1909f chore(version): Update package versions [skip ci] 2025-04-09 14:00:54 +00:00
ohif-bot c7529394ad chore(version): version.json [skip ci] 2025-04-09 14:00:48 +00:00
Pedro Köhler f6f60a63d2 fix: x-ray pixel spacing calibration (#4922) 2025-04-09 09:55:51 -04:00
ohif-bot 0d2ccc5830 chore(version): Update package versions [skip ci] 2025-04-09 03:01:36 +00:00
ohif-bot 08be5cc656 chore(version): version.json [skip ci] 2025-04-09 03:01:30 +00:00
Alireza c453c29449 docs: migration guides 3p10 (#4937) 2025-04-08 22:57:07 -04:00
ohif-bot a69dd8b1a0 chore(version): Update package versions [skip ci] 2025-04-08 22:38:59 +00:00
ohif-bot 17c5642a59 chore(version): version.json [skip ci] 2025-04-08 22:38:53 +00:00
Alireza bd2f843469 chore: update screenshopts for test (#4934) 2025-04-08 18:34:45 -04:00
ohif-bot 8b9b02d84c chore(version): Update package versions [skip ci] 2025-04-08 21:25:58 +00:00
ohif-bot c9958bafad chore(version): version.json [skip ci] 2025-04-08 21:25:52 +00:00
Dan Rukas 85f465968b ui(fix): small Create Report dialog fixes (#4935) 2025-04-08 17:21:08 -04:00
ohif-bot 47e491f1c8 chore(version): Update package versions [skip ci] 2025-04-08 16:10:38 +00:00
ohif-bot e56b53e897 chore(version): version.json [skip ci] 2025-04-08 16:10:32 +00:00
AlirezaandDan Rukas 2bdf4afa4d refactor(ui): Migrate components to ui-next (#4930)
Co-authored-by: Dan Rukas <dan.rukas@gmail.com>
2025-04-08 12:06:07 -04:00
Dan Rukas 1b8729dded ui(components): CinePlayer updated for ui-next (#4932) 2025-04-08 12:03:35 -04:00
ohif-bot f71d370969 chore(version): Update package versions [skip ci] 2025-04-08 13:48:32 +00:00
ohif-bot 033831ee72 chore(version): version.json [skip ci] 2025-04-08 13:48:24 +00:00
Dan Rukas 49c08298fc ui(colors): Adds neutral color variable and updates scrollbars (#4933) 2025-04-08 09:43:30 -04:00
elawendeandErnst Lawende cfa202e89a fix(OIDC): Use new access_token after refresh (#4853)
Co-authored-by: Ernst Lawende <ernstl@bastion365.nl>
2025-04-08 09:43:06 -04:00
ohif-bot 391a5f3bdd chore(version): Update package versions [skip ci] 2025-04-07 20:50:11 +00:00
ohif-bot 64d65a6830 chore(version): version.json [skip ci] 2025-04-07 20:50:05 +00:00
Alireza 3653de7487 fix(segmentation): Add segment jump for new segments and make panels scrollable (#4928) 2025-04-07 16:45:29 -04:00
Dan Rukas a8ac297e16 ui(components): Viewport components updated to ui-next with design updates (#4886) 2025-04-07 16:45:05 -04:00
ohif-bot 4e09f85d5d chore(version): Update package versions [skip ci] 2025-04-07 14:09:45 +00:00
ohif-bot d2f187153d chore(version): version.json [skip ci] 2025-04-07 14:09:39 +00:00
Pedro Köhler 62e5a62066 feat: allow saving to same SR series (#4889) 2025-04-07 10:05:22 -04:00
ohif-bot 7b40963997 chore(version): Update package versions [skip ci] 2025-04-04 17:38:08 +00:00
ohif-bot 75493334cf chore(version): version.json [skip ci] 2025-04-04 17:38:00 +00:00
Pedro Köhler 746f2524eb fix(cine): Resolve Cine Player Faults on DisplaySet Update (#4909) 2025-04-04 13:33:36 -04:00
ohif-bot 0eb9ff235c chore(version): Update package versions [skip ci] 2025-04-04 16:16:04 +00:00
ohif-bot e9b95afd4c chore(version): version.json [skip ci] 2025-04-04 16:15:57 +00:00
Alireza fc33bd0d59 feat(image capture): Enhance SEG and RTSS Support with Dependency Updates and Bug Fixes (#4924) 2025-04-04 12:11:29 -04:00
ohif-bot 479e280fdf chore(version): Update package versions [skip ci] 2025-04-04 16:07:02 +00:00
ohif-bot d4f81cd2b7 chore(version): version.json [skip ci] 2025-04-04 16:06:55 +00:00
nithin-trenserandDevu Jayalekshmi 54fded554e feat: Automate tracking prompts for a less disruptive annotation workflow (#4822)
Co-authored-by: Devu Jayalekshmi <devu.jayalekshmi@trenser.com>
2025-04-04 12:02:13 -04:00
ohif-bot c0391cea1b chore(version): Update package versions [skip ci] 2025-04-03 15:45:43 +00:00
ohif-bot a9e69eb36f chore(version): version.json [skip ci] 2025-04-03 15:45:36 +00:00
dependabot[bot] a52be6460b chore(deps): bump @babel/runtime-corejs2 from 7.25.7 to 7.26.10 (#4843)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-03 11:33:33 -04:00
dependabot[bot] 587b6423cb chore(deps): bump @babel/helpers from 7.26.0 to 7.26.10 in /platform/docs (#4861)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-03 11:33:19 -04:00
dependabot[bot] f52a32d6c5 chore(deps): bump @babel/runtime from 7.24.7 to 7.26.10 (#4860)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-03 11:33:09 -04:00
dependabot[bot] 8142c55b6a chore(deps): bump @babel/runtime-corejs3 from 7.26.0 to 7.26.10 in /platform/docs (#4859)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-03 11:32:54 -04:00
dependabot[bot] 14afb42e2c chore(deps): bump axios from 0.27.2 to 1.8.4 (#4905)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-03 11:32:45 -04:00
dependabot[bot] 4e8e4f1491 chore(deps): bump tar-fs from 2.1.1 to 2.1.2 (#4901)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-03 11:32:39 -04:00
dependabot[bot] 814a2c914b chore(deps): bump image-size from 1.2.0 to 1.2.1 in /platform/docs (#4912)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-03 11:32:31 -04:00
ohif-bot f4fb42adb2 chore(version): Update package versions [skip ci] 2025-04-02 22:23:12 +00:00
ohif-bot 0fee0236c2 chore(version): version.json [skip ci] 2025-04-02 22:23:05 +00:00
Alireza e7fe879318 chore: update dependabot (#4916) 2025-04-02 18:17:05 -04:00
ohif-bot a619634678 chore(version): Update package versions [skip ci] 2025-04-02 22:15:26 +00:00
ohif-bot 651a7dc574 chore(version): version.json [skip ci] 2025-04-02 22:15:20 +00:00
Alireza 8432d5f633 fix(seg): Enhance segmentation tools and UI, refactor code, and update dependencies (#4915) 2025-04-02 18:10:47 -04:00
ohif-bot 1dc00d3030 chore(version): Update package versions [skip ci] 2025-04-01 18:23:53 +00:00
ohif-bot 44d450c5b8 chore(version): version.json [skip ci] 2025-04-01 18:23:46 +00:00
Alireza c75a3f3af5 feat(segmentation): Enhance Segmentation with New AI and Once Click Tools (#4910) 2025-04-01 14:19:03 -04:00
ohif-bot 908482d2e0 chore(version): Update package versions [skip ci] 2025-04-01 13:35:34 +00:00
ohif-bot 6b26db387e chore(version): version.json [skip ci] 2025-04-01 13:35:27 +00:00
Dan Rukasandsedghi b7419fb6a1 ui(components): Adds InputNumber component and 4d panel updates (#4874)
Co-authored-by: sedghi <ar.sedghi@gmail.com>
2025-04-01 09:30:32 -04:00
ohif-bot c4045251fb chore(version): Update package versions [skip ci] 2025-03-31 13:14:52 +00:00
ohif-bot ec4cc64b06 chore(version): version.json [skip ci] 2025-03-31 13:14:46 +00:00
dxlin d9473e8db2 test: update Test/tmtv for v3.10.0-beta.136 (#4893) 2025-03-31 09:10:31 -04:00
ohif-bot 7f3fb9bb61 chore(version): Update package versions [skip ci] 2025-03-28 14:57:45 +00:00
ohif-bot 8cf5b52b84 chore(version): version.json [skip ci] 2025-03-28 14:57:39 +00:00
Alireza d7bd36544a fix: jump to measurement forever (#4867) 2025-03-28 10:53:04 -04:00
Alireza eafa2efda5 Revert "fix(measurement): fix for measurement jump is not switching series in 2DMPR (#4779)" (#4898) 2025-03-28 10:17:33 -04:00
arul-trenser 3f06c966eb fix(measurement): fix for measurement jump is not switching series in 2DMPR (#4779) 2025-03-28 10:14:19 -04:00
ohif-bot 951f95ef0a chore(version): Update package versions [skip ci] 2025-03-27 20:51:50 +00:00
ohif-bot 5737935a96 chore(version): version.json [skip ci] 2025-03-27 20:51:44 +00:00
Bill Wallace 459b0d223a fix: routerBasename setting (#4891) 2025-03-27 16:47:16 -04:00
ohif-bot c368d763b0 chore(version): Update package versions [skip ci] 2025-03-27 19:58:04 +00:00
ohif-bot 6b6f49b867 chore(version): version.json [skip ci] 2025-03-27 19:57:58 +00:00
Igor Octaviano 892d03fc3c fix(seg): multiframe SEG (#4890) 2025-03-27 15:52:22 -04:00
ohif-bot 2acc878f26 chore(version): Update package versions [skip ci] 2025-03-26 18:02:55 +00:00
ohif-bot 1df91fd104 chore(version): version.json [skip ci] 2025-03-26 18:02:48 +00:00
Pedro Köhler 9bde644fce fix: remove duplicated deleteMeasurements code (#4895) 2025-03-26 13:58:15 -04:00
ohif-bot 7f8fa1e1f4 chore(version): Update package versions [skip ci] 2025-03-26 17:44:41 +00:00
ohif-bot dce9c6b7c3 chore(version): version.json [skip ci] 2025-03-26 17:44:35 +00:00
Pedro Köhler 467acbd997 fix: datasource connection error style (#4888) 2025-03-26 13:40:09 -04:00
ohif-bot f90184d9d9 chore(version): Update package versions [skip ci] 2025-03-25 15:04:03 +00:00
ohif-bot cc9720dca2 chore(version): version.json [skip ci] 2025-03-25 15:03:57 +00:00
Alireza 4dd485bd2d fix(seg): orthanc seg (#4883) 2025-03-25 10:59:19 -04:00
ohif-bot 209d81f475 chore(version): Update package versions [skip ci] 2025-03-25 13:37:07 +00:00
ohif-bot 5c319ce4f0 chore(version): version.json [skip ci] 2025-03-25 13:37:00 +00:00
Bill Wallace f6fa509874 fix: Skip showing NaN for average pixel/max value (#4884) 2025-03-25 09:32:36 -04:00
Bill Wallace 8e47112d8e fix: Delete study measurements instead of all measurements (#4882) 2025-03-25 09:32:29 -04:00
Pedro Köhler 165c9dc710 fix: compare series date time (#4887) 2025-03-25 09:31:49 -04:00
ohif-bot 8faa9bf851 chore(version): Update package versions [skip ci] 2025-03-20 19:18:39 +00:00
ohif-bot 246c10da07 chore(version): version.json [skip ci] 2025-03-20 19:18:32 +00:00
Dan Rukas 782a6a0c88 ui(panels): Tool option components and responsive behavior for ROIThreshold (#4876) 2025-03-20 15:13:59 -04:00
ohif-bot 5293253ecd chore(version): Update package versions [skip ci] 2025-03-20 16:10:51 +00:00
ohif-bot 7f86ca1a2b chore(version): version.json [skip ci] 2025-03-20 16:10:45 +00:00
Pedro Köhler 01c3726ced feat: add customization to PanelStudyBrowser dbl click callback (#4871) 2025-03-20 12:06:26 -04:00
ohif-bot f73c485821 chore(version): Update package versions [skip ci] 2025-03-20 13:26:51 +00:00
ohif-bot 30a3965da7 chore(version): version.json [skip ci] 2025-03-20 13:26:45 +00:00
Bill Wallace df8efba82c feat: Group findings table values with custom components/grouping functions (#4712) 2025-03-20 09:22:45 -04:00
ohif-bot 0522d58d9b chore(version): Update package versions [skip ci] 2025-03-19 22:29:06 +00:00
ohif-bot 576ad47ebe chore(version): version.json [skip ci] 2025-03-19 22:29:00 +00:00
Alireza 5f112337b6 feat(segmentation): Enhance Segmentation Tools with Preview and Selection Features (#4870) 2025-03-19 18:24:33 -04:00
ohif-bot 20042f9109 chore(version): Update package versions [skip ci] 2025-03-19 15:57:55 +00:00
ohif-bot d59b9d88fa chore(version): version.json [skip ci] 2025-03-19 15:57:48 +00:00
Sai Shanmukha Narumanchi 7f229d5c23 fix(DicomWebDataSource) : Fix a missing null check in addRetrieveBulkDataNaturalized (#4866) 2025-03-19 11:52:56 -04:00
ohif-bot eac0308275 chore(version): Update package versions [skip ci] 2025-03-18 21:11:23 +00:00
ohif-bot c2f9252aef chore(version): version.json [skip ci] 2025-03-18 21:11:16 +00:00
Pedro Köhler 55d6393a3f feat: overlapping segments (#4849) 2025-03-18 17:07:13 -04:00
ohif-bot 5a6c2aeed9 chore(version): Update package versions [skip ci] 2025-03-18 14:57:07 +00:00
ohif-bot 751919c27b chore(version): version.json [skip ci] 2025-03-18 14:57:00 +00:00
Pedro Köhler fb93ef9219 fix: add back dicom tag browser SQ items (#4869) 2025-03-18 10:52:19 -04:00
ohif-bot a0a3a30361 chore(version): Update package versions [skip ci] 2025-03-18 03:01:26 +00:00
ohif-bot 64b196db20 chore(version): version.json [skip ci] 2025-03-18 03:01:20 +00:00
Dan Rukas c227c5742a ui(panels): PetSUV updates for ui-next and responsive layout (#4864) 2025-03-17 22:57:19 -04:00
ohif-bot 2c75638d1c chore(version): Update package versions [skip ci] 2025-03-18 02:39:10 +00:00
ohif-bot bf27da4174 chore(version): version.json [skip ci] 2025-03-18 02:39:04 +00:00
Alireza ec79b5523e feat(segmentation): segment statistics, labelmap interpolation and segment bidirectional (#4865) 2025-03-17 22:34:34 -04:00
ohif-bot 694f3e9611 chore(version): Update package versions [skip ci] 2025-03-17 15:51:26 +00:00
ohif-bot d33b1ade87 chore(version): version.json [skip ci] 2025-03-17 15:51:19 +00:00
Dan Rukasandsedghi abab3dc7ad ui(components): LayoutSelector recreated for ui-next (#4845)
Co-authored-by: sedghi <ar.sedghi@gmail.com>
2025-03-17 11:47:09 -04:00
Alireza b7fbf04230 fix(metadata): change the default values for cosines (#4863) 2025-03-17 11:44:05 -04:00
ohif-bot ec71721d49 chore(version): Update package versions [skip ci] 2025-03-17 15:09:28 +00:00
ohif-bot a265257569 chore(version): version.json [skip ci] 2025-03-17 15:09:22 +00:00
Dan Rukas 951c21fda8 ui(migrate) Migrate local view to new ui-next components (#4850) 2025-03-17 11:05:11 -04:00
Dan Rukas f916b2f40e ui(components): Adds InputFilter component to ui-next (#4848) 2025-03-17 11:04:42 -04:00
Pedro H. Köhler 841320adfb fix: max call stack exceeded for dicom tag browser (#4855) 2025-03-17 11:03:08 -04:00
ohif-bot 10976c2a48 chore(version): Update package versions [skip ci] 2025-03-17 15:01:31 +00:00
ohif-bot e2ab118217 chore(version): version.json [skip ci] 2025-03-17 15:01:24 +00:00
Alireza 8cd48c7f70 chore: run dependabot (#4862) 2025-03-17 10:56:39 -04:00
ohif-bot fd2f88920b chore(version): Update package versions [skip ci] 2025-03-17 14:25:30 +00:00
ohif-bot fdd33aea0f chore(version): version.json [skip ci] 2025-03-17 14:25:23 +00:00
Alireza ebe6fbaf31 feat: update vtk js (#4858) 2025-03-17 10:21:06 -04:00
ohif-bot bfa76de93b chore(version): Update package versions [skip ci] 2025-03-11 13:58:54 +00:00
ohif-bot 56c7c27ad9 chore(version): version.json [skip ci] 2025-03-11 13:58:47 +00:00
Ibrahim 3f70617c62 feat(viewport-gap): remove viewport gap and update border behavior (#4835) 2025-03-11 09:54:19 -04:00
Ibrahim 1f634bab45 feat(fonts): bundle fonts in OHIF and dont use google cdn (#4821)
[OHI-1681]
2025-03-11 09:50:46 -04:00
ohif-bot f6ad3446ad chore(version): Update package versions [skip ci] 2025-03-11 13:11:42 +00:00
ohif-bot a6059363b1 chore(version): version.json [skip ci] 2025-03-11 13:11:35 +00:00
dependabot[bot] 834be4aaf1 chore(deps): bump prismjs from 1.29.0 to 1.30.0 in /platform/docs (#4838)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-11 09:07:13 -04:00
ohif-bot f15022c6e9 chore(version): Update package versions [skip ci] 2025-03-10 19:38:15 +00:00
ohif-bot a5fb53fbcf chore(version): version.json [skip ci] 2025-03-10 19:38:09 +00:00
dependabot[bot]andsedghi d9349ead6f chore(deps): bump axios from 0.28.1 to 1.8.2 (#4833)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: sedghi <ar.sedghi@gmail.com>
2025-03-10 15:34:10 -04:00
ohif-bot 9d7c2abdef chore(version): Update package versions [skip ci] 2025-03-07 19:19:37 +00:00
ohif-bot 18ef2562a1 chore(version): version.json [skip ci] 2025-03-07 19:19:30 +00:00
Celian-abd 17d2889dc5 fix(tmtv tools): add CircleROIStartEndThreshold to supported tools in TMTV extension (#4828) 2025-03-07 14:15:00 -05:00
ohif-bot 2109b2c1e6 chore(version): Update package versions [skip ci] 2025-03-07 03:23:40 +00:00
ohif-bot 19fb355c50 chore(version): version.json [skip ci] 2025-03-07 03:23:34 +00:00
Alireza cc5cdfb081 feat(toolbox): Refactor Toolbar and Toolbox to enable sections (#4825) 2025-03-06 22:19:47 -05:00
ohif-bot 86c12246ac chore(version): Update package versions [skip ci] 2025-03-06 22:13:48 +00:00
ohif-bot 022313302e chore(version): version.json [skip ci] 2025-03-06 22:13:42 +00:00
Pedro H. Köhler 5a7f7841b2 fix: dicom tag browser row height on toggle (#4832) 2025-03-06 17:09:11 -05:00
ohif-bot 9b6af66859 chore(version): Update package versions [skip ci] 2025-02-28 20:22:03 +00:00
ohif-bot cdfe5c7bef chore(version): version.json [skip ci] 2025-02-28 20:21:57 +00:00
Dan Rukasandsedghi a3b51aa8db ui(components): New Dialog and Modal components for ui-next (#4772)
Co-authored-by: sedghi <ar.sedghi@gmail.com>
2025-02-28 15:17:55 -05:00
ohif-bot 62b4fa3e89 chore(version): Update package versions [skip ci] 2025-02-28 19:21:07 +00:00
ohif-bot c757d69671 chore(version): version.json [skip ci] 2025-02-28 19:20:58 +00:00
Bill Wallaceandsedghi 24567c4e92 fix: Cornerstone3D 3.0 (#4816)
Co-authored-by: sedghi <ar.sedghi@gmail.com>
2025-02-28 14:16:39 -05:00
ohif-bot 62abb3ac47 chore(version): Update package versions [skip ci] 2025-02-28 18:56:06 +00:00
ohif-bot f7a0763000 chore(version): version.json [skip ci] 2025-02-28 18:55:59 +00:00
Bill Wallace d25809eed0 fix: Performance of panel study browser (#4819) 2025-02-28 13:51:43 -05:00
ohif-bot 6b0bb9402f chore(version): Update package versions [skip ci] 2025-02-28 17:30:34 +00:00
ohif-bot 9cbf82167e chore(version): version.json [skip ci] 2025-02-28 17:30:27 +00:00
Sofien-Sellami 7f0cc0f60e feat: separate checks for addWindowLevelActionMenu and addSegmentationOverlay (#4813) 2025-02-28 12:25:52 -05:00
ohif-bot 7d67834159 chore(version): Update package versions [skip ci] 2025-02-26 17:19:13 +00:00
ohif-bot 51f9407d93 chore(version): version.json [skip ci] 2025-02-26 17:19:07 +00:00
Sofien-Sellami fdb073c216 fix: broken activateViewportBeforeInteraction behavior (#4810) 2025-02-26 12:14:56 -05:00
ohif-bot d319d20b0d chore(version): Update package versions [skip ci] 2025-02-26 16:28:51 +00:00
ohif-bot 8a977bb70c chore(version): version.json [skip ci] 2025-02-26 16:28:42 +00:00
Alireza 9233143b9d fix(sr): sr hydration and load was not working, Screenshot Comparison, and Testing (#4814) 2025-02-26 11:23:40 -05:00
ohif-bot 1f5c60bbda chore(version): Update package versions [skip ci] 2025-02-25 22:26:27 +00:00
ohif-bot ca22bac851 chore(version): version.json [skip ci] 2025-02-25 22:26:21 +00:00
Alireza 32fc6f0d4b chore: upgrade cornerstone3D version (#4809) 2025-02-25 17:21:57 -05:00
ohif-bot e2491daff7 chore(version): Update package versions [skip ci] 2025-02-25 21:30:04 +00:00
ohif-bot 7dd0260f86 chore(version): version.json [skip ci] 2025-02-25 21:29:58 +00:00
Yiannis Theocharakis 4b63d48518 feat(Segments Looping) : Fixed looping through Segments of SEG (#4793) 2025-02-25 16:25:52 -05:00
ohif-bot 4ecffe89cb chore(version): Update package versions [skip ci] 2025-02-25 21:24:41 +00:00
ohif-bot 9fb3653281 chore(version): version.json [skip ci] 2025-02-25 21:24:35 +00:00
Rafael Osipov 06660be7c4 docs: Fix broken link to viewer internationalization docs (#4808) 2025-02-25 16:20:19 -05:00
ohif-bot 5d5eafb0f5 chore(version): Update package versions [skip ci] 2025-02-25 18:36:49 +00:00
ohif-bot 0abb62167a chore(version): version.json [skip ci] 2025-02-25 18:36:43 +00:00
Alireza 3e6913b097 feat(hotkeys): Migrate hotkeys to customization service and fix issues with overrides (#4777) 2025-02-25 13:32:30 -05:00
ohif-bot 4559d39a91 chore(version): Update package versions [skip ci] 2025-02-25 16:55:42 +00:00
ohif-bot b7ddbccdff chore(version): version.json [skip ci] 2025-02-25 16:55:37 +00:00
Bill Wallace e480e841bb fix: Changes to address hang/crash on jump to instance (#4679) 2025-02-25 11:51:44 -05:00
Ibrahim afd528b8d7 fix(rt): jump to segment discards the configured width (#4799) 2025-02-25 11:49:16 -05:00
ohif-bot 76da2d86a9 chore(version): Update package versions [skip ci] 2025-02-25 14:20:31 +00:00
ohif-bot 5527c0e8ea chore(version): version.json [skip ci] 2025-02-25 14:20:25 +00:00
Bill Wallace bda98b0bee fix: Delay for all series thumbnails on fetching thumbnail (#4802) 2025-02-25 09:15:45 -05:00
ohif-bot 74d96d378d chore(version): Update package versions [skip ci] 2025-02-23 20:49:40 +00:00
ohif-bot 94496f72b9 chore(version): version.json [skip ci] 2025-02-23 20:49:34 +00:00
Dan Rukas 338e386948 ui(icons): Added New Segmentation Tool Art (#4797) 2025-02-23 15:44:53 -05:00
ohif-bot 0531bee83b chore(version): Update package versions [skip ci] 2025-02-20 14:26:22 +00:00
ohif-bot 6411488cd1 chore(version): version.json [skip ci] 2025-02-20 14:26:16 +00:00
Pedro H. Köhler 55f0b54db1 fix: combine frame instance (#4792) 2025-02-20 09:21:44 -05:00
ohif-bot 2e70f0ffa1 chore(version): Update package versions [skip ci] 2025-02-20 14:01:47 +00:00
ohif-bot 0931356f68 chore(version): version.json [skip ci] 2025-02-20 14:01:41 +00:00
Pedro H. Köhler b7cd0c6027 fix: icon is not defined (#4794) 2025-02-20 08:57:29 -05:00
ohif-bot 8d83c117cf chore(version): Update package versions [skip ci] 2025-02-19 14:21:08 +00:00
ohif-bot bd93a6f428 chore(version): version.json [skip ci] 2025-02-19 14:21:01 +00:00
Ceciliadrc 125f11fc73 fix(button): fix for className (#4604) 2025-02-19 09:16:22 -05:00
ohif-bot ce3bc8962d chore(version): Update package versions [skip ci] 2025-02-18 23:24:46 +00:00
ohif-bot 0d8820d941 chore(version): version.json [skip ci] 2025-02-18 23:24:39 +00:00
Yiannis Theocharakis c61dbba8d0 fix(Segments Looping) : Fixed looping through Segments of RTSTRUCT using Left/Right viewport buttons (#4783) 2025-02-18 18:20:26 -05:00
Bill Wallace 2410c6a509 fix: cache thumbnail in display set (#4782) 2025-02-18 18:19:09 -05:00
ohif-bot d26c9551b6 chore(version): Update package versions [skip ci] 2025-02-18 16:41:10 +00:00
ohif-bot b6afba12a5 chore(version): version.json [skip ci] 2025-02-18 16:41:04 +00:00
Pedro H. Köhler 4e16099ad3 fix: lodash dependencies (#4791) 2025-02-18 11:36:26 -05:00
ohif-bot 887a892a5b chore(version): Update package versions [skip ci] 2025-02-18 15:34:40 +00:00
ohif-bot db4d94c3fe chore(version): version.json [skip ci] 2025-02-18 15:34:31 +00:00
Pedro H. Köhler 0b5836ca1a feat: improve dicom tag browser with nested rows (#4451) 2025-02-18 10:29:53 -05:00
ohif-bot 8b969eb1e9 chore(version): Update package versions [skip ci] 2025-02-18 14:12:35 +00:00
ohif-bot ac30be6038 chore(version): version.json [skip ci] 2025-02-18 14:12:28 +00:00
Alireza d8a6e79df0 fix: depandabot (#4786) 2025-02-18 09:08:10 -05:00
Pedro H. Köhler 5712e91ca1 fix: right panel for the create mode cli command (#4788) 2025-02-18 09:07:17 -05:00
Pedro H. Köhler a3b1251386 docs: fix yarn version (#4790) 2025-02-18 09:04:35 -05:00
ohif-bot 2fbaa11cf1 chore(version): Update package versions [skip ci] 2025-02-13 17:31:17 +00:00
ohif-bot 8fe7b78e54 chore(version): version.json [skip ci] 2025-02-13 17:31:11 +00:00
Yiannis Theocharakis d9a3ba7990 fix(Frame View) : Fixed synchronization for Frame View Hanging Protocol (#4780) 2025-02-13 12:27:01 -05:00
ohif-bot 2ca9f2b0cc chore(version): Update package versions [skip ci] 2025-02-11 17:44:43 +00:00
ohif-bot 559f248d1f chore(version): version.json [skip ci] 2025-02-11 17:44:37 +00:00
Pedro H. Köhler 084a10f783 feat: add viewport overlays to microscopy mode (#4776) 2025-02-11 12:39:45 -05:00
ohif-bot 5043ea3260 chore(version): Update package versions [skip ci] 2025-02-04 23:37:42 +00:00
ohif-bot e0ec1011cf chore(version): version.json [skip ci] 2025-02-04 23:37:36 +00:00
devin-ai-integration[bot]andAlireza Sedghi bbf1a19676 fix: add commandsManager to MoreDropdownMenu onClick props (#4765)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Alireza Sedghi <ar.sedghi@gmail.com>
2025-02-04 18:33:45 -05:00
ohif-bot 9a3d832cc2 chore(version): Update package versions [skip ci] 2025-02-04 17:29:26 +00:00
ohif-bot e373e22481 chore(version): version.json [skip ci] 2025-02-04 17:29:19 +00:00
Alireza 149d6d049c fix(core): Address 3D reconstruction and Android compatibility issues and clean up 4D data mode (#4762) 2025-02-04 12:25:10 -05:00
ohif-bot 4d06a7d286 chore(version): Update package versions [skip ci] 2025-02-04 14:10:16 +00:00
ohif-bot a7e4204cea chore(version): version.json [skip ci] 2025-02-04 14:10:10 +00:00
Joost van Griethuysen 8996df80e4 fix: changing colormap for second volume in fusion viewport (#4746) 2025-02-04 09:05:55 -05:00
ohif-bot d5dbe20256 chore(version): Update package versions [skip ci] 2025-02-04 13:55:40 +00:00
ohif-bot 105fd7a468 chore(version): version.json [skip ci] 2025-02-04 13:55:34 +00:00
Abhijith Sb 2c183aa4a7 feat(ui): Add support for Custom Modal component in Modal Service (#4752) 2025-02-04 08:51:23 -05:00
ohif-bot 8788dae6a4 chore(version): Update package versions [skip ci] 2025-02-04 13:50:59 +00:00
ohif-bot 6937129991 chore(version): version.json [skip ci] 2025-02-04 13:50:54 +00:00
nithin-trenser 8acbd760d8 feat(ui): customization option for viewport notification (#4638) 2025-02-04 08:46:39 -05:00
ohif-bot c65e7f57cc chore(version): Update package versions [skip ci] 2025-02-04 13:36:09 +00:00
ohif-bot 5e09b405f4 chore(version): version.json [skip ci] 2025-02-04 13:36:04 +00:00
Joe Boccanfuso 12d3db2dbc fix(context-menu): Fixing regression introduced by PR #4727 (#4760) 2025-02-04 08:31:40 -05:00
ohif-bot ae9a238289 chore(version): Update package versions [skip ci] 2025-02-03 15:35:58 +00:00
ohif-bot 691accacd9 chore(version): version.json [skip ci] 2025-02-03 15:35:53 +00:00
arul-trenser e035ef1dcc fix(measurement label auto-completion): Customization of measurement label auto-completion fails for measurements following arrow annotations. (#4739) 2025-02-03 10:31:41 -05:00
ohif-bot 43aa20c372 chore(version): Update package versions [skip ci] 2025-02-03 14:33:26 +00:00
ohif-bot 860f76586a chore(version): version.json [skip ci] 2025-02-03 14:33:20 +00:00
Ibrahim 9b8e5cfd1a fix(store-segmentation): storing segmentations was hitting the wrong command resulting in an undefined datasource (#4755)
[OHI-1445]
2025-02-03 09:28:58 -05:00
ohif-bot 613401e043 chore(version): Update package versions [skip ci] 2025-02-03 14:22:28 +00:00
ohif-bot 47b9a06f74 chore(version): version.json [skip ci] 2025-02-03 14:22:22 +00:00
f15eb44b4c feat: Add customization support for more UI components (#4634)
Co-authored-by: ashikcn <ashik@L1140.trenser.com>
Co-authored-by: Devu Jayalekshmi <devu.jayalekshmi@trenser.com>
Co-authored-by: Arul Mozhi <arul.mozhi@trenser.com>
2025-02-03 09:18:00 -05:00
ohif-bot 8f79ebafd9 chore(version): Update package versions [skip ci] 2025-01-31 15:12:46 +00:00
ohif-bot a3c7c2c3da chore(version): version.json [skip ci] 2025-01-31 15:12:40 +00:00
Celian-abd 32017d15a4 fix: Remove non-functional Tailwind class for SegmentationPanel (#4745) 2025-01-31 10:07:55 -05:00
ohif-bot e3967e62f2 chore(version): Update package versions [skip ci] 2025-01-31 14:47:57 +00:00
ohif-bot 09f9024728 chore(version): version.json [skip ci] 2025-01-31 14:47:51 +00:00
Alireza 0cb6106d0f fix: publish dependency (#4753) 2025-01-31 09:43:35 -05:00
ohif-bot 4dc26ae603 chore(version): Update package versions [skip ci] 2025-01-31 13:58:58 +00:00
ohif-bot 9850f92b8a chore(version): version.json [skip ci] 2025-01-31 13:58:52 +00:00
Gas Giantandscouser f10683c667 fix: typo in pet_series_module (#4748)
Co-authored-by: scouser <sharukh@Sharukhs-MacBook-Pro.local>
2025-01-31 08:54:45 -05:00
ohif-bot 2bfc790870 chore(version): Update package versions [skip ci] 2025-01-29 18:41:36 +00:00
ohif-bot 3f08a76aac chore(version): version.json [skip ci] 2025-01-29 18:41:29 +00:00
Dan Rukasandsedghi a8d84443a9 ui(components): migrates to new toolbar ui components and Numeric for Input InputRange and InputDoubleRange (#4728)
Co-authored-by: sedghi <ar.sedghi@gmail.com>
2025-01-29 13:36:52 -05:00
ohif-bot ac5ae4a29e chore(version): Update package versions [skip ci] 2025-01-29 14:19:36 +00:00
ohif-bot 6d9c701fa9 chore(version): version.json [skip ci] 2025-01-29 14:19:30 +00:00
Jaseel-trenser 054b262e9c feat(customization): enable custom onDropHandler for viewportGrid (#4641) 2025-01-29 09:14:50 -05:00
ohif-bot 3dac0f9e73 chore(version): Update package versions [skip ci] 2025-01-28 20:04:13 +00:00
ohif-bot d62c32c898 chore(version): version.json [skip ci] 2025-01-28 20:04:07 +00:00
Alireza d554f02f7c fix(dependencies): Update dcmjs library and improve documentation links (#4741) 2025-01-28 14:59:59 -05:00
ohif-bot ff6dd3a152 chore(version): Update package versions [skip ci] 2025-01-28 16:38:07 +00:00
ohif-bot a22818c1f4 chore(version): version.json [skip ci] 2025-01-28 16:38:01 +00:00
Joe Boccanfuso 6abb095b8a feat(side-panels): Added resize handle interactive feedback for side panels (#4734) 2025-01-28 11:33:53 -05:00
ohif-bot fe658c6392 chore(version): Update package versions [skip ci] 2025-01-28 13:59:39 +00:00
ohif-bot 70c94e8d5a chore(version): version.json [skip ci] 2025-01-28 13:59:33 +00:00
Devu-trenser 5addf51c45 feat(windowLevelActionMenu) customization support for windowLevelActionMenu (#4725) 2025-01-28 08:54:58 -05:00
ashik-trenserandashikcn 8e3e2085d4 fix: for initialImageIndex mismatch issue for loading SR after disabling prompts (#4732)
Co-authored-by: ashikcn <ashik@L1140.trenser.com>
2025-01-28 08:53:42 -05:00
ohif-bot 078a72996f chore(version): Update package versions [skip ci] 2025-01-27 16:59:04 +00:00
ohif-bot dc14a48fd9 chore(version): version.json [skip ci] 2025-01-27 16:58:57 +00:00
arul-trenser 61699d00b6 fix(context menu): Context menus for edge-proximate measurements are partially obscured. (#4727) 2025-01-27 11:54:29 -05:00
ohif-bot eb854ac2cb chore(version): Update package versions [skip ci] 2025-01-27 16:24:18 +00:00
ohif-bot 6c42f269a7 chore(version): version.json [skip ci] 2025-01-27 16:24:12 +00:00
Ibrahim ac6e674b4d feat(static-wado): add support for case-insensitive searching (#4603)
[OHI-1357]
2025-01-27 11:20:00 -05:00
ohif-bot 316cf0a7bd chore(version): Update package versions [skip ci] 2025-01-27 14:27:48 +00:00
ohif-bot c011236060 chore(version): version.json [skip ci] 2025-01-27 14:27:43 +00:00
dependabot[bot] 2ed7a1adfa chore(deps): bump store2 from 2.14.3 to 2.14.4 (#4737)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-27 09:23:38 -05:00
ohif-bot 492150478b chore(version): Update package versions [skip ci] 2025-01-24 15:27:16 +00:00
ohif-bot cb03077d37 chore(version): version.json [skip ci] 2025-01-24 15:27:10 +00:00
Alireza 28fb921089 fix(docs): image in customization (#4735) 2025-01-24 10:22:29 -05:00
ohif-bot bc175d68b6 chore(version): Update package versions [skip ci] 2025-01-24 13:46:27 +00:00
ohif-bot 884020f143 chore(version): version.json [skip ci] 2025-01-24 13:46:21 +00:00
Gas Giantandscouser d6f0092a32 feat: delete active annotation using backspace/delete key (#4722)
Co-authored-by: scouser <sharukh@Sharukhs-MacBook-Pro.local>
2025-01-24 08:42:00 -05:00
ohif-bot 01fa5d83a6 chore(version): Update package versions [skip ci] 2025-01-23 19:28:43 +00:00
ohif-bot 625919d4e4 chore(version): version.json [skip ci] 2025-01-23 19:28:36 +00:00
Alireza 55ad8efbab feat(customization): new customization service api (#4688) 2025-01-23 14:24:13 -05:00
ohif-bot fab52808cb chore(version): Update package versions [skip ci] 2025-01-23 19:18:02 +00:00
ohif-bot f5ad0de2d2 chore(version): version.json [skip ci] 2025-01-23 19:17:56 +00:00
Ibrahim d9abc3da8d feat(panels): responsive thumbnails based on panel size (#4723) 2025-01-23 14:13:44 -05:00
ohif-bot 9d07705a1f chore(version): Update package versions [skip ci] 2025-01-22 16:40:54 +00:00
ohif-bot 0966f62310 chore(version): version.json [skip ci] 2025-01-22 16:40:48 +00:00
Alireza f00d18292f fix(seg): sphere scissor on stack and cpu rendering reset properties was broken (#4721) 2025-01-22 11:36:45 -05:00
ohif-bot 3628ad84c8 chore(version): Update package versions [skip ci] 2025-01-21 16:25:19 +00:00
ohif-bot b21287e908 chore(version): version.json [skip ci] 2025-01-21 16:25:13 +00:00
Joe Boccanfuso d90a4cfb16 feat(resizable-side-panels): Make the left and right side panels (optionally) resizable. (#4672) 2025-01-21 11:20:34 -05:00
ohif-bot a518854a4c chore(version): Update package versions [skip ci] 2025-01-21 14:38:46 +00:00
ohif-bot 28f86f3cd6 chore(version): version.json [skip ci] 2025-01-21 14:38:40 +00:00
Alireza cf97fa9b7b fix(ui): Update dependencies and add missing icons (#4699) 2025-01-21 09:34:19 -05:00
ohif-bot ad7ade28ea chore(version): Update package versions [skip ci] 2025-01-21 14:12:03 +00:00
ohif-bot 9aed7b8486 chore(version): version.json [skip ci] 2025-01-21 14:11:57 +00:00
Jaseel-trenser 8aedb2ec54 fix: Inconsistent Handling of Patient Name Tag (#4703) 2025-01-21 09:07:30 -05:00
ohif-bot cbe926f52b chore(version): Update package versions [skip ci] 2025-01-20 14:17:53 +00:00
ohif-bot 855648f41f chore(version): version.json [skip ci] 2025-01-20 14:17:47 +00:00
Bez bf587070cc fix(rotation): support cycling rotateViewportCCW (#4533) 2025-01-20 09:13:43 -05:00
ohif-bot 953f36ab4c chore(version): Update package versions [skip ci] 2025-01-20 14:11:21 +00:00
ohif-bot 0911e3c0d1 chore(version): version.json [skip ci] 2025-01-20 14:11:15 +00:00
Bill Wallace 464148ece6 fix(hp): Display set should allow remembered updates (#4707) 2025-01-20 09:06:28 -05:00
ohif-bot 544dc543d9 chore(version): Update package versions [skip ci] 2025-01-17 21:48:05 +00:00
ohif-bot f5dcf25d98 chore(version): version.json [skip ci] 2025-01-17 21:47:59 +00:00
Bill Wallace 7734296d1d docs: Documentation on how to set default configuration to search for today (#4708) 2025-01-17 16:43:51 -05:00
ohif-bot 9e047784d1 chore(version): Update package versions [skip ci] 2025-01-16 16:47:32 +00:00
ohif-bot df96a4992c chore(version): version.json [skip ci] 2025-01-16 16:47:26 +00:00
Pedro H. Köhler 19931614dc fix: context menu icon (#4696) 2025-01-16 11:42:58 -05:00
ohif-bot ef477c29c5 chore(version): Update package versions [skip ci] 2025-01-16 03:33:26 +00:00
ohif-bot 6c8ebc1d5a chore(version): version.json [skip ci] 2025-01-16 03:33:19 +00:00
ec4b5a6876 fix(multiframe): handling proxies properly (#4693)
Co-authored-by: Bill Wallace <wayfarer3130@gmail.com>
Co-authored-by: Chao Sui <chao.sui@curvebeamai.com>
2025-01-15 22:29:06 -05:00
ohif-bot 9f8df95f3e chore(version): Update package versions [skip ci] 2025-01-15 20:21:31 +00:00
ohif-bot c718b20659 chore(version): version.json [skip ci] 2025-01-15 20:21:25 +00:00
Bill Wallaceandsedghi cded082617 fix: Having sop instance in a per-frame or shared attribute breaks load (#4560)
Co-authored-by: sedghi <ar.sedghi@gmail.com>
2025-01-15 15:16:50 -05:00
ohif-bot 27c040fdda chore(version): Update package versions [skip ci] 2025-01-14 14:07:59 +00:00
ohif-bot e923bf2bc5 chore(version): version.json [skip ci] 2025-01-14 14:07:53 +00:00
Pedro H. Köhler 2d611d06ed fix: cs dicom sr commands module (#4683) 2025-01-14 09:03:04 -05:00
Alireza c901a847af fix: bugs after multimonitor (#4680) 2025-01-13 18:32:54 -05:00
ohif-bot 7662157cbf chore(version): Update package versions [skip ci] 2025-01-13 15:54:04 +00:00
ohif-bot 161c84d875 chore(version): version.json [skip ci] 2025-01-13 15:53:58 +00:00
Bill Wallace 07c628e689 feat(multimonitor): Add simple multi-monitor support to open another study(#4178) 2025-01-13 10:49:08 -05:00
ohif-bot a4a6de07f4 chore(version): Update package versions [skip ci] 2025-01-13 14:40:08 +00:00
ohif-bot daf18f3e6b chore(version): version.json [skip ci] 2025-01-13 14:40:01 +00:00
Devu-trenser 9832dbe653 fix(toolbarService): All header tools are enabled in volume3D viewport (#4677) 2025-01-13 09:35:44 -05:00
ohif-bot f4df631a4a chore(version): Update package versions [skip ci] 2025-01-13 14:04:38 +00:00
ohif-bot bdfaa35f7f chore(version): version.json [skip ci] 2025-01-13 14:04:31 +00:00
Joeycho 9b04020a87 docs: migration docs typo (3.8 -> 3.9, useViewportGridStore()) (#4675) 2025-01-13 09:00:13 -05:00
ohif-bot 7a13c28eb3 chore(version): Update package versions [skip ci] 2025-01-10 20:09:59 +00:00
ohif-bot b9fb0ce96f chore(version): version.json [skip ci] 2025-01-10 20:09:53 +00:00
Alireza d4a4267429 feat(dev): move to rsbuild for dev - faster (#4674) 2025-01-10 15:05:35 -05:00
ohif-bot 847f1da8e0 chore(version): Update package versions [skip ci] 2025-01-10 17:05:53 +00:00
ohif-bot 71ccbe87c1 chore(version): version.json [skip ci] 2025-01-10 17:05:47 +00:00
Ibrahim a2d34c97ed fix(docker): run compression regardless of APP_CONFIG being present ( in cases such as volume mount) (#4673) 2025-01-10 12:01:15 -05:00
ohif-bot ee76912fed chore(version): Update package versions [skip ci] 2025-01-10 15:11:20 +00:00
ohif-bot e94e8439ad chore(version): version.json [skip ci] 2025-01-10 15:11:14 +00:00
Ibrahim 01924b8bf2 fix(icons): icons missing for volume presets and others (#4671) 2025-01-10 10:06:34 -05:00
ohif-bot 7d22480036 chore(version): Update package versions [skip ci] 2025-01-10 04:02:04 +00:00
ohif-bot 4e70ee7894 chore(version): version.json [skip ci] 2025-01-10 04:01:57 +00:00
Joeycho 64a72774a8 chore: delete unused and duplicate function, it exists under /utils (#4657) 2025-01-09 22:57:32 -05:00
ohif-bot 1b56b48d7b chore(version): Update package versions [skip ci] 2025-01-10 03:11:43 +00:00
ohif-bot 41e870b7db chore(version): version.json [skip ci] 2025-01-10 03:11:37 +00:00
Bill Wallace ebbc37d291 fix: orthanc datasource dev (#4663) 2025-01-09 22:07:30 -05:00
ohif-bot 29b9dc58ba chore(version): Update package versions [skip ci] 2025-01-10 03:06:19 +00:00
ohif-bot 409e06d0c6 chore(version): version.json [skip ci] 2025-01-10 03:06:13 +00:00
Bill Wallace 82440e88d5 feat: Start using group filtering to define measurements table layout (#4501) 2025-01-09 22:01:42 -05:00
ohif-bot bc73de9710 chore(version): Update package versions [skip ci] 2025-01-09 16:22:12 +00:00
ohif-bot ba8f8ba2d6 chore(version): version.json [skip ci] 2025-01-09 16:22:04 +00:00
Salim Kanoun 8e2c607904 fix: Execute HP onProtocolEnter callback after HPservice.run( (#4589) 2025-01-09 11:15:57 -05:00
ohif-bot 98f4ac2e75 chore(version): Update package versions [skip ci] 2025-01-09 15:56:12 +00:00
ohif-bot 192d8e69a9 chore(version): version.json [skip ci] 2025-01-09 15:56:05 +00:00
Salim Kanoun aef68d18b8 fix: Make StudyInstanceUID optional to retrieve a Series from DicomMetadataStore (#4644) 2025-01-09 10:51:09 -05:00
ohif-bot 2dd9b893ad chore(version): Update package versions [skip ci] 2025-01-09 14:32:47 +00:00
ohif-bot 2ebd03de07 chore(version): version.json [skip ci] 2025-01-09 14:32:41 +00:00
Bill Wallace e0088ec918 fix: Inconsistencies and update the style setting on load for embedded styles from codingValues (#4599) 2025-01-09 09:28:02 -05:00
ohif-bot 3c848c6d34 chore(version): Update package versions [skip ci] 2025-01-08 20:35:07 +00:00
ohif-bot c931b63d5e chore(version): version.json [skip ci] 2025-01-08 20:35:01 +00:00
Alireza 38edde5e37 fix(circleci): Update workflow dependencies for Docker publishing stages (#4662) 2025-01-08 15:30:24 -05:00
Alireza 66eebbed0f fix(circleci): Update workflow dependencies for Docker publishing stages (#4661) 2025-01-08 15:20:16 -05:00
ohif-bot 8ecbca700c chore(version): Update package versions [skip ci] 2025-01-08 20:02:34 +00:00
ohif-bot 1223dd00e1 chore(version): version.json [skip ci] 2025-01-08 20:02:28 +00:00
Alireza 3c6a5ef69e fix(circleci): Update workflow dependencies for Docker publishing stages (#4660) 2025-01-08 14:57:50 -05:00
ohif-bot dd5251ac95 chore(version): Update package versions [skip ci] 2025-01-08 19:43:13 +00:00
ohif-bot 71c0c18a8a chore(version): version.json [skip ci] 2025-01-08 19:43:07 +00:00
Alireza 5f6f528fbf fix(Dockerfile): Clear bun package manager cache before installation (#4659) 2025-01-08 14:38:39 -05:00
ohif-bot 60547b507f chore(version): Update package versions [skip ci] 2025-01-08 19:28:18 +00:00
ohif-bot f8b7687f94 chore(version): version.json [skip ci] 2025-01-08 19:28:12 +00:00
Dan Rukasandsedghi 230ae9758b ui(components): Viewport components added to ui-next (#4649)
Co-authored-by: sedghi <ar.sedghi@gmail.com>
2025-01-08 14:23:34 -05:00
ohif-bot 47de38f3ff chore(version): Update package versions [skip ci] 2025-01-08 19:18:23 +00:00
ohif-bot 5b454472e4 chore(version): version.json [skip ci] 2025-01-08 19:18:16 +00:00
devin-ai-integration[bot] 2f5076ece8 fix: Convert Rows and Columns to numbers before comparison (#4654) (#4656)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2025-01-08 14:13:28 -05:00
ohif-bot 401d5dcedd chore(version): Update package versions [skip ci] 2025-01-08 18:15:53 +00:00
ohif-bot 464976f035 chore(version): version.json [skip ci] 2025-01-08 18:15:47 +00:00
Dan Rukasandsedghi b54f2075ec ui(component): Tooltip component ui-next (#4635)
Co-authored-by: sedghi <ar.sedghi@gmail.com>
2025-01-08 13:10:57 -05:00
ohif-bot 03ad79eaed chore(version): Update package versions [skip ci] 2025-01-08 14:46:22 +00:00
ohif-bot e993b7f9e1 chore(version): version.json [skip ci] 2025-01-08 14:46:15 +00:00
Alireza 8e12021939 fix(docker): make multiarch work (#4655) 2025-01-08 09:41:47 -05:00
ohif-bot 8ad5d559bb chore(version): Update package versions [skip ci] 2025-01-08 14:05:29 +00:00
ohif-bot 20a4daa54e chore(version): version.json [skip ci] 2025-01-08 14:05:22 +00:00
Alireza 836e67a6ab fix(docker): publish manifest for multiarch and update cs3d (#4650) 2025-01-08 09:00:22 -05:00
961 changed files with 91753 additions and 21688 deletions

No files matched your search

+119 -31
View File
@@ -2,7 +2,7 @@ version: 2.1
orbs:
codecov: codecov/codecov@1.0.5
cypress: cypress-io/cypress@3.3.1
cypress: cypress-io/cypress@3.4.2
defaults: &defaults
docker:
@@ -208,7 +208,7 @@ jobs:
- setup_remote_docker:
docker_layer_caching: false
- run:
name: Build and push Docker image from the release branch
name: Build Docker image for amd64
command: |
# This file will exist if a new version was published by
# our command in the previous job.
@@ -222,12 +222,16 @@ jobs:
export IMAGE_VERSION_FULL=v$IMAGE_VERSION
echo $IMAGE_VERSION
echo $IMAGE_VERSION_FULL
# Build our image, auth, and push
docker build --tag ohif/app:$IMAGE_VERSION_FULL --tag ohif/app:latest .
# Build our amd64 image, auth, and push
docker build --platform linux/amd64 --tag ohif/app:$IMAGE_VERSION_FULL-amd64 --tag ohif/app:latest-amd64 .
echo $DOCKER_PWD | docker login -u $DOCKER_LOGIN --password-stdin
docker push ohif/app:$IMAGE_VERSION_FULL
docker push ohif/app:latest
docker push ohif/app:$IMAGE_VERSION_FULL-amd64
docker push ohif/app:latest-amd64
fi
- persist_to_workspace:
root: ~/repo
paths:
- .
DOCKER_RELEASE_PUBLISH_ARM:
<<: *defaults
@@ -238,7 +242,7 @@ jobs:
- setup_remote_docker:
docker_layer_caching: false
- run:
name: Build and push ARM64 Docker image from the release branch
name: Build Docker image for arm64 (Release)
command: |
# This file will exist if a new version was published by
# our command in the previous job.
@@ -252,12 +256,16 @@ jobs:
export IMAGE_VERSION_FULL=v$IMAGE_VERSION
echo $IMAGE_VERSION
echo $IMAGE_VERSION_FULL
# Build our ARM64 image, auth, and push
docker build --tag ohif/app:$IMAGE_VERSION_FULL-arm64 --tag ohif/app:latest-arm64 .
# Build our arm64 image, auth, and push
docker build --platform linux/arm64 --tag ohif/app:$IMAGE_VERSION_FULL-arm64 --tag ohif/app:latest-arm64 .
echo $DOCKER_PWD | docker login -u $DOCKER_LOGIN --password-stdin
docker push ohif/app:$IMAGE_VERSION_FULL-arm64
docker push ohif/app:latest-arm64
fi
- persist_to_workspace:
root: ~/repo
paths:
- .
DOCKER_BETA_PUBLISH:
<<: *defaults
@@ -268,7 +276,7 @@ jobs:
- setup_remote_docker:
docker_layer_caching: false
- run:
name: Build and push Docker image from the master branch (beta releases)
name: Build Docker image for amd64 (Beta)
command: |
echo $(ls -l)
@@ -280,19 +288,17 @@ jobs:
else
echo "Building and pushing Docker image from the master branch (beta releases)"
rm -f ./.npmrc
# Set our version number using vars
export IMAGE_VERSION=$(cat version.txt)
export IMAGE_VERSION_FULL=v$IMAGE_VERSION
echo $IMAGE_VERSION
echo $IMAGE_VERSION_FULL
# Build our image, auth, and push
echo "starting docker build"
docker build --tag ohif/app:$IMAGE_VERSION_FULL .
# Build our amd64 image, auth, and push
docker build --platform linux/amd64 --tag ohif/app:$IMAGE_VERSION_FULL-amd64 --tag ohif/app:latest-beta-amd64 .
echo $DOCKER_PWD | docker login -u $DOCKER_LOGIN --password-stdin
echo "starting docker push"
docker push ohif/app:$IMAGE_VERSION_FULL
docker push ohif/app:$IMAGE_VERSION_FULL-amd64
docker push ohif/app:latest-beta-amd64
fi
DOCKER_BETA_PUBLISH_ARM:
@@ -304,7 +310,7 @@ jobs:
- setup_remote_docker:
docker_layer_caching: false
- run:
name: Build and push ARM64 Docker image from the master branch (beta releases)
name: Build Docker image for arm64 (Beta)
command: |
echo $(ls -l)
@@ -321,13 +327,11 @@ jobs:
export IMAGE_VERSION_FULL=v$IMAGE_VERSION
echo $IMAGE_VERSION
echo $IMAGE_VERSION_FULL
# Build our ARM64 image, auth, and push
echo "starting docker build"
docker build --tag ohif/app:$IMAGE_VERSION_FULL-arm64 .
# Build our arm64 image, auth, and push
docker build --platform linux/arm64 --tag ohif/app:$IMAGE_VERSION_FULL-arm64 --tag ohif/app:latest-beta-arm64 .
echo $DOCKER_PWD | docker login -u $DOCKER_LOGIN --password-stdin
echo "starting docker push"
docker push ohif/app:$IMAGE_VERSION_FULL-arm64
docker push ohif/app:latest-beta-arm64
fi
CYPRESS:
@@ -337,8 +341,10 @@ jobs:
steps:
- install_bun
- run:
name: Install Xvfb
command: sudo apt-get update && sudo apt-get install -y xvfb
name: Install System Dependencies
command: |
sudo apt-get update
sudo apt-get install -y xvfb libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6
- run:
name: Start Xvfb
command: Xvfb :99 -screen 0 1920x1080x24 &
@@ -347,15 +353,91 @@ jobs:
name: Export Display Variable
command: export DISPLAY=:99
- cypress/install:
install-browsers: true
install-command: bun install --no-save
package-manager: yarn
- cypress/run-tests:
cypress-command:
npx wait-on@latest http://localhost:3000 && cd platform/app && npx cypress run --record
--browser chrome --parallel
cypress-command: |
npx wait-on@latest http://localhost:3000 && cd platform/app && npx cypress run --record --parallel
start-command: bun run test:data && bun run test:e2e:serve
DOCKER_MULTIARCH_MANIFEST:
<<: *defaults
resource_class: large
steps:
- attach_workspace:
at: ~/repo
- setup_remote_docker:
docker_layer_caching: false
- run:
name: Create and push multi-architecture manifest (Release)
command: |
# This file will exist if a new version was published by
# our command in the previous job.
if [[ ! -e version.txt ]]; then
exit 0
else
echo "Building and pushing multi-architecture manifest from the master branch (release releases)"
rm -f ./.npmrc
# Set our version number using vars
export IMAGE_VERSION=$(cat version.txt)
export IMAGE_VERSION_FULL=v$IMAGE_VERSION
echo $IMAGE_VERSION
echo $IMAGE_VERSION_FULL
echo $DOCKER_PWD | docker login -u $DOCKER_LOGIN --password-stdin
# Create and push manifest for specific version
docker manifest create ohif/app:$IMAGE_VERSION_FULL \
--amend ohif/app:$IMAGE_VERSION_FULL-amd64 \
--amend ohif/app:$IMAGE_VERSION_FULL-arm64
docker manifest push ohif/app:$IMAGE_VERSION_FULL
# Create and push manifest for "latest" tag
docker manifest create ohif/app:latest \
--amend ohif/app:latest-amd64 \
--amend ohif/app:latest-arm64
docker manifest push ohif/app:latest
fi
DOCKER_BETA_MULTIARCH_MANIFEST:
<<: *defaults
resource_class: large
steps:
- attach_workspace:
at: ~/repo
- setup_remote_docker:
docker_layer_caching: false
- run:
name: Create and push multi-architecture manifest (Beta)
command: |
echo $(ls -l)
# This file will exist if a new version was published by
# our command in the previous job.
if [[ ! -e version.txt ]]; then
exit 0
else
echo "Building and pushing multi-architecture manifest from the master branch (beta releases)"
rm -f ./.npmrc
# Set our version number using vars
export IMAGE_VERSION=$(cat version.txt)
export IMAGE_VERSION_FULL=v$IMAGE_VERSION
echo $IMAGE_VERSION
echo $IMAGE_VERSION_FULL
echo $DOCKER_PWD | docker login -u $DOCKER_LOGIN --password-stdin
# Create and push manifest for specific beta version
docker manifest create ohif/app:$IMAGE_VERSION_FULL \
--amend ohif/app:$IMAGE_VERSION_FULL-amd64 \
--amend ohif/app:$IMAGE_VERSION_FULL-arm64
docker manifest push ohif/app:$IMAGE_VERSION_FULL
# Create and push manifest for "latest-beta" tag
docker manifest create ohif/app:latest-beta \
--amend ohif/app:latest-beta-amd64 \
--amend ohif/app:latest-beta-arm64
docker manifest push ohif/app:latest-beta
fi
workflows:
PR_CHECKS:
jobs:
@@ -383,7 +465,10 @@ workflows:
- NPM_PUBLISH
- DOCKER_BETA_PUBLISH_ARM:
requires:
- NPM_PUBLISH
- DOCKER_BETA_PUBLISH
- DOCKER_BETA_MULTIARCH_MANIFEST:
requires:
- DOCKER_BETA_PUBLISH_ARM
# viewer.ohif.org
DEPLOY_RELEASE:
@@ -404,4 +489,7 @@ workflows:
- NPM_PUBLISH
- DOCKER_RELEASE_PUBLISH_ARM:
requires:
- NPM_PUBLISH
- DOCKER_RELEASE_PUBLISH
- DOCKER_MULTIARCH_MANIFEST:
requires:
- DOCKER_RELEASE_PUBLISH_ARM
+1 -1
View File
@@ -7,7 +7,7 @@ server {
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
try_files $uri $uri/ ${PUBLIC_URL}index.html;
add_header Cross-Origin-Resource-Policy same-origin;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
+21 -12
View File
@@ -2,25 +2,34 @@
if [ -n "$SSL_PORT" ]
then
envsubst '${SSL_PORT}:${PORT}' < /usr/src/default.ssl.conf.template > /etc/nginx/conf.d/default.conf
envsubst '${SSL_PORT}:${PORT}' < /usr/src/default.ssl.conf.template | envsubst '${PUBLIC_URL}' > /etc/nginx/conf.d/default.conf
else
envsubst '${PORT}' < /usr/src/default.conf.template > /etc/nginx/conf.d/default.conf
envsubst '${PORT}:${PUBLIC_URL}' < /usr/src/default.conf.template > /etc/nginx/conf.d/default.conf
fi
if [ -n "$APP_CONFIG" ] ; then
if [ -n "$APP_CONFIG" ]; then
echo "$APP_CONFIG" > /usr/share/nginx/html${PUBLIC_URL}app-config.js
# Removes the old compressed app-config file, then compresses the replacement
# and finally creates a new empty file so that gunzip works correctly.
# This code is correct despite the AI warning otherwise about order of create/delete
rm /usr/share/nginx/html${PUBLIC_URL}app-config.js.gz
gzip /usr/share/nginx/html${PUBLIC_URL}app-config.js
touch /usr/share/nginx/html${PUBLIC_URL}app-config.js
echo "Using custom APP_CONFIG environment variable"
else
echo "Not using custom APP_CONFIG"
fi
if [ ! -n "$APP_CONFIG" ]
then
echo "Not using custom app config"
if [ -f /usr/share/nginx/html${PUBLIC_URL}app-config.js ]; then
if [ -s /usr/share/nginx/html${PUBLIC_URL}app-config.js ]; then
echo "Detected non-empty app-config.js. Ensuring .gz file is updated..."
rm -f /usr/share/nginx/html${PUBLIC_URL}app-config.js.gz
gzip /usr/share/nginx/html${PUBLIC_URL}app-config.js
touch /usr/share/nginx/html${PUBLIC_URL}app-config.js
echo "Compressed app-config.js to app-config.js.gz"
else
echo "app-config.js is empty. Skipping compression."
fi
else
echo "No app-config.js file found. Skipping compression."
fi
if [ -n "$CLIENT_ID" ] || [ -n "$HEALTHCARE_API_ENDPOINT" ]
then
# If CLIENT_ID is specified, use the google.js configuration with the modified ID
+2 -1
View File
@@ -18,7 +18,8 @@
},
"rules": {
// Enforce consistent brace style for all control statements for readability
"curly": "error"
"curly": "error",
"import/no-anonymous-default-export": "off"
},
"globals": {
"cy": true,
+19
View File
@@ -0,0 +1,19 @@
version: 2
enable-beta-ecosystems: true
updates:
- package-ecosystem: 'bun'
directory: '/'
schedule:
interval: 'daily'
labels: ['dependencies']
commit-message:
prefix: 'chore'
include: 'scope'
- package-ecosystem: 'npm'
directory: '/'
schedule:
interval: 'daily'
labels: ['dependencies']
commit-message:
prefix: 'chore'
include: 'scope'
+3 -4
View File
@@ -9,7 +9,7 @@ jobs:
timeout-minutes: 60
runs-on: ubuntu-latest
container:
image: mcr.microsoft.com/playwright:v1.49.0-noble
image: mcr.microsoft.com/playwright:v1.51.1-noble
strategy:
fail-fast: false
matrix:
@@ -28,9 +28,8 @@ jobs:
- name: Set up Git safe directory
run: git config --global --add safe.directory /__w/Viewers/Viewers
- name: Run Playwright tests
run:
export NODE_OPTIONS="--max_old_space_size=8192" && bun x playwright test --shard=${{
matrix.shardIndex }}/${{ matrix.shardTotal }}
run: |
NODE_OPTIONS="--max_old_space_size=8192" TEST_ENV=true bun x playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
- name: Upload blob report to GitHub Actions Artifacts
if: ${{ !cancelled() }}
+1
View File
@@ -1,5 +1,6 @@
# Packages
node_modules
.cursor/
# Output
build
+5
View File
@@ -49,6 +49,7 @@ const defineValues = {
'process.env.LOCIZE_PROJECTID': JSON.stringify(process.env.LOCIZE_PROJECTID || ''),
'process.env.LOCIZE_API_KEY': JSON.stringify(process.env.LOCIZE_API_KEY || ''),
'process.env.REACT_APP_I18N_DEBUG': JSON.stringify(process.env.REACT_APP_I18N_DEBUG || ''),
'process.env.TEST_ENV': JSON.stringify(process.env.TEST_ENV || ''),
};
// Only redefine updated values. This avoids warning messages in the logs
@@ -168,6 +169,10 @@ module.exports = (env, argv, { SRC_DIR, ENTRY }) => {
},
],
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource',
},
], //.concat(vtkRules),
},
resolve: {
+1285
View File
File diff suppressed because it is too large. Load diff
+12 -6
View File
@@ -43,7 +43,10 @@
# docker build -t ohif/viewer:latest .
# Copy Files
FROM node:20.18.1-slim as builder
RUN apt-get update && apt-get install -y build-essential python3
RUN mkdir /usr/src/app
WORKDIR /usr/src/app
RUN npm install -g bun
@@ -54,12 +57,11 @@ ENV PATH=/usr/src/app/node_modules/.bin:$PATH
COPY package.json yarn.lock preinstall.js lerna.json ./
COPY --parents ./addOns/package.json ./addOns/*/*/package.json ./extensions/*/package.json ./modes/*/package.json ./platform/*/package.json ./
# Run the install before copying the rest of the files
RUN bun pm cache rm
RUN bun install
# Copy the local directory
COPY --link --exclude=yarn.lock --exclude=package.json --exclude=Dockerfile . .
# Do a second install to finalize things after the copy
RUN bun run show:config
RUN bun install
# Build here
# After install it should hopefully be stable until the local directory changes
@@ -67,6 +69,7 @@ ENV QUICK_BUILD true
# ENV GENERATE_SOURCEMAP=false
ARG APP_CONFIG=config/default.js
ARG PUBLIC_URL=/
ENV PUBLIC_URL=${PUBLIC_URL}
RUN bun run show:config
RUN bun run build
@@ -79,16 +82,19 @@ RUN ./.docker/compressDist.sh
# which runs Nginx using Alpine Linux
FROM nginxinc/nginx-unprivileged:1.27-alpine as final
#RUN apk add --no-cache bash
ARG PORT=80
ENV PORT=${PORT}
ARG PUBLIC_URL=/
ENV PUBLIC_URL=${PUBLIC_URL}
ARG PORT=80
ENV PORT=${PORT}
RUN rm /etc/nginx/conf.d/default.conf
USER nginx
COPY --chown=nginx:nginx .docker/Viewer-v3.x /usr/src
RUN chmod 777 /usr/src/entrypoint.sh
COPY --from=builder /usr/src/app/platform/app/dist /usr/share/nginx/html${PUBLIC_URL}
COPY --from=builder /usr/src/app/platform/app/dist/index.html /usr/share/nginx/html
# Copy paths that are renamed/redirected generally
# Microscopy libraries depend on root level include, so must be copied
COPY --from=builder /usr/src/app/platform/app/dist/dicom-microscopy-viewer /usr/share/nginx/html/dicom-microscopy-viewer
# In entrypoint.sh, app-config.js might be overwritten, so chmod it to be writeable.
# The nginx user cannot chmod it, so change to root.
USER root
+3 -3
View File
@@ -1,5 +1,4 @@
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<div align="center">
<h1>OHIF Medical Imaging Viewer</h1>
<p><strong>The OHIF Viewer</strong> is a zero-footprint medical image viewer
@@ -135,7 +134,7 @@ Here is a schematic representation of our development workflow:
### Requirements
- [Yarn 1.17.3+](https://yarnpkg.com/en/docs/install)
- [Yarn 1.20.0+](https://yarnpkg.com/en/docs/install)
- [Node 18+](https://nodejs.org/en/)
- Yarn Workspaces should be enabled on your machine:
- `yarn config set workspaces-experimental true`
@@ -171,7 +170,8 @@ also supports a number of commands that can be found in their respective
| Yarn Commands | Description |
| ---------------------------- | ------------------------------------------------------------- |
| **Develop** | |
| `dev` or `start` | Default development experience for Viewer |
| `dev` | Default development experience for Viewer |
| `dev:fast` | Our experimental fast dev mode that uses rsbuild instead of webpack |
| `test:unit` | Jest multi-project test runner; overall coverage |
| **Deploy** | |
| `build`\* | Builds production output for our PWA Viewer | |
File diff suppressed because it is too large. Load diff
+8 -5
View File
@@ -1,7 +1,7 @@
{
"name": "@externals/devDependencies",
"description": "External dev dependencies - put dev build dependencies here",
"version": "3.10.0-beta.38",
"version": "3.10.2",
"license": "MIT",
"private": true,
"engines": {
@@ -10,10 +10,10 @@
},
"dependencies": {
"@babel/runtime": "^7.20.13",
"@kitware/vtk.js": "32.1.1",
"@kitware/vtk.js": "32.12.0",
"clsx": "^2.1.1",
"core-js": "^3.2.1",
"moment": "^2.9.4"
"moment": "^2.30.1"
},
"peerDependencies": {
"react": "^18.3.1",
@@ -21,7 +21,11 @@
},
"devDependencies": {
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.15",
"@rsbuild/core": "^1.1.13",
"@rsbuild/plugin-node-polyfill": "1.2.0",
"@rsbuild/plugin-react": "^1.1.0",
"@svgr/webpack": "^8.1.0",
"@swc/helpers": "^0.5.15",
"@types/jest": "^27.5.0",
"@typescript-eslint/eslint-plugin": "^6.3.0",
"@typescript-eslint/parser": "^6.3.0",
@@ -68,11 +72,10 @@
"prettier-plugin-tailwindcss": "^0.5.4",
"react-refresh": "^0.14.2",
"semver": "^7.5.1",
"serve": "^14.2.0",
"serve": "^14.2.4",
"shader-loader": "^1.3.1",
"shx": "^0.3.3",
"source-map-loader": "^4.0.1",
"start-server-and-test": "^1.10.0",
"style-loader": "^1.0.0",
"stylus": "^0.59.0",
"stylus-loader": "^7.1.3",
+992
View File
@@ -3,6 +3,998 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.10.2](https://github.com/OHIF/Viewers/compare/v3.10.1...v3.10.2) (2025-06-09)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
## [3.10.1](https://github.com/OHIF/Viewers/compare/v3.10.0...v3.10.1) (2025-04-11)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.159...v3.10.0) (2025-04-10)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.159](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.158...v3.10.0-beta.159) (2025-04-09)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.158](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.157...v3.10.0-beta.158) (2025-04-09)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.157](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.156...v3.10.0-beta.157) (2025-04-09)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.156](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.155...v3.10.0-beta.156) (2025-04-08)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.155](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.154...v3.10.0-beta.155) (2025-04-08)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.154](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.153...v3.10.0-beta.154) (2025-04-08)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.153](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.152...v3.10.0-beta.153) (2025-04-08)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.152](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.151...v3.10.0-beta.152) (2025-04-07)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.151](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.150...v3.10.0-beta.151) (2025-04-07)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.150](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.149...v3.10.0-beta.150) (2025-04-04)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.149](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.148...v3.10.0-beta.149) (2025-04-04)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.148](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.147...v3.10.0-beta.148) (2025-04-04)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.147](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.146...v3.10.0-beta.147) (2025-04-03)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.146](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.145...v3.10.0-beta.146) (2025-04-02)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.145](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.144...v3.10.0-beta.145) (2025-04-02)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.144](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.143...v3.10.0-beta.144) (2025-04-01)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.143](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.142...v3.10.0-beta.143) (2025-04-01)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.142](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.141...v3.10.0-beta.142) (2025-03-31)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.141](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.140...v3.10.0-beta.141) (2025-03-28)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.140](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.139...v3.10.0-beta.140) (2025-03-27)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.139](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.138...v3.10.0-beta.139) (2025-03-27)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.138](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.137...v3.10.0-beta.138) (2025-03-26)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.137](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.136...v3.10.0-beta.137) (2025-03-26)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.136](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.135...v3.10.0-beta.136) (2025-03-25)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.135](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.134...v3.10.0-beta.135) (2025-03-25)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.134](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.133...v3.10.0-beta.134) (2025-03-20)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.133](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.132...v3.10.0-beta.133) (2025-03-20)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.132](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.131...v3.10.0-beta.132) (2025-03-20)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.131](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.130...v3.10.0-beta.131) (2025-03-19)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.130](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.129...v3.10.0-beta.130) (2025-03-19)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.129](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.128...v3.10.0-beta.129) (2025-03-18)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.128](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.127...v3.10.0-beta.128) (2025-03-18)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.127](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.126...v3.10.0-beta.127) (2025-03-18)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.126](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.125...v3.10.0-beta.126) (2025-03-18)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.125](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.124...v3.10.0-beta.125) (2025-03-17)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.124](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.123...v3.10.0-beta.124) (2025-03-17)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.123](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.122...v3.10.0-beta.123) (2025-03-17)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.122](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.121...v3.10.0-beta.122) (2025-03-17)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.121](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.120...v3.10.0-beta.121) (2025-03-11)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.120](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.119...v3.10.0-beta.120) (2025-03-11)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.119](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.118...v3.10.0-beta.119) (2025-03-10)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.118](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.117...v3.10.0-beta.118) (2025-03-07)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.117](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.116...v3.10.0-beta.117) (2025-03-07)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.116](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.115...v3.10.0-beta.116) (2025-03-06)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.115](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.114...v3.10.0-beta.115) (2025-02-28)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.114](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.113...v3.10.0-beta.114) (2025-02-28)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.113](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.112...v3.10.0-beta.113) (2025-02-28)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.112](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.111...v3.10.0-beta.112) (2025-02-28)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.111](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.110...v3.10.0-beta.111) (2025-02-26)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.110](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.109...v3.10.0-beta.110) (2025-02-26)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.109](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.108...v3.10.0-beta.109) (2025-02-25)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.107...v3.10.0-beta.108) (2025-02-25)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.106...v3.10.0-beta.107) (2025-02-25)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.105...v3.10.0-beta.106) (2025-02-25)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.104...v3.10.0-beta.105) (2025-02-25)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.103...v3.10.0-beta.104) (2025-02-25)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.102...v3.10.0-beta.103) (2025-02-23)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.101...v3.10.0-beta.102) (2025-02-20)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.100...v3.10.0-beta.101) (2025-02-20)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.99...v3.10.0-beta.100) (2025-02-19)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.98...v3.10.0-beta.99) (2025-02-18)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.97...v3.10.0-beta.98) (2025-02-18)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.96...v3.10.0-beta.97) (2025-02-18)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.95...v3.10.0-beta.96) (2025-02-18)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.94...v3.10.0-beta.95) (2025-02-13)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.93...v3.10.0-beta.94) (2025-02-11)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.92...v3.10.0-beta.93) (2025-02-04)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.91...v3.10.0-beta.92) (2025-02-04)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.90...v3.10.0-beta.91) (2025-02-04)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.89...v3.10.0-beta.90) (2025-02-04)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.88...v3.10.0-beta.89) (2025-02-04)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.87...v3.10.0-beta.88) (2025-02-04)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.86...v3.10.0-beta.87) (2025-02-03)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.85...v3.10.0-beta.86) (2025-02-03)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.84...v3.10.0-beta.85) (2025-02-03)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.83...v3.10.0-beta.84) (2025-01-31)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.82...v3.10.0-beta.83) (2025-01-31)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.81...v3.10.0-beta.82) (2025-01-31)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.80...v3.10.0-beta.81) (2025-01-29)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.79...v3.10.0-beta.80) (2025-01-29)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.78...v3.10.0-beta.79) (2025-01-28)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.77...v3.10.0-beta.78) (2025-01-28)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.76...v3.10.0-beta.77) (2025-01-28)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.75...v3.10.0-beta.76) (2025-01-27)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.74...v3.10.0-beta.75) (2025-01-27)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.73...v3.10.0-beta.74) (2025-01-27)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.72...v3.10.0-beta.73) (2025-01-24)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.71...v3.10.0-beta.72) (2025-01-24)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.70...v3.10.0-beta.71) (2025-01-23)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.69...v3.10.0-beta.70) (2025-01-23)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.68...v3.10.0-beta.69) (2025-01-22)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.67...v3.10.0-beta.68) (2025-01-21)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.66...v3.10.0-beta.67) (2025-01-21)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.65...v3.10.0-beta.66) (2025-01-21)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.64...v3.10.0-beta.65) (2025-01-20)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.63...v3.10.0-beta.64) (2025-01-20)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.62...v3.10.0-beta.63) (2025-01-17)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.61...v3.10.0-beta.62) (2025-01-16)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.60...v3.10.0-beta.61) (2025-01-16)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.59...v3.10.0-beta.60) (2025-01-15)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.58...v3.10.0-beta.59) (2025-01-14)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.57...v3.10.0-beta.58) (2025-01-13)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.56...v3.10.0-beta.57) (2025-01-13)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.55...v3.10.0-beta.56) (2025-01-13)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.54...v3.10.0-beta.55) (2025-01-10)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.53...v3.10.0-beta.54) (2025-01-10)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.52...v3.10.0-beta.53) (2025-01-10)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.51...v3.10.0-beta.52) (2025-01-10)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.50...v3.10.0-beta.51) (2025-01-10)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.49...v3.10.0-beta.50) (2025-01-10)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.48...v3.10.0-beta.49) (2025-01-09)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.47...v3.10.0-beta.48) (2025-01-09)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.46...v3.10.0-beta.47) (2025-01-09)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.45...v3.10.0-beta.46) (2025-01-08)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.44...v3.10.0-beta.45) (2025-01-08)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.43...v3.10.0-beta.44) (2025-01-08)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.42...v3.10.0-beta.43) (2025-01-08)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.41...v3.10.0-beta.42) (2025-01-08)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.40...v3.10.0-beta.41) (2025-01-08)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.39...v3.10.0-beta.40) (2025-01-08)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.38...v3.10.0-beta.39) (2025-01-08)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
# [3.10.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.10.0-beta.37...v3.10.0-beta.38) (2025-01-07)
**Note:** Version bump only for package @externals/dicom-microscopy-viewer
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@externals/dicom-microscopy-viewer",
"description": "External reference to dicom-microscopy-viewer",
"version": "3.10.0-beta.38",
"version": "3.10.2",
"license": "MIT",
"dependencies": {
"dicom-microscopy-viewer": "^0.46.1"
+1 -1
View File
@@ -38,7 +38,7 @@
"resolutions": {
"**/@babel/runtime": "^7.20.13",
"commander": "8.3.0",
"dcmjs": ">=0.33.0",
"dcmjs": "0.38.0",
"dicomweb-client": ">=0.10.4",
"nth-check": "^2.1.1",
"trim-newlines": "^5.0.0",
-3
View File
@@ -1,6 +1,4 @@
// https://babeljs.io/docs/en/options#babelrcroots
const { extendDefaultPlugins } = require('svgo');
module.exports = {
babelrcRoots: ['./platform/*', './extensions/*', './modes/*'],
presets: ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'],
@@ -51,7 +49,6 @@ module.exports = {
'@babel/preset-react',
'@babel/preset-typescript',
],
plugins: ['react-refresh/babel'],
ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'],
},
},
+7746
View File
File diff suppressed because it is too large. Load diff
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
28cf3a17fad5070ba00d0d5d27633237b499da7a
36813e0c2f1ea48953714e8e48aff6e9d4516449
File diff suppressed because it is too large. Load diff
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-cornerstone-dicom-pmap",
"version": "3.10.0-beta.38",
"version": "3.10.2",
"description": "DICOM Parametric Map read workflow",
"author": "OHIF",
"license": "MIT",
@@ -33,10 +33,10 @@
"start": "yarn run dev"
},
"peerDependencies": {
"@ohif/core": "3.10.0-beta.38",
"@ohif/extension-cornerstone": "3.10.0-beta.38",
"@ohif/extension-default": "3.10.0-beta.38",
"@ohif/i18n": "3.10.0-beta.38",
"@ohif/core": "3.10.2",
"@ohif/extension-cornerstone": "3.10.2",
"@ohif/extension-default": "3.10.2",
"@ohif/i18n": "3.10.2",
"prop-types": "^15.6.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
@@ -46,9 +46,9 @@
},
"dependencies": {
"@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^2.14.2",
"@cornerstonejs/core": "^2.14.2",
"@kitware/vtk.js": "32.1.1",
"@cornerstonejs/adapters": "^3.11.6",
"@cornerstonejs/core": "^3.11.6",
"@kitware/vtk.js": "32.12.0",
"react-color": "^2.19.3"
}
}
@@ -1,6 +1,6 @@
import PropTypes from 'prop-types';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useViewportGrid, LoadingIndicatorTotalPercent } from '@ohif/ui';
import { useViewportGrid } from '@ohif/ui-next';
function OHIFCornerstonePMAPViewport(props: withAppTypes) {
const {
@@ -12,7 +12,7 @@ function OHIFCornerstonePMAPViewport(props: withAppTypes) {
extensionManager,
} = props;
const viewportId = viewportOptions.viewportId;
const { displaySetService, segmentationService, uiNotificationService } =
const { displaySetService, segmentationService, uiNotificationService, customizationService } =
servicesManager.services;
// PMAP viewport will always have a single display set
@@ -20,6 +20,10 @@ function OHIFCornerstonePMAPViewport(props: withAppTypes) {
throw new Error('PMAP viewport must have a single display set');
}
const LoadingIndicatorTotalPercent = customizationService.getCustomization(
'ui.loadingIndicatorTotalPercent'
);
const pmapDisplaySet = displaySets[0];
const [viewportGrid, viewportGridService] = useViewportGrid();
const referencedDisplaySetRef = useRef(null);
File diff suppressed because it is too large. Load diff
@@ -37,7 +37,6 @@ module.exports = {
'@babel/preset-react',
'@babel/preset-typescript',
],
plugins: ['react-refresh/babel'],
ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'],
},
},
+5 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-cornerstone-dicom-rt",
"version": "3.10.0-beta.38",
"version": "3.10.2",
"description": "DICOM RT read workflow",
"author": "OHIF",
"license": "MIT",
@@ -33,10 +33,10 @@
"start": "yarn run dev"
},
"peerDependencies": {
"@ohif/core": "3.10.0-beta.38",
"@ohif/extension-cornerstone": "3.10.0-beta.38",
"@ohif/extension-default": "3.10.0-beta.38",
"@ohif/i18n": "3.10.0-beta.38",
"@ohif/core": "3.10.2",
"@ohif/extension-cornerstone": "3.10.2",
"@ohif/extension-default": "3.10.2",
"@ohif/i18n": "3.10.2",
"prop-types": "^15.6.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
@@ -1,5 +1,5 @@
function createRTToolGroupAndAddTools(ToolGroupService, customizationService, toolGroupId) {
const { tools } = customizationService.get('cornerstone.overlayViewportTools') ?? {};
const tools = customizationService.getCustomization('cornerstone.overlayViewportTools');
return ToolGroupService.createToolGroupAndAddTools(toolGroupId, tools);
}
@@ -1,5 +1,3 @@
import { ButtonEnums } from '@ohif/ui';
const RESPONSE = {
NO_NEVER: -1,
CANCEL: 0,
@@ -13,13 +11,13 @@ function promptHydrateRT({
preHydrateCallbacks,
hydrateRTDisplaySet,
}: withAppTypes) {
const { uiViewportDialogService } = servicesManager.services;
const { uiViewportDialogService, customizationService } = servicesManager.services;
const extensionManager = servicesManager._extensionManager;
const appConfig = extensionManager._appConfig;
return new Promise(async function (resolve, reject) {
const promptResult = appConfig?.disableConfirmationPrompts
? RESPONSE.HYDRATE_SEG
: await _askHydrate(uiViewportDialogService, viewportId);
: await _askHydrate(uiViewportDialogService, customizationService, viewportId);
if (promptResult === RESPONSE.HYDRATE_SEG) {
preHydrateCallbacks?.forEach(callback => {
@@ -37,19 +35,23 @@ function promptHydrateRT({
});
}
function _askHydrate(uiViewportDialogService: AppTypes.UIViewportDialogService, viewportId) {
function _askHydrate(
uiViewportDialogService: AppTypes.UIViewportDialogService,
customizationService: AppTypes.CustomizationService,
viewportId
) {
return new Promise(function (resolve, reject) {
const message = 'Do you want to open this Segmentation?';
const message = customizationService.getCustomization('viewportNotification.hydrateRTMessage');
const actions = [
{
id: 'no-hydrate',
type: ButtonEnums.type.secondary,
type: 'secondary',
text: 'No',
value: RESPONSE.CANCEL,
},
{
id: 'yes-hydrate',
type: ButtonEnums.type.primary,
type: 'primary',
text: 'Yes',
value: RESPONSE.HYDRATE_SEG,
},
@@ -1,6 +1,8 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import { useViewportGrid, LoadingIndicatorTotalPercent, ViewportActionArrows } from '@ohif/ui';
import { ViewportActionArrows } from '@ohif/ui-next';
import { useViewportGrid } from '@ohif/ui-next';
import { utils } from '@ohif/extension-cornerstone';
import promptHydrateRT from '../utils/promptHydrateRT';
import _getStatusComponent from './_getStatusComponent';
@@ -38,12 +40,16 @@ function OHIFCornerstoneRTViewport(props: withAppTypes) {
throw new Error('RT viewport should only have a single display set');
}
const LoadingIndicatorTotalPercent = customizationService.getCustomization(
'ui.loadingIndicatorTotalPercent'
);
const rtDisplaySet = displaySets[0];
const [viewportGrid, viewportGridService] = useViewportGrid();
// States
const [selectedSegment, setSelectedSegment] = useState(1);
const selectedSegmentObjectIndex: number = 0;
const { setPositionPresentation } = usePositionPresentationStore();
// Hydration means that the RT is opened and segments are loaded into the
@@ -136,26 +142,15 @@ function OHIFCornerstoneRTViewport(props: withAppTypes) {
const onSegmentChange = useCallback(
direction => {
const segmentationId = rtDisplaySet.displaySetInstanceUID;
const segmentation = segmentationService.getSegmentation(segmentationId);
const { segments } = segmentation;
const numberOfSegments = Object.keys(segments).length;
let newSelectedSegmentIndex = selectedSegment + direction;
// Segment 0 is always background
if (newSelectedSegmentIndex >= numberOfSegments - 1) {
newSelectedSegmentIndex = 1;
} else if (newSelectedSegmentIndex === 0) {
newSelectedSegmentIndex = numberOfSegments - 1;
}
segmentationService.jumpToSegmentCenter(segmentationId, newSelectedSegmentIndex, viewportId);
setSelectedSegment(newSelectedSegmentIndex);
utils.handleSegmentChange({
direction,
segDisplaySet: rtDisplaySet,
viewportId,
selectedSegmentObjectIndex,
segmentationService,
});
},
[selectedSegment, segmentationService]
[selectedSegmentObjectIndex]
);
useEffect(() => {
@@ -1,7 +1,7 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Icon, Tooltip, ViewportActionButton } from '@ohif/ui';
import { Icons } from '@ohif/ui-next';
import { ViewportActionButton } from '@ohif/ui-next';
import { Icons, Tooltip, TooltipTrigger, TooltipContent } from '@ohif/ui-next';
export default function _getStatusComponent({ isHydrated, onStatusClick }) {
let ToolTipMessage = null;
@@ -10,17 +10,15 @@ export default function _getStatusComponent({ isHydrated, onStatusClick }) {
switch (isHydrated) {
case true:
StatusIcon = () => <Icons.ByName name="status-alert" />;
ToolTipMessage = () => <div>This Segmentation is loaded in the segmentation panel</div>;
break;
case false:
StatusIcon = () => (
<Icons.ByName
className="text-aqua-pale"
className="text-muted-foreground h-4 w-4"
name="status-untracked"
/>
);
ToolTipMessage = () => <div>Click LOAD to load RTSTRUCT.</div>;
}
@@ -44,11 +42,15 @@ export default function _getStatusComponent({ isHydrated, onStatusClick }) {
return (
<>
{ToolTipMessage && (
<Tooltip
content={<ToolTipMessage />}
position="bottom-left"
>
<StatusArea />
<Tooltip>
<TooltipTrigger asChild>
<span>
<StatusArea />
</span>
</TooltipTrigger>
<TooltipContent side="bottom">
<ToolTipMessage />
</TooltipContent>
</Tooltip>
)}
{!ToolTipMessage && <StatusArea />}
File diff suppressed because it is too large. Load diff
@@ -37,7 +37,6 @@ module.exports = {
'@babel/preset-react',
'@babel/preset-typescript',
],
plugins: ['react-refresh/babel'],
ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'],
},
},
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-cornerstone-dicom-seg",
"version": "3.10.0-beta.38",
"version": "3.10.2",
"description": "DICOM SEG read workflow",
"author": "OHIF",
"license": "MIT",
@@ -33,10 +33,10 @@
"start": "yarn run dev"
},
"peerDependencies": {
"@ohif/core": "3.10.0-beta.38",
"@ohif/extension-cornerstone": "3.10.0-beta.38",
"@ohif/extension-default": "3.10.0-beta.38",
"@ohif/i18n": "3.10.0-beta.38",
"@ohif/core": "3.10.2",
"@ohif/extension-cornerstone": "3.10.2",
"@ohif/extension-default": "3.10.2",
"@ohif/i18n": "3.10.2",
"prop-types": "^15.6.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
@@ -46,9 +46,9 @@
},
"dependencies": {
"@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^2.14.2",
"@cornerstonejs/core": "^2.14.2",
"@kitware/vtk.js": "32.1.1",
"@cornerstonejs/adapters": "^3.11.6",
"@cornerstonejs/core": "^3.11.6",
"@kitware/vtk.js": "32.12.0",
"react-color": "^2.19.3"
}
}
@@ -1,20 +1,12 @@
import dcmjs from 'dcmjs';
import { createReportDialogPrompt } from '@ohif/extension-default';
import { Types } from '@ohif/core';
import { classes, Types } from '@ohif/core';
import { cache, metaData } from '@cornerstonejs/core';
import {
segmentation as cornerstoneToolsSegmentation,
Enums as cornerstoneToolsEnums,
utilities,
} from '@cornerstonejs/tools';
import { segmentation as cornerstoneToolsSegmentation } from '@cornerstonejs/tools';
import { adaptersRT, helpers, adaptersSEG } from '@cornerstonejs/adapters';
import { classes, DicomMetadataStore } from '@ohif/core';
import { createReportDialogPrompt } from '@ohif/extension-default';
import { DicomMetadataStore } from '@ohif/core';
import vtkImageMarchingSquares from '@kitware/vtk.js/Filters/General/ImageMarchingSquares';
import vtkDataArray from '@kitware/vtk.js/Common/Core/DataArray';
import vtkImageData from '@kitware/vtk.js/Common/DataModel/ImageData';
const { segmentation: segmentationUtils } = utilities;
import PROMPT_RESPONSES from '../../default/src/utils/_shared/PROMPT_RESPONSES';
const { datasetToBlob } = dcmjs.data;
@@ -45,13 +37,8 @@ const commandsModule = ({
servicesManager,
extensionManager,
}: Types.Extensions.ExtensionParams): Types.Extensions.CommandsModule => {
const {
segmentationService,
uiDialogService,
displaySetService,
viewportGridService,
toolGroupService,
} = servicesManager.services as AppTypes.Services;
const { segmentationService, displaySetService, viewportGridService, toolGroupService } =
servicesManager.services as AppTypes.Services;
const actions = {
/**
@@ -226,14 +213,6 @@ const commandsModule = ({
* otherwise throws an error.
*/
storeSegmentation: async ({ segmentationId, dataSource }) => {
const promptResult = await createReportDialogPrompt(uiDialogService, {
extensionManager,
});
if (promptResult.action !== 1 && !promptResult.value) {
return;
}
const segmentation = segmentationService.getSegmentation(segmentationId);
if (!segmentation) {
@@ -241,33 +220,50 @@ const commandsModule = ({
}
const { label } = segmentation;
const SeriesDescription = promptResult.value || label || 'Research Derived Series';
const defaultDataSource = dataSource ?? extensionManager.getActiveDataSource();
const generatedData = actions.generateSegmentation({
segmentationId,
options: {
SeriesDescription,
},
const {
value: reportName,
dataSourceName: selectedDataSource,
action,
} = await createReportDialogPrompt({
servicesManager,
extensionManager,
title: 'Store Segmentation',
});
if (!generatedData || !generatedData.dataset) {
throw new Error('Error during segmentation generation');
if (action === PROMPT_RESPONSES.CREATE_REPORT) {
try {
const selectedDataSourceConfig = selectedDataSource
? extensionManager.getDataSources(selectedDataSource)[0]
: defaultDataSource;
const generatedData = actions.generateSegmentation({
segmentationId,
options: {
SeriesDescription: reportName || label || 'Research Derived Series',
},
});
if (!generatedData || !generatedData.dataset) {
throw new Error('Error during segmentation generation');
}
const { dataset: naturalizedReport } = generatedData;
await selectedDataSourceConfig.store.dicom(naturalizedReport);
// add the information for where we stored it to the instance as well
naturalizedReport.wadoRoot = selectedDataSourceConfig.getConfig().wadoRoot;
DicomMetadataStore.addInstances([naturalizedReport], true);
return naturalizedReport;
} catch (error) {
console.debug('Error storing segmentation:', error);
throw error;
}
}
const { dataset: naturalizedReport } = generatedData;
await dataSource.store.dicom(naturalizedReport);
// The "Mode" route listens for DicomMetadataStore changes
// When a new instance is added, it listens and
// automatically calls makeDisplaySets
// add the information for where we stored it to the instance as well
naturalizedReport.wadoRoot = dataSource.getConfig().wadoRoot;
DicomMetadataStore.addInstances([naturalizedReport], true);
return naturalizedReport;
},
/**
* Converts segmentations into RTSS for download.
@@ -276,21 +272,24 @@ const commandsModule = ({
* converts dataset to downloadable blob.
*
*/
downloadRTSS: ({ segmentationId }) => {
downloadRTSS: async ({ segmentationId }) => {
const segmentations = segmentationService.getSegmentation(segmentationId);
const vtkUtils = {
vtkImageMarchingSquares,
vtkDataArray,
vtkImageData,
};
const RTSS = generateRTSSFromSegmentations(
// inject colors to the segmentIndex
const firstRepresentation =
segmentationService.getRepresentationsForSegmentation(segmentationId)[0];
Object.entries(segmentations.segments).forEach(([segmentIndex, segment]) => {
segment.color = segmentationService.getSegmentColor(
firstRepresentation.viewportId,
segmentationId,
segmentIndex
);
});
const RTSS = await generateRTSSFromSegmentations(
segmentations,
classes.MetadataProvider,
DicomMetadataStore,
cache,
cornerstoneToolsEnums,
vtkUtils
DicomMetadataStore
);
try {
@@ -303,48 +302,9 @@ const commandsModule = ({
console.warn(e);
}
},
setBrushSize: ({ value, toolNames }) => {
const brushSize = Number(value);
toolGroupService.getToolGroupIds()?.forEach(toolGroupId => {
if (toolNames?.length === 0) {
segmentationUtils.setBrushSizeForToolGroup(toolGroupId, brushSize);
} else {
toolNames?.forEach(toolName => {
segmentationUtils.setBrushSizeForToolGroup(toolGroupId, brushSize, toolName);
});
}
});
},
setThresholdRange: ({
value,
toolNames = ['ThresholdCircularBrush', 'ThresholdSphereBrush'],
}) => {
toolGroupService.getToolGroupIds()?.forEach(toolGroupId => {
const toolGroup = toolGroupService.getToolGroup(toolGroupId);
toolNames?.forEach(toolName => {
toolGroup.setToolConfiguration(toolName, {
strategySpecificConfiguration: {
THRESHOLD: {
threshold: value,
},
},
});
});
});
},
};
const definitions = {
/**
* Obsolete?
*/
loadSegmentationDisplaySetsForViewport: {
commandFn: actions.loadSegmentationDisplaySetsForViewport,
},
/**
* Obsolete?
*/
loadSegmentationsForViewport: {
commandFn: actions.loadSegmentationsForViewport,
},
@@ -361,12 +321,6 @@ const commandsModule = ({
downloadRTSS: {
commandFn: actions.downloadRTSS,
},
setBrushSize: {
commandFn: actions.setBrushSize,
},
setThresholdRange: {
commandFn: actions.setThresholdRange,
},
};
return {
@@ -177,12 +177,16 @@ async function _loadSegments({
throw new Error('referencedDisplaySet is missing for SEG');
}
const { instances: images } = referencedDisplaySet;
const imageIds = images.map(({ imageId }) => imageId);
let { imageIds } = referencedDisplaySet;
if (!imageIds) {
// try images
const { images } = referencedDisplaySet;
imageIds = images.map(image => image.imageId);
}
// Todo: what should be defaults here
const tolerance = 0.001;
const skipOverlapping = true;
eventTarget.addEventListener(Enums.Events.SEGMENTATION_LOAD_PROGRESS, evt => {
const { percentComplete } = evt.detail;
segmentationService._broadcastEvent(segmentationService.EVENTS.SEGMENT_LOADING_COMPLETE, {
@@ -190,11 +194,10 @@ async function _loadSegments({
});
});
const results = await adaptersSEG.Cornerstone3D.Segmentation.generateToolState(
const results = await adaptersSEG.Cornerstone3D.Segmentation.createFromDICOMSegBuffer(
imageIds,
arrayBuffer,
metaData,
{ skipOverlapping, tolerance, eventTarget, triggerEvent }
{ metadataProvider: metaData, tolerance }
);
let usedRecommendedDisplayCIELabValue = true;
@@ -211,15 +214,6 @@ async function _loadSegments({
}
});
if (results.overlappingSegments) {
uiNotificationService.show({
title: 'Overlapping Segments',
message:
'Unsupported overlapping segments detected, segmentation rendering results may be incorrect.',
type: 'warning',
});
}
if (!usedRecommendedDisplayCIELabValue) {
// Display a notification about the non-utilization of RecommendedDisplayCIELabValue
uiNotificationService.show({
@@ -1,6 +1,15 @@
export function getToolbarModule({ servicesManager }: withAppTypes) {
const { segmentationService, toolbarService, toolGroupService } = servicesManager.services;
return [
{
name: 'evaluate.cornerstone.hasSegmentation',
evaluate: ({ viewportId }) => {
const segmentations = segmentationService.getSegmentationRepresentations(viewportId);
return {
disabled: !segmentations?.length,
};
},
},
{
name: 'evaluate.cornerstone.segmentation',
evaluate: ({ viewportId, button, toolNames, disabledText }) => {
@@ -12,27 +21,39 @@ export function getToolbarModule({ servicesManager }: withAppTypes) {
if (!segmentations?.length) {
return {
disabled: true,
className: '!text-common-bright !bg-black opacity-50',
disabledText: disabledText ?? 'No segmentations available',
};
}
const activeSegmentation = segmentationService.getActiveSegmentation(viewportId);
if (!Object.keys(activeSegmentation.segments).length) {
return {
disabled: true,
disabledText: 'Add segment to enable this tool',
};
}
const toolGroup = toolGroupService.getToolGroupForViewport(viewportId);
if (!toolGroup) {
return {
disabled: true,
className: '!text-common-bright ohif-disabled',
disabledText: disabledText ?? 'Not available on the current viewport',
};
}
if (!toolNames) {
return {
disabled: false,
// isActive: false,
};
}
const toolName = toolbarService.getToolNameForButton(button);
if (!toolGroup.hasTool(toolName) && !toolNames) {
return {
disabled: true,
className: '!text-common-bright ohif-disabled',
disabledText: disabledText ?? 'Not available on the current viewport',
};
}
@@ -43,12 +64,6 @@ export function getToolbarModule({ servicesManager }: withAppTypes) {
return {
disabled: false,
className: isPrimaryActive
? '!text-black !bg-primary-light hover:bg-primary-light hover-text-black hover:cursor-pointer'
: '!text-common-bright !bg-black hover:bg-primary-light hover:cursor-pointer hover:text-black',
// Todo: isActive right now is used for nested buttons where the primary
// button needs to be fully rounded (vs partial rounded) when active
// otherwise it does not have any other use
isActive: isPrimaryActive,
};
},
@@ -1,5 +1,5 @@
function createSEGToolGroupAndAddTools(ToolGroupService, customizationService, toolGroupId) {
const { tools } = customizationService.get('cornerstone.overlayViewportTools') ?? {};
const tools = customizationService.getCustomization('cornerstone.overlayViewportTools');
return ToolGroupService.createToolGroupAndAddTools(toolGroupId, tools);
}
@@ -1,5 +1,3 @@
import { ButtonEnums } from '@ohif/ui';
const RESPONSE = {
NO_NEVER: -1,
CANCEL: 0,
@@ -13,14 +11,14 @@ function promptHydrateSEG({
preHydrateCallbacks,
hydrateCallback,
}: withAppTypes) {
const { uiViewportDialogService } = servicesManager.services;
const { uiViewportDialogService, customizationService } = servicesManager.services;
const extensionManager = servicesManager._extensionManager;
const appConfig = extensionManager._appConfig;
return new Promise(async function (resolve, reject) {
const promptResult = appConfig?.disableConfirmationPrompts
? RESPONSE.HYDRATE_SEG
: await _askHydrate(uiViewportDialogService, viewportId);
: await _askHydrate(uiViewportDialogService, customizationService, viewportId);
if (promptResult === RESPONSE.HYDRATE_SEG) {
preHydrateCallbacks?.forEach(callback => {
@@ -39,19 +37,23 @@ function promptHydrateSEG({
});
}
function _askHydrate(uiViewportDialogService, viewportId) {
function _askHydrate(
uiViewportDialogService: AppTypes.UIViewportDialogService,
customizationService: AppTypes.CustomizationService,
viewportId
) {
return new Promise(function (resolve, reject) {
const message = 'Do you want to open this Segmentation?';
const message = customizationService.getCustomization('viewportNotification.hydrateSEGMessage');
const actions = [
{
id: 'no-hydrate',
type: ButtonEnums.type.secondary,
type: 'secondary',
text: 'No',
value: RESPONSE.CANCEL,
},
{
id: 'yes-hydrate',
type: ButtonEnums.type.primary,
type: 'primary',
text: 'Yes',
value: RESPONSE.HYDRATE_SEG,
},
@@ -1,11 +1,13 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { LoadingIndicatorTotalPercent, useViewportGrid, ViewportActionArrows } from '@ohif/ui';
import { ViewportActionArrows } from '@ohif/ui-next';
import { useViewportGrid } from '@ohif/ui-next';
import createSEGToolGroupAndAddTools from '../utils/initSEGToolGroup';
import promptHydrateSEG from '../utils/promptHydrateSEG';
import _getStatusComponent from './_getStatusComponent';
import { usePositionPresentationStore } from '@ohif/extension-cornerstone';
import { SegmentationRepresentations } from '@cornerstonejs/tools/enums';
import { utils } from '@ohif/extension-cornerstone';
const SEG_TOOLGROUP_BASE_NAME = 'SEGToolGroup';
@@ -30,6 +32,10 @@ function OHIFCornerstoneSEGViewport(props: withAppTypes) {
viewportActionCornersService,
} = servicesManager.services;
const LoadingIndicatorTotalPercent = customizationService.getCustomization(
'ui.loadingIndicatorTotalPercent'
);
const toolGroupId = `${SEG_TOOLGROUP_BASE_NAME}-${viewportId}`;
// SEG viewport will always have a single display set
@@ -41,7 +47,7 @@ function OHIFCornerstoneSEGViewport(props: withAppTypes) {
const [viewportGrid, viewportGridService] = useViewportGrid();
// States
const [selectedSegment, setSelectedSegment] = useState(1);
const selectedSegmentObjectIndex: number = 0;
const { setPositionPresentation } = usePositionPresentationStore();
// Hydration means that the SEG is opened and segments are loaded into the
@@ -126,27 +132,15 @@ function OHIFCornerstoneSEGViewport(props: withAppTypes) {
const onSegmentChange = useCallback(
direction => {
const segmentationId = segDisplaySet.displaySetInstanceUID;
const segmentation = segmentationService.getSegmentation(segmentationId);
const { segments } = segmentation;
const numberOfSegments = Object.keys(segments).length;
let newSelectedSegmentIndex = selectedSegment + direction;
// Segment 0 is always background
if (newSelectedSegmentIndex > numberOfSegments - 1) {
newSelectedSegmentIndex = 1;
} else if (newSelectedSegmentIndex === 0) {
newSelectedSegmentIndex = numberOfSegments - 1;
}
segmentationService.jumpToSegmentCenter(segmentationId, newSelectedSegmentIndex, viewportId);
setSelectedSegment(newSelectedSegmentIndex);
utils.handleSegmentChange({
direction,
segDisplaySet: segDisplaySet,
viewportId,
selectedSegmentObjectIndex,
segmentationService,
});
},
[selectedSegment]
[selectedSegmentObjectIndex]
);
const hydrateSEG = useCallback(() => {
@@ -166,7 +160,7 @@ function OHIFCornerstoneSEGViewport(props: withAppTypes) {
displaySetInstanceUID: referencedDisplaySet.displaySetInstanceUID,
});
viewportGridService.setDisplaySetsForViewport({
commandsManager.runCommand('loadSegmentationDisplaySetsForViewport', {
viewportId,
displaySetInstanceUIDs: [referencedDisplaySet.displaySetInstanceUID],
});
@@ -1,7 +1,7 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Tooltip, ViewportActionButton } from '@ohif/ui';
import { Icons } from '@ohif/ui-next';
import { ViewportActionButton } from '@ohif/ui-next';
import { Icons, Tooltip, TooltipTrigger, TooltipContent } from '@ohif/ui-next';
export default function _getStatusComponent({ isHydrated, onStatusClick }) {
let ToolTipMessage = null;
@@ -10,17 +10,15 @@ export default function _getStatusComponent({ isHydrated, onStatusClick }) {
switch (isHydrated) {
case true:
StatusIcon = () => <Icons.ByName name="status-alert" />;
ToolTipMessage = () => <div>This Segmentation is loaded in the segmentation panel</div>;
break;
case false:
StatusIcon = () => (
<Icons.ByName
className="text-aqua-pale"
className="text-muted-foreground h-4 w-4"
name="status-untracked"
/>
);
ToolTipMessage = () => <div>Click LOAD to load segmentation.</div>;
}
@@ -44,11 +42,15 @@ export default function _getStatusComponent({ isHydrated, onStatusClick }) {
return (
<>
{ToolTipMessage && (
<Tooltip
content={<ToolTipMessage />}
position="bottom-left"
>
<StatusArea />
<Tooltip>
<TooltipTrigger asChild>
<span>
<StatusArea />
</span>
</TooltipTrigger>
<TooltipContent side="bottom">
<ToolTipMessage />
</TooltipContent>
</Tooltip>
)}
{!ToolTipMessage && <StatusArea />}
File diff suppressed because it is too large. Load diff
+8 -8
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-cornerstone-dicom-sr",
"version": "3.10.0-beta.38",
"version": "3.10.2",
"description": "OHIF extension for an SR Cornerstone Viewport",
"author": "OHIF",
"license": "MIT",
@@ -34,10 +34,10 @@
"test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests"
},
"peerDependencies": {
"@ohif/core": "3.10.0-beta.38",
"@ohif/extension-cornerstone": "3.10.0-beta.38",
"@ohif/extension-measurement-tracking": "3.10.0-beta.38",
"@ohif/ui": "3.10.0-beta.38",
"@ohif/core": "3.10.2",
"@ohif/extension-cornerstone": "3.10.2",
"@ohif/extension-measurement-tracking": "3.10.2",
"@ohif/ui": "3.10.2",
"dcmjs": "*",
"dicom-parser": "^1.8.9",
"hammerjs": "^2.0.8",
@@ -46,9 +46,9 @@
},
"dependencies": {
"@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^2.14.2",
"@cornerstonejs/core": "^2.14.2",
"@cornerstonejs/tools": "^2.14.2",
"@cornerstonejs/adapters": "^3.11.6",
"@cornerstonejs/core": "^3.11.6",
"@cornerstonejs/tools": "^3.11.6",
"classnames": "^2.3.2"
}
}
@@ -10,15 +10,23 @@ import hydrateStructuredReport from './utils/hydrateStructuredReport';
const { MeasurementReport } = adaptersSR.Cornerstone3D;
const { log } = OHIF;
interface Options {
SeriesDescription?: string;
SeriesInstanceUID?: string;
SeriesNumber?: number;
InstanceNumber?: number;
SeriesDate?: string;
SeriesTime?: string;
}
/**
*
* @param measurementData An array of measurements from the measurements service
* that you wish to serialize.
* @param additionalFindingTypes toolTypes that should be stored with labels as Findings
* @param options Naturalized DICOM JSON headers to merge into the displaySet.
*
*/
const _generateReport = (measurementData, additionalFindingTypes, options = {}) => {
const _generateReport = (measurementData, additionalFindingTypes, options: Options = {}) => {
const filteredToolState = getFilteredCornerstoneToolState(
measurementData,
additionalFindingTypes
@@ -38,13 +46,37 @@ const _generateReport = (measurementData, additionalFindingTypes, options = {})
if (typeof dataset.SpecificCharacterSet === 'undefined') {
dataset.SpecificCharacterSet = 'ISO_IR 192';
}
dataset.InstanceNumber = options.InstanceNumber ?? 1;
return dataset;
};
const commandsModule = (props: withAppTypes) => {
const { servicesManager, extensionManager, commandsManager } = props;
const { customizationService, displaySetService, viewportGridService } = servicesManager.services;
const { customizationService, viewportGridService, displaySetService } = servicesManager.services;
const actions = {
changeColorMeasurement: ({ uid }) => {
// When this gets supported, it probably belongs in cornerstone, not sr
throw new Error('Unsupported operation: changeColorMeasurement');
// const { color } = measurementService.getMeasurement(uid);
// const rgbaColor = {
// r: color[0],
// g: color[1],
// b: color[2],
// a: color[3] / 255.0,
// };
// colorPickerDialog(uiDialogService, rgbaColor, (newRgbaColor, actionId) => {
// if (actionId === 'cancel') {
// return;
// }
// const color = [newRgbaColor.r, newRgbaColor.g, newRgbaColor.b, newRgbaColor.a * 255.0];
// segmentationService.setSegmentColor(viewportId, segmentationId, segmentIndex, color);
// });
},
/**
*
* @param measurementData An array of measurements from the measurements service
@@ -54,7 +86,7 @@ const commandsModule = (props: withAppTypes) => {
* that you wish to serialize.
*/
downloadReport: ({ measurementData, additionalFindingTypes, options = {} }) => {
const srDataset = actions.generateReport(measurementData, additionalFindingTypes, options);
const srDataset = _generateReport(measurementData, additionalFindingTypes, options);
const reportBlob = dcmjs.data.datasetToBlob(srDataset);
//Create a URL for the binary.
@@ -98,12 +130,11 @@ const commandsModule = (props: withAppTypes) => {
throw new Error('Invalid report, no content');
}
const onBeforeDicomStore =
customizationService.getModeCustomization('onBeforeDicomStore')?.value;
const onBeforeDicomStore = customizationService.getCustomization('onBeforeDicomStore');
let dicomDict;
if (typeof onBeforeDicomStore === 'function') {
dicomDict = onBeforeDicomStore({ measurementData, naturalizedReport });
dicomDict = onBeforeDicomStore({ dicomDict, measurementData, naturalizedReport });
}
await dataSource.store.dicom(naturalizedReport, null, dicomDict);
@@ -131,18 +162,20 @@ const commandsModule = (props: withAppTypes) => {
*/
loadSRMeasurements: ({ displaySetInstanceUID }) => {
const { SeriesInstanceUIDs } = hydrateStructuredReport(
{ servicesManager, extensionManager },
{ servicesManager, extensionManager, commandsManager },
displaySetInstanceUID
);
const displaySets = displaySetService.getDisplaySetsForSeries(SeriesInstanceUIDs[0]);
if (displaySets.length) {
viewportGridService.setDisplaySetsForViewports([
{
viewportId: viewportGridService.getActiveViewportId(),
displaySetInstanceUIDs: [displaySets[0].displaySetInstanceUID],
},
]);
commandsManager.run('setDisplaySetsForViewports', {
viewportsToUpdate: [
{
viewportId: viewportGridService.getActiveViewportId(),
displaySetInstanceUIDs: [displaySets[0].displaySetInstanceUID],
},
],
});
}
},
};
@@ -5,10 +5,12 @@ import { ExtensionManager, useToolbar } from '@ohif/core';
import { setTrackingUniqueIdentifiersForElement } from '../tools/modules/dicomSRModule';
import { Icon, Tooltip, useViewportGrid, ViewportActionArrows } from '@ohif/ui';
import { ViewportActionArrows } from '@ohif/ui-next';
import createReferencedImageDisplaySet from '../utils/createReferencedImageDisplaySet';
import { usePositionPresentationStore } from '@ohif/extension-cornerstone';
import { Icons } from '@ohif/ui-next';
import { useViewportGrid } from '@ohif/ui-next';
import { Icons, Tooltip, TooltipTrigger, TooltipContent } from '@ohif/ui-next';
const MEASUREMENT_TRACKING_EXTENSION_ID = '@ohif/extension-measurement-tracking';
const SR_TOOLGROUP_BASE_NAME = 'SRToolGroup';
@@ -389,7 +391,12 @@ function _getStatusComponent({
switch (state) {
case 1:
StatusIcon = () => <Icons.ByName name="status-alert" />;
StatusIcon = () => (
<Icons.ByName
name="status-alert"
className="h-4 w-4"
/>
);
ToolTipMessage = () => (
<div>
@@ -400,7 +407,12 @@ function _getStatusComponent({
);
break;
case 2:
StatusIcon = () => <Icons.ByName name="status-locked" />;
StatusIcon = () => (
<Icons.ByName
name="status-locked"
className="h-4 w-4"
/>
);
ToolTipMessage = () => (
<div>
@@ -415,7 +427,7 @@ function _getStatusComponent({
case 3:
StatusIcon = () => (
<Icons.ByName
className="text-aqua-pale"
className="text-muted-foreground h-4 w-4"
name="status-untracked"
/>
);
@@ -437,7 +449,7 @@ function _getStatusComponent({
return (
<div className="flex h-6 cursor-default text-sm leading-6 text-white">
<div className="bg-customgray-100 flex min-w-[45px] items-center rounded-l-xl rounded-r p-1">
<StatusIcon />
<StatusIcon className="h-4 w-4" />
<span className="ml-1">SR</span>
</div>
{state === 3 && (
@@ -467,11 +479,18 @@ function _getStatusComponent({
return (
<>
{ToolTipMessage && (
<Tooltip
content={<ToolTipMessage />}
position="bottom-left"
>
<StatusArea />
<Tooltip>
<TooltipTrigger asChild>
<span>
<StatusArea />
</span>
</TooltipTrigger>
<TooltipContent
side="bottom"
align="start"
>
<ToolTipMessage />
</TooltipContent>
</Tooltip>
)}
{!ToolTipMessage && <StatusArea />}
@@ -90,6 +90,7 @@ function _getDisplaySetsFromSeries(
SeriesDescription,
SeriesNumber,
SeriesDate,
SeriesTime,
ConceptNameCodeSequence,
SOPClassUID,
} = instance;
@@ -106,6 +107,7 @@ function _getDisplaySetsFromSeries(
SeriesDescription,
SeriesNumber,
SeriesDate,
SeriesTime,
SOPInstanceUID,
SeriesInstanceUID,
StudyInstanceUID,
@@ -263,9 +265,9 @@ function _checkIfCanAddMeasurementsToDisplaySet(
for (let j = unloadedMeasurements.length - 1; j >= 0; j--) {
let measurement = unloadedMeasurements[j];
const onBeforeSRAddMeasurement = customizationService.getModeCustomization(
const onBeforeSRAddMeasurement = customizationService.getCustomization(
'onBeforeSRAddMeasurement'
)?.value;
);
if (typeof onBeforeSRAddMeasurement === 'function') {
measurement = onBeforeSRAddMeasurement({
@@ -77,28 +77,4 @@ export default function init({
SRRectangleROI: dashedLine,
global: {},
});
measurementService.subscribe(
MeasurementService.EVENTS.JUMP_TO_MEASUREMENT_LAYOUT,
({ viewportId, measurement, isConsumed }) => {
if (isConsumed) {
return;
}
try {
const currentViewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
const { viewPlaneNormal } = currentViewport.getCamera();
const referencedImageId = csToolsUtils.getClosestImageIdForStackViewport(
currentViewport as StackViewport,
measurement.points[0],
viewPlaneNormal
);
const imageIndex = (currentViewport as StackViewport)
.getImageIds()
.indexOf(referencedImageId);
csUtils.jumpToSlice(currentViewport.element, { imageIndex });
} catch (error) {
console.warn('Unable to jump to image based on measurement coordinate', error);
}
}
);
}
@@ -1,7 +1,7 @@
import React from 'react';
import { SOPClassHandlerId, SOPClassHandlerId3D } from './id';
import { ViewportActionButton } from '@ohif/ui';
import { ViewportActionButton } from '@ohif/ui-next';
import i18n from '@ohif/i18n';
export default function onModeEnter({ servicesManager }) {
@@ -25,7 +25,7 @@ const contentItemFormatters = {
return `${NumericValue} ${CodeValue}`;
},
PNAME: contentItem => {
const personName = contentItem.PersonName?.[0]?.Alphabetic;
const personName = contentItem.PersonName?.[0];
return personName ? utils.formatPN(personName) : undefined;
},
DATE: contentItem => {
@@ -41,22 +41,14 @@ const convertSites = (codingValues, sites) => {
*
*/
export default function hydrateStructuredReport(
{ servicesManager, extensionManager }: withAppTypes,
{ servicesManager, extensionManager, commandsManager }: withAppTypes,
displaySetInstanceUID
) {
const annotationManager = CsAnnotation.state.getAnnotationManager();
const dataSource = extensionManager.getActiveDataSource()[0];
const { measurementService, displaySetService, customizationService } = servicesManager.services;
const codingValues = customizationService.getCustomization('codingValues', {});
const { disableEditing } = customizationService.getCustomization(
'PanelMeasurement.disableEditing',
{
id: 'default.disableEditing',
disableEditing: false,
}
);
const codingValues = customizationService.getCustomization('codingValues');
const disableEditing = customizationService.getCustomization('panelMeasurement.disableEditing');
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
@@ -93,7 +85,8 @@ export default function hydrateStructuredReport(
}
});
const datasetToUse = _mapLegacyDataSet(instance);
// Mapping of legacy datasets is now directly handled by adapters module
const datasetToUse = instance;
// Use dcmjs to generate toolState.
let storedMeasurementByAnnotationType = MeasurementReport.generateToolState(
@@ -107,8 +100,7 @@ export default function hydrateStructuredReport(
metaData
);
const onBeforeSRHydration =
customizationService.getModeCustomization('onBeforeSRHydration')?.value;
const onBeforeSRHydration = customizationService.getCustomization('onBeforeSRHydration')?.value;
if (typeof onBeforeSRHydration === 'function') {
storedMeasurementByAnnotationType = onBeforeSRHydration({
@@ -208,7 +200,11 @@ export default function hydrateStructuredReport(
annotation.data.label = getLabelFromDCMJSImportedToolData(toolData);
annotation.data.finding = convertCode(codingValues, toolData.finding?.[0]);
annotation.data.findingSites = convertSites(codingValues, toolData.findingSites);
annotation.data.site = annotation.data.findingSites?.[0];
annotation.data.findingSites?.forEach(site => {
if (site.type) {
annotation.data[site.type] = site;
}
});
const matchingMapping = mappings.find(m => m.annotationType === annotationType);
@@ -220,9 +216,13 @@ export default function hydrateStructuredReport(
dataSource
);
commandsManager.runCommand('updateMeasurement', {
uid: newAnnotationUID,
code: annotation.data.finding,
});
if (disableEditing) {
const addedAnnotation = annotationManager.getAnnotation(newAnnotationUID);
locking.setAnnotationLocked(addedAnnotation, true);
locking.setAnnotationLocked(newAnnotationUID, true);
}
if (!imageIds.includes(imageId)) {
@@ -238,62 +238,3 @@ export default function hydrateStructuredReport(
SeriesInstanceUIDs,
};
}
function _mapLegacyDataSet(dataset) {
const REPORT = 'Imaging Measurements';
const GROUP = 'Measurement Group';
const TRACKING_IDENTIFIER = 'Tracking Identifier';
// Identify the Imaging Measurements
const imagingMeasurementContent = toArray(dataset.ContentSequence).find(
codeMeaningEquals(REPORT)
);
// Retrieve the Measurements themselves
const measurementGroups = toArray(imagingMeasurementContent.ContentSequence).filter(
codeMeaningEquals(GROUP)
);
// 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, index) => {
const measurementGroupContentSequence = toArray(measurementGroup.ContentSequence);
const TrackingIdentifierGroup = measurementGroupContentSequence.find(
contentItem => contentItem.ConceptNameCodeSequence.CodeMeaning === TRACKING_IDENTIFIER
);
const TrackingIdentifier = TrackingIdentifierGroup.TextValue;
let [cornerstoneTag, toolName] = TrackingIdentifier.split(':');
if (supportedLegacyCornerstoneTags.includes(cornerstoneTag)) {
cornerstoneTag = CORNERSTONE_3D_TAG;
}
const mappedTrackingIdentifier = `${cornerstoneTag}:${toolName}`;
TrackingIdentifierGroup.TextValue = mappedTrackingIdentifier;
});
return dataset;
}
const toArray = function (x) {
return Array.isArray(x) ? x : [x];
};
const codeMeaningEquals = codeMeaningName => {
return contentItem => {
return contentItem.ConceptNameCodeSequence.CodeMeaning === codeMeaningName;
};
};
@@ -1,60 +0,0 @@
import { adaptersSR } from '@cornerstonejs/adapters';
const cornerstoneAdapters =
adaptersSR.Cornerstone3D.MeasurementReport.CORNERSTONE_TOOL_CLASSES_BY_UTILITY_TYPE;
const supportedLegacyCornerstoneTags = ['cornerstoneTools@^4.0.0'];
const CORNERSTONE_3D_TAG = adaptersSR.Cornerstone3D.CORNERSTONE_3D_TAG;
/**
* Checks if the given `displaySet`can be rehydrated into the `measurementService`.
*
* @param {object} displaySet The SR `displaySet` to check.
* @param {object[]} mappings The CornerstoneTools 4 mappings to the `measurementService`.
* @returns {boolean} True if the SR can be rehydrated into the `measurementService`.
*/
export default function isRehydratable(displaySet, mappings) {
if (!mappings || !mappings.length) {
return false;
}
const mappingDefinitions = mappings.map(m => m.annotationType);
const { measurements } = displaySet;
const adapterKeys = Object.keys(cornerstoneAdapters).filter(
adapterKey =>
typeof cornerstoneAdapters[adapterKey].isValidCornerstoneTrackingIdentifier === 'function'
);
const adapters = [];
adapterKeys.forEach(key => {
if (mappingDefinitions.includes(key)) {
// Must have both a dcmjs adapter and a measurementService
// Definition in order to be a candidate for import.
adapters.push(cornerstoneAdapters[key]);
}
});
for (let i = 0; i < measurements.length; i++) {
const { TrackingIdentifier } = measurements[i] || {};
const hydratable = adapters.some(adapter => {
let [cornerstoneTag, toolName] = TrackingIdentifier.split(':');
if (supportedLegacyCornerstoneTags.includes(cornerstoneTag)) {
cornerstoneTag = CORNERSTONE_3D_TAG;
}
const mappedTrackingIdentifier = `${cornerstoneTag}:${toolName}`;
return adapter.isValidCornerstoneTrackingIdentifier(mappedTrackingIdentifier);
});
if (hydratable) {
return true;
}
console.log('Measurement is not rehydratable', TrackingIdentifier, measurements[i]);
}
console.log('No measurements found which were rehydratable');
return false;
}
@@ -0,0 +1,41 @@
import { adaptersSR } from '@cornerstonejs/adapters';
const { MeasurementReport } = adaptersSR.Cornerstone3D;
/**
* Checks if the given `displaySet`can be rehydrated into the `measurementService`.
*
* @param {object} displaySet The SR `displaySet` to check.
* @param {object[]} mappings The CornerstoneTools 4 mappings to the `measurementService`.
* @returns {boolean} True if the SR can be rehydrated into the `measurementService`.
*/
export default function isRehydratable(displaySet, mappings) {
if (!mappings || !mappings.length) {
return false;
}
const mappingDefinitions = new Set<string>();
for (const m of mappings) {
mappingDefinitions.add(m.annotationType);
}
const { measurements } = displaySet;
for (let i = 0; i < measurements.length; i++) {
const { TrackingIdentifier } = measurements[i] || {};
if (!TrackingIdentifier) {
console.warn('No tracking identifier for measurement ', measurements[i]);
continue;
}
const adapter = MeasurementReport.getAdapterForTrackingIdentifier(TrackingIdentifier);
const hydratable = adapter && mappingDefinitions.has(adapter.toolType);
if (hydratable) {
return true;
}
console.log('Measurement is not rehydratable', TrackingIdentifier, measurements[i]);
}
console.log('No measurements found which were rehydratable');
return false;
}
File diff suppressed because it is too large. Load diff
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-cornerstone-dynamic-volume",
"version": "3.10.0-beta.38",
"version": "3.10.2",
"description": "OHIF extension for 4D volumes data",
"author": "OHIF",
"license": "MIT",
@@ -29,11 +29,11 @@
"test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests"
},
"peerDependencies": {
"@ohif/core": "3.10.0-beta.38",
"@ohif/extension-cornerstone": "3.10.0-beta.38",
"@ohif/extension-default": "3.10.0-beta.38",
"@ohif/i18n": "3.10.0-beta.38",
"@ohif/ui": "3.10.0-beta.38",
"@ohif/core": "3.10.2",
"@ohif/extension-cornerstone": "3.10.2",
"@ohif/extension-default": "3.10.2",
"@ohif/i18n": "3.10.2",
"@ohif/ui": "3.10.2",
"dcmjs": "*",
"dicom-parser": "^1.8.21",
"hammerjs": "^2.0.8",
@@ -42,8 +42,8 @@
},
"dependencies": {
"@babel/runtime": "^7.20.13",
"@cornerstonejs/core": "^2.14.2",
"@cornerstonejs/tools": "^2.14.2",
"@cornerstonejs/core": "^3.11.6",
"@cornerstonejs/tools": "^3.11.6",
"classnames": "^2.3.2"
}
}
@@ -262,7 +262,6 @@ function _getInstanceFromSegmentations(segmentations, { servicesManager }) {
}
function updateSegmentationsChartDisplaySet({ servicesManager }: withAppTypes): void {
debugger;
const { segmentationService } = servicesManager.services;
const segmentations = segmentationService.getSegmentations();
const { seriesMetadata, instance } =
@@ -263,7 +263,7 @@ const commandsModule = ({ commandsManager, servicesManager }: withAppTypes) => {
}
}
viewportGridService.setDisplaySetsForViewports(viewportsToUpdate);
commandsManager.run('setDisplaySetsForViewports', { viewportsToUpdate });
},
swapComputedWithDynamicDisplaySet: () => {
// Todo: this assumes there is only one dynamic display set in the viewer
@@ -313,7 +313,7 @@ const commandsModule = ({ commandsManager, servicesManager }: withAppTypes) => {
}
}
viewportGridService.setDisplaySetsForViewports(viewportsToUpdate);
commandsManager.run('setDisplaySetsForViewports', { viewportsToUpdate });
},
createNewLabelMapForDynamicVolume: async ({ label }) => {
const { viewports, activeViewportId } = viewportGridService.getState();
@@ -1,6 +1,6 @@
import React from 'react';
import { DynamicDataPanel } from './panels';
import { Toolbox } from '@ohif/ui-next';
import { Toolbox } from '@ohif/extension-default';
import { PanelSegmentation } from '@ohif/extension-cornerstone';
import DynamicExport from './panels/DynamicExport';
@@ -19,9 +19,6 @@ function getPanelModule({ commandsManager, extensionManager, servicesManager, co
return (
<>
<Toolbox
commandsManager={commandsManager}
servicesManager={servicesManager}
extensionManager={extensionManager}
buttonSectionId="dynamic-toolbox"
title="Threshold Tools"
/>
@@ -1,30 +1,45 @@
import React, { useState } from 'react';
import { Button, PanelSection, ButtonGroup, IconButton, InputNumber, Tooltip } from '@ohif/ui';
import { DoubleSlider, Icons } from '@ohif/ui-next';
import {
Tabs,
TabsList,
TabsTrigger,
Button,
PanelSection,
Icons,
Tooltip,
TooltipTrigger,
TooltipContent,
Numeric,
} from '@ohif/ui-next';
import { Enums } from '@cornerstonejs/core';
const controlClassNames = {
sizeClassName: 'w-[58px] h-[28px]',
arrowsDirection: 'horizontal',
labelPosition: 'bottom',
// Helper function to safely convert any value to uppercase string
const toUpperCaseString = value => {
if (value === null || value === undefined) {
return '';
}
return String(value).toUpperCase();
};
const Header = ({ title, tooltip }) => (
<div className="flex items-center space-x-1">
<Tooltip
content={<div className="text-white">{tooltip}</div>}
position="bottom-left"
tight={true}
tooltipBoxClassName="max-w-xs p-2"
>
<Icons.ByName
name="info-link"
className="text-primary-active h-[14px] w-[14px]"
/>
<Tooltip>
<TooltipTrigger asChild>
<span>
<Icons.ByName
name="info-link"
className="text-primary h-3 w-3"
/>
</span>
</TooltipTrigger>
<TooltipContent
sideOffset={4}
className="max-w-xs"
>
<div>{tooltip}</div>
</TooltipContent>
</Tooltip>
<span className="text-aqua-pale text-[11px] uppercase">{title}</span>
<span className="text-muted-foreground text-xs uppercase tracking-wide">{title}</span>
</div>
);
@@ -37,132 +52,149 @@ const DynamicVolumeControls = ({
minFps,
maxFps,
// Frames
currentFrameIndex,
onFrameChange,
framesLength,
currentDimensionGroupNumber,
onDimensionGroupChange,
numDimensionGroups,
onGenerate,
onDoubleRangeChange,
rangeValues,
onDynamicClick,
}) => {
const [computedView, setComputedView] = useState(false);
const [computeViewMode, setComputeViewMode] = useState(Enums.DynamicOperatorType.SUM);
const [sliderRangeValues, setSliderRangeValues] = useState([0, framesLength - 1]);
const handleSliderChange = newValues => {
onDoubleRangeChange(newValues);
setSliderRangeValues(newValues);
// Wrapper for onGenerate to handle potential errors
const handleGenerate = () => {
try {
if (typeof onGenerate === 'function') {
onGenerate(computeViewMode);
} else {
console.error('onGenerate is not a function', onGenerate);
}
} catch (error) {
console.error('Error in onGenerate:', error);
}
};
const formatLabel = value => Math.round(value);
return (
<div className="flex select-none flex-col">
<PanelSection
title="Controls"
childrenClassName="space-y-4 pb-5 px-5"
>
<div className="mt-2">
<Header
title="View"
tooltip={
'Select the view mode, 4D to view the dynamic volume or Computed to view the computed volume'
}
/>
<ButtonGroup className="mt-2 w-full">
<button
className="w-1/2"
onClick={() => {
setComputedView(false);
onDynamicClick?.();
<PanelSection defaultOpen={true}>
<PanelSection.Header>Controls</PanelSection.Header>
<PanelSection.Content className="bg-muted space-y-4 px-5 pt-2 pb-4">
<div className="mb-4">
<Header
title="View"
tooltip={
'Select the view mode, 4D to view the dynamic volume or Computed to view the computed volume'
}
/>
<Tabs
value={computedView ? 'computed' : '4d'}
onValueChange={value => {
const isComputed = value === 'computed';
setComputedView(isComputed);
if (!isComputed && typeof onDynamicClick === 'function') {
onDynamicClick();
}
}}
className="my-2 w-full"
>
4D
</button>
<button
className="w-1/2"
onClick={() => {
setComputedView(true);
}}
>
Computed
</button>
</ButtonGroup>
</div>
<div>
<FrameControls
onPlayPauseChange={onPlayPauseChange}
isPlaying={isPlaying}
computedView={computedView}
// fps
fps={fps}
onFpsChange={onFpsChange}
minFps={minFps}
maxFps={maxFps}
//
framesLength={framesLength}
onFrameChange={onFrameChange}
currentFrameIndex={currentFrameIndex}
/>
</div>
<div className={`mt-6 flex flex-col ${computedView ? '' : 'ohif-disabled'}`}>
<Header
title="Computed Operation"
tooltip={
<div>
Operation Buttons (SUM, AVERAGE, SUBTRACT): Select the mathematical operation to be
applied to the data set.
<br></br> Range Slider: Choose the numeric range within which the operation will be
performed.
<br></br>Generate Button: Execute the chosen operation on the specified range of
data.{' '}
</div>
}
/>
<ButtonGroup
className={`mt-2 w-full`}
separated={true}
>
<button
className="w-1/2"
onClick={() => setComputeViewMode(Enums.DynamicOperatorType.SUM)}
>
{Enums.DynamicOperatorType.SUM.toString().toUpperCase()}
</button>
<button
className="w-1/2"
onClick={() => setComputeViewMode(Enums.DynamicOperatorType.AVERAGE)}
>
{Enums.DynamicOperatorType.AVERAGE.toString().toUpperCase()}
</button>
<button
className="w-1/2"
onClick={() => setComputeViewMode(Enums.DynamicOperatorType.SUBTRACT)}
>
{Enums.DynamicOperatorType.SUBTRACT.toString().toUpperCase()}
</button>
</ButtonGroup>
<div className="mt-2 w-full">
<DoubleSlider
min={0}
max={framesLength - 1}
step={1}
defaultValue={sliderRangeValues}
onValueChange={handleSliderChange}
formatLabel={formatLabel}
className="w-full"
<TabsList className="w-full">
<TabsTrigger
value="4d"
className="w-1/2"
>
4D
</TabsTrigger>
<TabsTrigger
value="computed"
className="w-1/2"
>
Computed
</TabsTrigger>
</TabsList>
</Tabs>
</div>
<div>
<DimensionGroupControls
onPlayPauseChange={onPlayPauseChange}
isPlaying={isPlaying}
computedView={computedView}
// fps
fps={fps}
onFpsChange={onFpsChange}
minFps={minFps}
maxFps={maxFps}
//
numDimensionGroups={numDimensionGroups}
onDimensionGroupChange={onDimensionGroupChange}
currentDimensionGroupNumber={currentDimensionGroupNumber}
/>
</div>
<Button
className="mt-2 !h-[26px] !w-[115px] self-start !p-0"
onClick={() => {
onGenerate(computeViewMode);
}}
>
Generate
</Button>
</div>
<div className={`mt-3 flex flex-col ${computedView ? '' : 'ohif-disabled'}`}>
<Header
title="Computed Operation"
tooltip={
<div>
Operation Buttons (SUM, AVERAGE, SUBTRACT): Select the mathematical operation to
be applied to the data set.
<br /> Range Slider: Choose the numeric range of dimension groups within which the
operation will be performed.
<br />
Generate Button: Execute the chosen operation on the specified range of data.
</div>
}
/>
<Tabs
value={String(computeViewMode)}
onValueChange={value => {
setComputeViewMode(value);
}}
className="mt-2 w-full"
>
<TabsList className="w-full gap-1">
{' '}
<TabsTrigger
value={String(Enums.DynamicOperatorType.SUM)}
className="w-1/3"
>
{toUpperCaseString(Enums.DynamicOperatorType.SUM)}
</TabsTrigger>
<TabsTrigger
value={String(Enums.DynamicOperatorType.AVERAGE)}
className="w-1/3"
>
{toUpperCaseString(Enums.DynamicOperatorType.AVERAGE)}
</TabsTrigger>
<TabsTrigger
value={String(Enums.DynamicOperatorType.SUBTRACT)}
className="w-1/3"
>
{toUpperCaseString(Enums.DynamicOperatorType.SUBTRACT)}
</TabsTrigger>
</TabsList>
</Tabs>
<div className="mt-2 w-full">
<Numeric.Container
mode="doubleRange"
min={1}
max={numDimensionGroups || 1}
values={rangeValues || [1, numDimensionGroups || 1]}
onChange={onDoubleRangeChange || (() => {})}
>
<Numeric.DoubleRange showNumberInputs />
</Numeric.Container>
</div>
<Button
variant="default"
size="sm"
className="mt-2 h-[26px] w-[115px] self-start p-0"
onClick={handleGenerate}
>
Generate
</Button>
</div>
</PanelSection.Content>
</PanelSection>
</div>
);
@@ -170,58 +202,84 @@ const DynamicVolumeControls = ({
export default DynamicVolumeControls;
function FrameControls({
function DimensionGroupControls({
isPlaying,
onPlayPauseChange,
fps,
minFps,
maxFps,
onFpsChange,
framesLength,
onFrameChange,
currentFrameIndex,
numDimensionGroups,
onDimensionGroupChange,
currentDimensionGroupNumber,
computedView,
}) {
const getPlayPauseIconName = () => (isPlaying ? 'icon-pause' : 'icon-play');
return (
<div className={computedView && 'ohif-disabled'}>
<div className={computedView ? 'ohif-disabled' : ''}>
<Header
title="4D Controls"
tooltip={
<div>
Play/Pause Button: Begin or pause the animation of the 4D visualization. <br></br> Frame
Selector: Navigate through individual frames of the 4D data. <br></br> FPS (Frames Per
Second) Selector: Adjust the playback speed of the animation.
Play/Pause Button: Begin or pause the animation of the 4D visualization. <br />
Dimension Group Selector: Navigate through individual dimension groups of the 4D data.{' '}
<br />
FPS (Frames Per Second) Selector: Adjust the playback speed of the animation.
</div>
}
/>
<div className="mt-3 flex justify-between">
<IconButton
className="bg-customblue-30 h-[26px] w-[58px] rounded-[4px]"
onClick={() => onPlayPauseChange(!isPlaying)}
<Button
id="play-pause-button"
variant="secondary"
size="default"
className="w-[58px]"
onClick={() => {
if (typeof onPlayPauseChange === 'function') {
onPlayPauseChange(!isPlaying);
}
}}
>
<Icons.ByName
name={getPlayPauseIconName()}
className="active:text-primary-light hover:bg-customblue-300 h-[24px] w-[24px] cursor-pointer text-white"
className="text-foreground h-[24px] w-[24px]"
/>
</IconButton>
<InputNumber
value={currentFrameIndex}
onChange={onFrameChange}
minValue={0}
maxValue={framesLength - 1}
label="Frame"
{...controlClassNames}
/>
<InputNumber
value={fps}
onChange={onFpsChange}
minValue={minFps}
maxValue={maxFps}
{...controlClassNames}
label="FPS"
/>
</Button>
<Numeric.Container
mode="stepper"
value={currentDimensionGroupNumber || 1}
onChange={onDimensionGroupChange || (() => {})}
min={1}
max={numDimensionGroups || 1}
step={1}
>
<div className="flex flex-col items-center">
<Numeric.NumberStepper
className="h-[28px] w-[58px]"
direction="horizontal"
/>
<Numeric.Label className="text-muted-foreground mt-1 text-sm">Frame</Numeric.Label>
</div>
</Numeric.Container>
<Numeric.Container
mode="stepper"
value={fps || 1}
onChange={onFpsChange || (() => {})}
min={minFps || 1}
max={maxFps || 30}
step={1}
>
<div className="flex flex-col items-center">
<Numeric.NumberStepper
className="h-[28px] w-[58px]"
direction="horizontal"
/>
<Numeric.Label className="text-muted-foreground mt-1 text-sm">FPS</Numeric.Label>
</div>
</Numeric.Container>
</div>
</div>
);
@@ -1,71 +0,0 @@
import React from 'react';
import { InputDoubleRange } from '@ohif/ui';
import { Select } from '@ohif/ui';
import { Button } from '@ohif/ui';
import PropTypes from 'prop-types';
const GenerateVolume = ({
rangeValues,
handleSliderChange,
operationsUI,
options,
handleGenerateOptionsChange,
onGenerateImage,
returnTo4D,
displayingComputedVolume,
}) => {
return (
<>
<div>
<div className="mb-2 text-white">Computed Image</div>
<Select
closeMenuOnSelect={true}
className="border-primary-main mr-2 bg-black text-white "
options={operationsUI}
placeholder={operationsUI.find(option => option.value === options.Operation).placeHolder}
value={options.Operation}
onChange={({ value }) => {
handleGenerateOptionsChange({
Operation: value,
});
}}
/>
<InputDoubleRange
values={rangeValues}
onChange={handleSliderChange}
minValue={rangeValues[0] || 1}
maxValue={rangeValues[1] || 2}
showLabel={true}
step={1}
/>
<div className="flex space-x-2">
<Button
onClick={onGenerateImage}
className="w-1/2"
>
Generate
</Button>
<Button
onClick={returnTo4D}
disabled={!displayingComputedVolume}
className="w-1/2"
>
Return To 4D
</Button>
</div>
</div>
</>
);
};
GenerateVolume.propTypes = {
rangeValues: PropTypes.array.isRequired,
handleSliderChange: PropTypes.func.isRequired,
operationsUI: PropTypes.array.isRequired,
options: PropTypes.object.isRequired,
handleGenerateOptionsChange: PropTypes.func.isRequired,
onGenerateImage: PropTypes.func.isRequired,
returnTo4D: PropTypes.func.isRequired,
};
export default GenerateVolume;
@@ -1,5 +1,6 @@
import React, { useEffect, useState, useRef } from 'react';
import { useCine, useViewportGrid } from '@ohif/ui';
import { useCine } from '@ohif/ui-next';
import { useViewportGrid } from '@ohif/ui-next';
import { utilities as csUtils, volumeLoader, eventTarget, Enums, cache } from '@cornerstonejs/core';
import { utilities as cstUtils } from '@cornerstonejs/tools';
import DynamicVolumeControls from './DynamicVolumeControls';
@@ -14,13 +15,12 @@ export default function PanelGenerateImage({ servicesManager, commandsManager }:
const [{ activeViewportId }] = useViewportGrid();
//
const [timePointsRange, setTimePointsRange] = useState([0, 0]);
const [timePointsRangeToUseForGenerate, setTimePointsRangeToUseForGenerate] = useState([0, 0]);
const [dimensionGroupRange, setDimensionGroupRange] = useState([1, 1]);
const [computedDisplaySet, setComputedDisplaySet] = useState(null);
const [dynamicVolume, setDynamicVolume] = useState(null);
const [frameRate, setFrameRate] = useState(20);
const [isPlaying, setIsPlaying] = useState(isCineEnabled);
const [timePointRendered, setTimePointRendered] = useState(null);
const [dimensionGroupNumberRendered, setDimensionGroupNumberRendered] = useState(null);
const [displayingComputed, setDisplayingComputed] = useState(false);
//
@@ -37,9 +37,8 @@ export default function PanelGenerateImage({ servicesManager, commandsManager }:
if (volumeData.volume?.isDynamicVolume()) {
setDynamicVolume(volumeData.volume);
uuidDynamicVolume.current = volumeData.displaySetInstanceUID;
const newRange = [1, volumeData.volume.numTimePoints];
setTimePointsRange(newRange);
setTimePointsRangeToUseForGenerate(newRange);
const newRange = [1, volumeData.volume.numDimensionGroups];
setDimensionGroupRange(newRange);
}
});
};
@@ -64,10 +63,10 @@ export default function PanelGenerateImage({ servicesManager, commandsManager }:
}, [cornerstoneViewportService, cineService, servicesManager.services.cineService]);
useEffect(() => {
const evt = Enums.Events.DYNAMIC_VOLUME_TIME_POINT_INDEX_CHANGED;
const evt = Enums.Events.DYNAMIC_VOLUME_DIMENSION_GROUP_CHANGED;
const callback = evt => {
setTimePointRendered(evt.detail.timePointIndex);
setDimensionGroupNumberRendered(evt.detail.dimensionGroupNumber);
};
eventTarget.addEventListener(evt, callback);
@@ -75,7 +74,7 @@ export default function PanelGenerateImage({ servicesManager, commandsManager }:
return () => {
eventTarget.removeEventListener(evt, callback);
};
}, [cornerstoneViewportService]);
}, []);
useEffect(() => {
const displaySetUIDs = viewportGridService.getDisplaySetsUIDsForViewport(activeViewportId);
@@ -101,9 +100,7 @@ export default function PanelGenerateImage({ servicesManager, commandsManager }:
setDynamicVolume(dynamicVolume);
uuidDynamicVolume.current = dynamicVolumeDisplaySet.displaySetInstanceUID;
const newRange = [1, dynamicVolume.numTimePoints];
setTimePointsRange(newRange);
setTimePointsRangeToUseForGenerate(newRange);
setDimensionGroupRange([1, dynamicVolume.numDimensionGroups]);
}, [
activeViewportId,
viewportGridService,
@@ -140,11 +137,12 @@ export default function PanelGenerateImage({ servicesManager, commandsManager }:
volumeId: computedVolumeId,
});
}
const [start, end] = timePointsRangeToUseForGenerate;
const frameNumbers = Array.from({ length: end - start + 1 }, (_, i) => i + start - 1);
const [start, end] = dimensionGroupRange;
// from start to end, with steps of 1
const frameNumbers = Array.from({ length: end - start + 1 }, (_, i) => start + i);
const options = {
frameNumbers: operationName === 'SUBTRACT' ? [start, end - 1] : frameNumbers,
dimensionGroupNumbers: operationName === 'SUBTRACT' ? [start, end] : frameNumbers,
targetVolume: computedVolume,
};
@@ -206,21 +204,6 @@ export default function PanelGenerateImage({ servicesManager, commandsManager }:
handlePlay();
};
function handleSliderChange(newValues) {
if (
newValues[0] === timePointsRangeToUseForGenerate[0] &&
newValues[1] === timePointsRangeToUseForGenerate[1]
) {
return;
}
setTimePointsRangeToUseForGenerate(newValues);
}
if (!dynamicVolume || timePointsRange.length === 0) {
return null;
}
return (
<DynamicVolumeControls
fps={frameRate}
@@ -228,16 +211,16 @@ export default function PanelGenerateImage({ servicesManager, commandsManager }:
onPlayPauseChange={onPlayPauseChange}
minFps={1}
maxFps={50}
currentFrameIndex={timePointRendered}
onFpsChange={handleSetFrameRate}
framesLength={timePointsRange[1]}
onFrameChange={timePointIndex => {
dynamicVolume.timePointIndex = timePointIndex;
currentDimensionGroupNumber={dimensionGroupNumberRendered}
numDimensionGroups={dynamicVolume?.numDimensionGroups || 1}
onDimensionGroupChange={dimensionGroupNumber => {
dynamicVolume.dimensionGroupNumber = dimensionGroupNumber;
}}
onGenerate={onGenerateImage}
onDynamicClick={displayingComputed ? () => renderDynamicImage(computedDisplaySet) : null}
onDoubleRangeChange={handleSliderChange}
initialRangeValues={timePointsRangeToUseForGenerate}
onDoubleRangeChange={setDimensionGroupRange}
rangeValues={dimensionGroupRange}
/>
);
}
@@ -5,7 +5,7 @@ function WorkflowPanel({ servicesManager }: { servicesManager: ServicesManager }
const ProgressDropdownWithService =
servicesManager.services.customizationService.getCustomization(
'progressDropdownWithServiceComponent'
).component;
);
return (
<div
File diff suppressed because it is too large. Load diff
+15 -13
View File
@@ -1,6 +1,6 @@
{
"name": "@ohif/extension-cornerstone",
"version": "3.10.0-beta.38",
"version": "3.10.2",
"description": "OHIF extension for Cornerstone",
"author": "OHIF",
"license": "MIT",
@@ -38,10 +38,9 @@
"@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2",
"@cornerstonejs/codec-openjpeg": "^1.2.4",
"@cornerstonejs/codec-openjph": "^2.4.5",
"@cornerstonejs/dicom-image-loader": "^2.14.2",
"@icr/polyseg-wasm": "^0.4.0",
"@ohif/core": "3.10.0-beta.38",
"@ohif/ui": "3.10.0-beta.38",
"@cornerstonejs/dicom-image-loader": "^3.11.6",
"@ohif/core": "3.10.2",
"@ohif/ui": "3.10.2",
"dcmjs": "*",
"dicom-parser": "^1.8.21",
"hammerjs": "^2.0.8",
@@ -50,19 +49,22 @@
"react-dom": "^18.3.1",
"react-resize-detector": "^10.0.1"
},
"devDependencies": {
"lodash": "^4.17.21"
},
"dependencies": {
"@babel/runtime": "^7.20.13",
"@cornerstonejs/adapters": "^2.14.2",
"@cornerstonejs/core": "^2.14.2",
"@cornerstonejs/tools": "^2.14.2",
"@icr/polyseg-wasm": "^0.4.0",
"@kitware/vtk.js": "32.1.1",
"@cornerstonejs/adapters": "^3.11.6",
"@cornerstonejs/ai": "^3.11.6",
"@cornerstonejs/core": "^3.11.6",
"@cornerstonejs/labelmap-interpolation": "^3.11.6",
"@cornerstonejs/polymorphic-segmentation": "^3.11.6",
"@cornerstonejs/tools": "^3.11.6",
"@itk-wasm/morphological-contour-interpolation": "1.1.0",
"@kitware/vtk.js": "32.12.0",
"html2canvas": "^1.4.1",
"lodash.compact": "^3.0.1",
"lodash.debounce": "^4.0.8",
"lodash.flatten": "^4.4.0",
"lodash.merge": "^4.6.2",
"lodash.zip": "^4.2.0",
"shader-loader": "^1.3.1",
"worker-loader": "^3.0.8"
}
@@ -3,7 +3,8 @@ import { useResizeDetector } from 'react-resize-detector';
import * as cs3DTools from '@cornerstonejs/tools';
import { Enums, eventTarget, getEnabledElement } from '@cornerstonejs/core';
import { MeasurementService } from '@ohif/core';
import { AllInOneMenu, Notification, useViewportDialog } from '@ohif/ui';
import { AllInOneMenu } from '@ohif/ui-next';
import { useViewportDialog } from '@ohif/ui-next';
import type { Types as csTypes } from '@cornerstonejs/core';
import { setEnabledElement } from '../state';
@@ -15,20 +16,14 @@ import type { Types } from '@ohif/core';
import OHIFViewportActionCorners from '../components/OHIFViewportActionCorners';
import { getWindowLevelActionMenu } from '../components/WindowLevelActionMenu/getWindowLevelActionMenu';
import { useAppConfig } from '@state';
import { getViewportDataOverlaySettingsMenu } from '../components/ViewportDataOverlaySettingMenu';
import { getViewportPresentations } from '../utils/presentations/getViewportPresentations';
import { useSynchronizersStore } from '../stores/useSynchronizersStore';
import ActiveViewportBehavior from '../utils/ActiveViewportBehavior';
import { WITH_NAVIGATION } from '../services/ViewportService/CornerstoneViewportService';
const STACK = 'stack';
/**
* Caches the jump to measurement operation, so that if display set is shown,
* it can jump to the measurement.
*/
let cacheJumpToMeasurementEvent;
// Todo: This should be done with expose of internal API similar to react-vtkjs-viewport
// Then we don't need to worry about the re-renders if the props change.
const OHIFCornerstoneViewport = React.memo(
@@ -88,7 +83,6 @@ const OHIFCornerstoneViewport = React.memo(
const [scrollbarHeight, setScrollbarHeight] = useState('100px');
const [enabledVPElement, setEnabledVPElement] = useState(null);
const elementRef = useRef() as React.MutableRefObject<HTMLDivElement>;
const [appConfig] = useAppConfig();
const {
displaySetService,
@@ -99,12 +93,14 @@ const OHIFCornerstoneViewport = React.memo(
segmentationService,
cornerstoneCacheService,
viewportActionCornersService,
customizationService,
measurementService,
} = servicesManager.services;
const [viewportDialogState] = useViewportDialog();
// useCallback for scroll bar height calculation
const setImageScrollBarHeight = useCallback(() => {
const scrollbarHeight = `${elementRef.current.clientHeight - 40}px`;
const scrollbarHeight = `${elementRef.current.clientHeight - 10}px`;
setScrollbarHeight(scrollbarHeight);
}, [elementRef]);
@@ -146,6 +142,11 @@ const OHIFCornerstoneViewport = React.memo(
const { viewportId, element } = evt.detail;
const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId);
if (!viewportInfo) {
return;
}
setEnabledElement(viewportId, element);
setEnabledVPElement(element);
@@ -258,14 +259,6 @@ const OHIFCornerstoneViewport = React.memo(
const presentations = getViewportPresentations(viewportId, viewportOptions);
let measurement;
if (cacheJumpToMeasurementEvent?.viewportId === viewportId) {
measurement = cacheJumpToMeasurementEvent.measurement;
// Delete the position presentation so that viewport navigates direct
presentations.positionPresentation = null;
cacheJumpToMeasurementEvent = null;
}
// Note: This is a hack to get the grid to re-render the OHIFCornerstoneViewport component
// Used for segmentation hydration right now, since the logic to decide whether
// a viewport needs to render a segmentation lives inside the CornerstoneViewportService
@@ -283,10 +276,6 @@ const OHIFCornerstoneViewport = React.memo(
displaySetOptions,
presentations
);
if (measurement) {
cs3DTools.annotation.selection.setAnnotationSelected(measurement.uid);
}
};
loadViewportData();
@@ -307,72 +296,66 @@ const OHIFCornerstoneViewport = React.memo(
return;
}
const unsubscribeFromJumpToMeasurementEvents = _subscribeToJumpToMeasurementEvents(
elementRef,
viewportId,
servicesManager
const { unsubscribe } = measurementService.subscribe(
MeasurementService.EVENTS.JUMP_TO_MEASUREMENT_VIEWPORT,
event => handleJumpToMeasurement(event, elementRef, viewportId, cornerstoneViewportService)
);
_checkForCachedJumpToMeasurementEvents(elementRef, viewportId, displaySets, servicesManager);
return () => {
unsubscribeFromJumpToMeasurementEvents();
unsubscribe();
};
}, [displaySets, elementRef, viewportId, isJumpToMeasurementDisabled, servicesManager]);
// Set up the window level action menu in the viewport action corners.
useEffect(() => {
// Doing an === check here because the default config value when not set is true
if (appConfig.addWindowLevelActionMenu === false) {
return;
const windowLevelActionMenu = customizationService.getCustomization(
'viewportActionMenu.windowLevelActionMenu'
);
const segmentationOverlay = customizationService.getCustomization(
'viewportActionMenu.segmentationOverlay'
);
if (windowLevelActionMenu?.enabled) {
viewportActionCornersService.addComponent({
viewportId,
id: 'windowLevelActionMenu',
component: getWindowLevelActionMenu({
viewportId,
element: elementRef.current,
displaySets,
servicesManager,
commandsManager,
location: windowLevelActionMenu.location,
verticalDirection: AllInOneMenu.VerticalDirection.TopToBottom,
horizontalDirection: AllInOneMenu.HorizontalDirection.RightToLeft,
}),
location: windowLevelActionMenu.location,
});
}
const location = viewportActionCornersService.LOCATIONS.topRight;
// TODO: In the future we should consider using the customization service
// to determine if and in which corner various action components should go.
viewportActionCornersService.addComponent({
viewportId,
id: 'windowLevelActionMenu',
component: getWindowLevelActionMenu({
if (segmentationOverlay?.enabled) {
viewportActionCornersService.addComponent({
viewportId,
element: elementRef.current,
displaySets,
servicesManager,
commandsManager,
location,
verticalDirection: AllInOneMenu.VerticalDirection.TopToBottom,
horizontalDirection: AllInOneMenu.HorizontalDirection.RightToLeft,
}),
location,
});
viewportActionCornersService.addComponent({
viewportId,
id: 'segmentation',
component: getViewportDataOverlaySettingsMenu({
viewportId,
element: elementRef.current,
displaySets,
servicesManager,
commandsManager,
location,
}),
location,
});
}, [
displaySets,
viewportId,
viewportActionCornersService,
servicesManager,
commandsManager,
appConfig,
]);
id: 'segmentation',
component: getViewportDataOverlaySettingsMenu({
viewportId,
element: elementRef.current,
displaySets,
servicesManager,
commandsManager,
location: segmentationOverlay.location,
}),
location: segmentationOverlay.location,
});
}
}, [displaySets, viewportId, viewportActionCornersService, servicesManager, commandsManager]);
const { ref: resizeRef } = useResizeDetector({
onResize,
});
const Notification = customizationService.getCustomization('ui.notificationComponent');
return (
<React.Fragment>
<div className="viewport-wrapper">
@@ -425,95 +408,52 @@ const OHIFCornerstoneViewport = React.memo(
areEqual
);
function _subscribeToJumpToMeasurementEvents(elementRef, viewportId, servicesManager) {
const { measurementService, cornerstoneViewportService } = servicesManager.services;
// Helper function to handle jumping to measurements
function handleJumpToMeasurement(event, elementRef, viewportId, cornerstoneViewportService) {
const { measurement, isConsumed } = event;
if (!measurement || isConsumed) {
return;
}
const { unsubscribe } = measurementService.subscribe(
MeasurementService.EVENTS.JUMP_TO_MEASUREMENT_VIEWPORT,
props => {
cacheJumpToMeasurementEvent = props;
const { viewportId: jumpId, measurement, isConsumed } = props;
if (!measurement || isConsumed) {
return;
}
if (cacheJumpToMeasurementEvent.cornerstoneViewport === undefined) {
// Decide on which viewport should handle this
cacheJumpToMeasurementEvent.cornerstoneViewport =
cornerstoneViewportService.getViewportIdToJump(jumpId, {
displaySetInstanceUID: measurement.displaySetInstanceUID,
...measurement.metadata,
referencedImageId:
measurement.referencedImageId || measurement.metadata?.referencedImageId,
});
}
if (cacheJumpToMeasurementEvent.cornerstoneViewport !== viewportId) {
return;
}
_jumpToMeasurement(measurement, elementRef, viewportId, servicesManager);
}
const enabledElement = getEnabledElement(elementRef.current);
if (!enabledElement) {
return;
}
const viewport = enabledElement.viewport as csTypes.IStackViewport | csTypes.IVolumeViewport;
const { metadata, displaySetInstanceUID } = measurement;
const viewportDisplaySets = cornerstoneViewportService.getViewportDisplaySets(viewportId);
const showingDisplaySet = viewportDisplaySets.find(
ds => ds.displaySetInstanceUID === displaySetInstanceUID
);
return unsubscribe;
}
let metadataToUse = metadata;
// if it is not showing the displaySet we need to remove the FOR from the metadata
if (!showingDisplaySet) {
metadataToUse = {
...metadata,
FrameOfReferenceUID: undefined,
};
}
// Check if there is a queued jumpToMeasurement event
function _checkForCachedJumpToMeasurementEvents(
elementRef,
viewportId,
displaySets,
servicesManager
) {
if (!cacheJumpToMeasurementEvent) {
return;
}
if (cacheJumpToMeasurementEvent.isConsumed) {
cacheJumpToMeasurementEvent = null;
return;
}
const displaysUIDs = displaySets.map(displaySet => displaySet.displaySetInstanceUID);
if (!displaysUIDs?.length) {
// Todo: make it work with cases where we want to define FOR based measurements too
if (!viewport.isReferenceViewable(metadataToUse, WITH_NAVIGATION)) {
return;
}
// Jump to measurement if the measurement exists
const { measurement } = cacheJumpToMeasurementEvent;
if (measurement && elementRef) {
if (displaysUIDs.includes(measurement?.displaySetInstanceUID)) {
_jumpToMeasurement(measurement, elementRef, viewportId, servicesManager);
}
}
}
function _jumpToMeasurement(measurement, targetElementRef, viewportId, servicesManager) {
const { viewportGridService } = servicesManager.services;
const targetElement = targetElementRef.current;
// Todo: setCornerstoneMeasurementActive should be handled by the toolGroupManager
// to set it properly
// setCornerstoneMeasurementActive(measurement);
viewportGridService.setActiveViewportId(viewportId);
const enabledElement = getEnabledElement(targetElement);
if (enabledElement) {
// See how the jumpToSlice() of Cornerstone3D deals with imageIdx param.
const viewport = enabledElement.viewport as csTypes.IStackViewport | csTypes.IVolumeViewport;
const { metadata } = measurement;
if (!viewport.isReferenceViewable(metadata, { withNavigation: true, withOrientation: true })) {
console.log("Reference isn't viewable, postponing until updated");
return;
}
try {
viewport.setViewReference(metadata);
cs3DTools.annotation.selection.setAnnotationSelected(measurement.uid);
// Jump to measurement consumed, remove.
cacheJumpToMeasurementEvent?.consume?.();
cacheJumpToMeasurementEvent = null;
viewport.render();
} catch (e) {
console.warn('Unable to apply', metadata, e);
}
cs3DTools.annotation.selection.setAnnotationSelected(measurement.uid);
event?.consume?.();
}
function _rehydrateSynchronizers(viewportId: string, syncGroupService: any) {
@@ -3,14 +3,16 @@ import { vec3 } from 'gl-matrix';
import PropTypes from 'prop-types';
import { metaData, Enums, utilities } from '@cornerstonejs/core';
import type { ImageSliceData } from '@cornerstonejs/core/types';
import { ViewportOverlay } from '@ohif/ui';
import { ViewportOverlay } from '@ohif/ui-next';
import type { InstanceMetadata } from '@ohif/core/src/types';
import { formatPN, formatDICOMDate, formatDICOMTime, formatNumberPrecision } from './utils';
import { formatDICOMDate, formatDICOMTime, formatNumberPrecision } from './utils';
import { utils } from '@ohif/core';
import { StackViewportData, VolumeViewportData } from '../../types/CornerstoneCacheService';
import './CustomizableViewportOverlay.css';
const EPSILON = 1e-4;
const { formatPN } = utils;
type ViewportData = StackViewportData | VolumeViewportData;
@@ -45,80 +47,6 @@ const OverlayItemComponents = {
'ohif.overlayItem.instanceNumber': InstanceNumberOverlayItem,
};
const studyDateItem = {
id: 'StudyDate',
customizationType: 'ohif.overlayItem',
label: '',
title: 'Study date',
condition: ({ referenceInstance }) => referenceInstance?.StudyDate,
contentF: ({ referenceInstance, formatters: { formatDate } }) =>
formatDate(referenceInstance.StudyDate),
};
const seriesDescriptionItem = {
id: 'SeriesDescription',
customizationType: 'ohif.overlayItem',
label: '',
title: 'Series description',
condition: ({ referenceInstance }) => {
return referenceInstance && referenceInstance.SeriesDescription;
},
contentF: ({ referenceInstance }) => referenceInstance.SeriesDescription,
};
const topLeftItems = {
id: 'cornerstoneOverlayTopLeft',
items: [studyDateItem, seriesDescriptionItem],
};
const topRightItems = { id: 'cornerstoneOverlayTopRight', items: [] };
const bottomLeftItems = {
id: 'cornerstoneOverlayBottomLeft',
items: [
{
id: 'WindowLevel',
customizationType: 'ohif.overlayItem.windowLevel',
},
{
id: 'ZoomLevel',
customizationType: 'ohif.overlayItem.zoomLevel',
condition: props => {
const activeToolName = props.toolGroupService.getActiveToolForViewport(props.viewportId);
return activeToolName === 'Zoom';
},
},
],
};
const bottomRightItems = {
id: 'cornerstoneOverlayBottomRight',
items: [
{
id: 'InstanceNumber',
customizationType: 'ohif.overlayItem.instanceNumber',
},
],
};
/**
* The @ohif/cornerstoneOverlay is a default value for a customization
* for the cornerstone overlays. The intent is to allow it to be extended
* without needing to re-write the individual overlays by using the append
* mechanism. Individual attributes can be modified individually without
* affecting the other items by using the append as well, with position
* based replacement.
* This is used as a default in the getCustomizationModule so that it
* is available early for additional customization extensions.
*/
const CornerstoneOverlay = {
id: '@ohif/cornerstoneOverlay',
topLeftItems,
topRightItems,
bottomLeftItems,
bottomRightItems,
};
/**
* Customizable Viewport Overlay
*/
@@ -141,26 +69,18 @@ function CustomizableViewportOverlay({
const [scale, setScale] = useState(1);
const { imageIndex } = imageSliceData;
// The new customization is 'cornerstoneOverlay', with an append or replace
// on the individual items rather than defining individual items.
const cornerstoneOverlay = customizationService.getCustomization('@ohif/cornerstoneOverlay');
// Historical usage defined the overlays as separate items due to lack of
// append functionality. This code enables the historical usage, but
// the recommended functionality is to append to the default values in
// cornerstoneOverlay rather than defining individual items.
const topLeftCustomization =
customizationService.getCustomization('cornerstoneOverlayTopLeft') ||
cornerstoneOverlay?.topLeftItems;
const topRightCustomization =
customizationService.getCustomization('cornerstoneOverlayTopRight') ||
cornerstoneOverlay?.topRightItems;
const bottomLeftCustomization =
customizationService.getCustomization('cornerstoneOverlayBottomLeft') ||
cornerstoneOverlay?.bottomLeftItems;
const bottomRightCustomization =
customizationService.getCustomization('cornerstoneOverlayBottomRight') ||
cornerstoneOverlay?.bottomRightItems;
const topLeftCustomization = customizationService.getCustomization('viewportOverlay.topLeft');
const topRightCustomization = customizationService.getCustomization('viewportOverlay.topRight');
const bottomLeftCustomization = customizationService.getCustomization(
'viewportOverlay.bottomLeft'
);
const bottomRightCustomization = customizationService.getCustomization(
'viewportOverlay.bottomRight'
);
const instanceNumber = useMemo(
() =>
@@ -262,8 +182,8 @@ function CustomizableViewportOverlay({
return null;
}
const { customizationType } = item;
const OverlayItemComponent = OverlayItemComponents[customizationType];
const { inheritsFrom } = item;
const OverlayItemComponent = OverlayItemComponents[inheritsFrom];
if (OverlayItemComponent) {
return <OverlayItemComponent {...overlayItemProps} />;
@@ -291,10 +211,6 @@ function CustomizableViewportOverlay({
const getContent = useCallback(
(customization, keyPrefix) => {
if (!customization?.items) {
return null;
}
const { items } = customization;
const props = {
...displaySetProps,
formatters: { formatDate: formatDICOMDate },
@@ -307,7 +223,7 @@ function CustomizableViewportOverlay({
return (
<>
{items.map((item, index) => (
{customization.map((item, index) => (
<div key={`${keyPrefix}_${index}`}>
{((!item?.condition || item.condition(props)) && _renderOverlayItem(item, props)) ||
null}
@@ -452,7 +368,7 @@ function OverlayItem(props) {
title={title}
>
{label ? <span className="mr-1 shrink-0">{label}</span> : null}
<span className="ml-1 mr-2 shrink-0">{value}</span>
<span className="ml-0 mr-2 shrink-0">{value}</span>
</div>
);
}
@@ -471,10 +387,10 @@ function VOIOverlayItem({ voi, customization }: OverlayItemProps) {
className="overlay-item flex flex-row"
style={{ color: customization?.color }}
>
<span className="mr-1 shrink-0">W:</span>
<span className="ml-1 mr-2 shrink-0">{windowWidth.toFixed(0)}</span>
<span className="mr-1 shrink-0">L:</span>
<span className="ml-1 shrink-0">{windowCenter.toFixed(0)}</span>
<span className="mr-0.5 shrink-0 opacity-[0.67]">W:</span>
<span className="mr-2.5 shrink-0">{windowWidth.toFixed(0)}</span>
<span className="mr-0.5 shrink-0 opacity-[0.67]">L:</span>
<span className="shrink-0">{windowCenter.toFixed(0)}</span>
</div>
);
}
@@ -488,7 +404,7 @@ function ZoomOverlayItem({ scale, customization }: OverlayItemProps) {
className="overlay-item flex flex-row"
style={{ color: (customization && customization.color) || undefined }}
>
<span className="mr-1 shrink-0">Zoom:</span>
<span className="mr-0.5 shrink-0 opacity-[0.67]">Zoom:</span>
<span>{scale.toFixed(2)}x</span>
</div>
);
@@ -512,7 +428,7 @@ function InstanceNumberOverlayItem({
<span>
{instanceNumber !== undefined && instanceNumber !== null ? (
<>
<span className="mr-1 shrink-0">I:</span>
<span className="mr-0.5 shrink-0 opacity-[0.67]">I:</span>
<span>{`${instanceNumber} (${imageIndex + 1}/${numberOfSlices})`}</span>
</>
) : (
@@ -531,4 +447,4 @@ CustomizableViewportOverlay.propTypes = {
export default CustomizableViewportOverlay;
export { CustomizableViewportOverlay, CornerstoneOverlay };
export { CustomizableViewportOverlay };
@@ -1,7 +1,7 @@
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { Enums, VolumeViewport3D, utilities as csUtils } from '@cornerstonejs/core';
import { ImageScrollbar } from '@ohif/ui';
import { ImageScrollbar } from '@ohif/ui-next';
function CornerstoneImageScrollbar({
viewportData,
@@ -3,19 +3,18 @@
--marker-height: 100px;
--scrollbar-width: 20px;
pointer-events: none;
font-size: 15px;
line-height: 18px;
}
.ViewportOrientationMarkers .orientation-marker {
position: absolute;
}
.ViewportOrientationMarkers .top-mid {
top: 0.6rem;
top: 0.38rem;
left: 50%;
}
.ViewportOrientationMarkers .left-mid {
top: 47%;
left: 5px;
left: 0.38rem;
}
.ViewportOrientationMarkers .right-mid {
top: 47%;
@@ -119,8 +119,8 @@ function ViewportOrientationMarkers({
className={classNames(
'overlay-text',
`${m}-mid orientation-marker`,
'text-aqua-pale',
'text-[13px]',
'text-highlight/65',
'text-base',
'leading-5'
)}
key={`${m}-mid orientation-marker`}
@@ -52,31 +52,6 @@ export function formatDICOMTime(time, strFormat = 'HH:mm:ss') {
return moment(time, 'HH:mm:ss').format(strFormat);
}
/**
* Formats a patient name for display purposes
*
* @param {string} name
* @returns {string} formatted name.
*/
export function formatPN(name) {
if (!name) {
return '';
}
if (typeof name === 'object') {
name = name.Alphabetic;
if (!name) {
return '';
}
}
const cleaned = name
.split('^')
.filter(s => !!s)
.join(', ')
.trim();
return cleaned === ',' || cleaned === '' ? '' : cleaned;
}
/**
* Gets compression type
*
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,195 @@
import React from 'react';
import { useSystem } from '@ohif/core';
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@ohif/ui-next';
import { ChevronDownIcon } from '@radix-ui/react-icons';
export type AccordionGrouping = {
[name: string]: unknown;
};
export type AccordionGroupProps = {
grouping: AccordionGrouping;
};
/**
* Searches for the required type from the provided allChildren list and
* renders them.
*/
export const CloneChildren = props => {
const { group, allChildren, children, childType, defaultTypes } = props;
const subType = group?.subType;
for (const child of allChildren) {
if (subType && child.props?.subType !== subType) {
continue;
}
if (childType && child.type === childType) {
return React.cloneElement(child, { ...props, children: child.props.children });
}
if (defaultTypes?.indexOf(child.type) === -1) {
return childType({ ...props, children: child });
}
}
if (!children) {
throw new Error(`No children defined for ${props.name} CloneChildren in group ${group?.name}`);
}
return React.cloneElement(children, props);
};
/** Used to exclude defaults */
const DEFAULT_TYPES = [GroupAccordion, Content, Trigger];
/**
* An AccordionGroup is a component that splits a set of items into different
* groups according to a set of grouping rules. It then puts the groups
* into a set of accordion folds selected from the body of the accordion group,
* looking for matching trigger/content sections according to the type definition
* in the group with first one found being used.
*
* This design allows for easy customization of the component by declaring grouping
* functions with default grouping setups and then only overriding the specific
* children needing to be changed. See the PanelMeasurement for some example
* possibilities of how to modify the default grouping, or the test-extension
* measurements panel for a practical, working example.
*/
export function AccordionGroup(props) {
const { grouping, items, children, sourceChildren, type } = props;
const childProps = useSystem();
let defaultValue = props.defaultValue;
const groups = grouping.groupingFunction(items, grouping, childProps);
if (!defaultValue) {
const defaultGroup = groups.values().find(group => group.isSelected);
defaultValue = defaultGroup?.key || defaultGroup?.title;
}
const valueArr =
(Array.isArray(defaultValue) && defaultValue) || (defaultValue && [defaultValue]) || [];
const sourceChildrenArr = sourceChildren ? React.Children.toArray(sourceChildren) : [];
const childrenArr = children ? React.Children.toArray(children) : [];
const allChildren = sourceChildrenArr.concat(childrenArr);
return (
<CloneChildren
allChildren={allChildren}
groups={groups}
childType={GroupAccordion}
grouping={grouping}
defaultValue={valueArr}
name={'grouping ' + grouping.name}
>
<DefaultAccordion name="DefaultAccordion" />
</CloneChildren>
);
}
function DefaultAccordion(props) {
const { groups, defaultValue, grouping, allChildren, asChild } = props;
if (!allChildren || !groups) {
return null;
}
if (Boolean(asChild)) {
return React.cloneElement(props.children, props);
}
return (
<Accordion
type={grouping.type || 'multiple'}
className="text-white"
defaultValue={defaultValue}
>
{[...groups.entries()].map(([key, group]) => {
return (
<AccordionItem
key={group.key + '-i'}
value={group.key}
>
<CloneChildren
allChildren={allChildren}
group={group}
childType={Trigger}
name="AccordionGroup.Trigger"
/>
<CloneChildren
allChildren={allChildren}
group={group}
childType={Content}
defaultTypes={DEFAULT_TYPES}
name="AccordionGroup.Content"
/>
</AccordionItem>
);
})}
</Accordion>
);
}
function GroupAccordion(props) {
const { groups, children } = props;
if (!groups) {
return null;
}
return [...groups.values()].map(group =>
React.cloneElement(children, {
...props,
children: children.props.children,
group,
...group,
key: group.title,
})
);
}
function Content(props) {
const { children, asChild, ...childProps } = props;
const { group } = props;
Object.assign(childProps, group);
if (!group) {
return null;
}
if (asChild) {
return React.cloneElement(children, { ...group, ...props, children: children.props.children });
}
return (
<AccordionContent>
{React.cloneElement(children, { ...group, ...props, children: children.props.children })}
</AccordionContent>
);
}
function Trigger(props) {
const { children, asChild, ...childProps } = props;
const { group } = props;
Object.assign(childProps, group);
if (!group) {
return null;
}
if (asChild) {
return React.cloneElement(children, childProps);
}
return (
<AccordionTrigger
value={group.value}
asChild={true}
>
<div>
{React.cloneElement(children, childProps)}
<ChevronDownIcon
key="chevronDown"
className="text-primary h-4 w-4 shrink-0 transition-transform duration-200"
/>
</div>
</AccordionTrigger>
);
}
AccordionGroup.Content = Content;
AccordionGroup.Trigger = Trigger;
AccordionGroup.Accordion = GroupAccordion;
export default AccordionGroup;
@@ -0,0 +1,4 @@
export * from './AccordionGroup';
import AccordionGroup from './AccordionGroup';
export default AccordionGroup;
@@ -1,6 +1,6 @@
import React, { ReactElement } from 'react';
import PropTypes from 'prop-types';
import { useViewportGrid } from '@ohif/ui';
import { useViewportGrid } from '@ohif/ui-next';
import ViewportWindowLevel from '../ViewportWindowLevel/ViewportWindowLevel';
const ActiveViewportWindowLevel = ({ servicesManager }: withAppTypes): ReactElement => {
@@ -1,5 +1,5 @@
import React, { useCallback, useEffect, useState, useRef } from 'react';
import { CinePlayer, useCine } from '@ohif/ui';
import { useCine } from '@ohif/ui-next';
import { Enums, eventTarget, cache } from '@cornerstonejs/core';
import { useAppConfig } from '@state';
@@ -53,13 +53,13 @@ function WrappedCinePlayer({
// check if the displaySet is dynamic and set the dynamic info
if (displaySet.isDynamicVolume) {
const { dynamicVolumeInfo } = displaySet;
const numTimePoints = dynamicVolumeInfo.timePoints.length;
const numDimensionGroups = dynamicVolumeInfo.timePoints.length;
const label = dynamicVolumeInfo.splittingTag;
const timePointIndex = dynamicVolumeInfo.timePointIndex || 0;
const dimensionGroupNumber = dynamicVolumeInfo.dimensionGroupNumber || 1;
setDynamicInfo({
volumeId: displaySet.displaySetInstanceUID,
timePointIndex,
numTimePoints,
dimensionGroupNumber,
numDimensionGroups,
label,
});
} else {
@@ -160,8 +160,7 @@ function RenderCinePlayer({
dynamicInfo: dynamicInfoProp,
customizationService,
}) {
const { component: CinePlayerComponent = CinePlayer } =
customizationService.get('cinePlayer') ?? {};
const CinePlayerComponent = customizationService.getCustomization('cinePlayer');
const [dynamicInfo, setDynamicInfo] = useState(dynamicInfoProp);
@@ -177,20 +176,20 @@ function RenderCinePlayer({
return;
}
const handleTimePointIndexChange = evt => {
const { volumeId, timePointIndex, numTimePoints, splittingTag } = evt.detail;
setDynamicInfo({ volumeId, timePointIndex, numTimePoints, label: splittingTag });
const handleDimensionGroupChange = evt => {
const { volumeId, dimensionGroupNumber, numDimensionGroups, splittingTag } = evt.detail;
setDynamicInfo({ volumeId, dimensionGroupNumber, numDimensionGroups, label: splittingTag });
};
eventTarget.addEventListener(
Enums.Events.DYNAMIC_VOLUME_TIME_POINT_INDEX_CHANGED,
handleTimePointIndexChange
Enums.Events.DYNAMIC_VOLUME_DIMENSION_GROUP_CHANGED,
handleDimensionGroupChange
);
return () => {
eventTarget.removeEventListener(
Enums.Events.DYNAMIC_VOLUME_TIME_POINT_INDEX_CHANGED,
handleTimePointIndexChange
Enums.Events.DYNAMIC_VOLUME_DIMENSION_GROUP_CHANGED,
handleDimensionGroupChange
);
};
}, [dynamicInfo]);
@@ -200,17 +199,17 @@ function RenderCinePlayer({
return;
}
const { volumeId, timePointIndex, numTimePoints, splittingTag } = dynamicInfo || {};
const { volumeId, dimensionGroupNumber, numDimensionGroups, splittingTag } = dynamicInfo || {};
const volume = cache.getVolume(volumeId, true);
volume.timePointIndex = timePointIndex;
volume.dimensionGroupNumber = dimensionGroupNumber;
setDynamicInfo({ volumeId, timePointIndex, numTimePoints, label: splittingTag });
setDynamicInfo({ volumeId, dimensionGroupNumber, numDimensionGroups, label: splittingTag });
}, []);
const updateDynamicInfo = useCallback(props => {
const { volumeId, timePointIndex } = props;
const { volumeId, dimensionGroupNumber } = props;
const volume = cache.getVolume(volumeId, true);
volume.timePointIndex = timePointIndex;
volume.dimensionGroupNumber = dimensionGroupNumber;
}, []);
return (
@@ -245,4 +244,4 @@ function RenderCinePlayer({
);
}
export default WrappedCinePlayer;
export default WrappedCinePlayer;
@@ -1,6 +1,7 @@
import React, { useCallback, useEffect, useRef, useState, ReactElement } from 'react';
import PropTypes from 'prop-types';
import { Button, ProgressLoadingBar } from '@ohif/ui';
import { useSystem } from '@ohif/core';
import { Button } from '@ohif/ui';
import { Icons } from '@ohif/ui-next';
import DicomFileUploader, {
EVENTS,
@@ -40,6 +41,11 @@ function DicomUploadProgress({
dicomFileUploaderArr,
onComplete,
}: DicomUploadProgressProps): ReactElement {
const { servicesManager } = useSystem();
const ProgressLoadingBar =
servicesManager.services.customizationService.getCustomization('ui.progressLoadingBar');
const [totalUploadSize] = useState(
dicomFileUploaderArr.reduce((acc, fileUploader) => acc + fileUploader.getFileSize(), 0)
);
@@ -297,7 +303,7 @@ function DicomUploadProgress({
<span
className={classNames(
NO_WRAP_ELLIPSIS_CLASS_NAMES,
'text-primary-active hover:text-primary-light active:text-aqua-pale ml-auto cursor-pointer'
'text-primary hover:text-primary-light active:text-aqua-pale ml-auto cursor-pointer'
)}
onClick={cancelAllUploads}
>
@@ -89,7 +89,7 @@ const DicomUploadProgressItem = memo(
)}
<div className="ml-auto flex cursor-pointer">
<Icons.Close
className="text-primary-active self-center"
className="text-primary self-center"
onClick={cancelUpload}
/>
</div>
@@ -0,0 +1,70 @@
import React from 'react';
import { Accordion, AccordionContent, AccordionItem } from '@ohif/ui-next';
import PanelAccordionTrigger from './PanelAccordionTrigger';
import MeasurementsMenu from './MeasurementsMenu';
import { useSystem } from '@ohif/core';
export function MeasurementItem(props) {
const { index, item } = props;
return (
<PanelAccordionTrigger
count={index + 1}
text={item.toolName || item.label || item.title}
colorHex="#f00"
isActive={item.isSelected}
menu={MeasurementsMenu}
group={{ items: [item], onClick: props.onClick }}
/>
);
}
export default function MeasurementAccordion(props) {
const { items } = props;
const system = useSystem();
const onClick = (e, group) => {
const { items } = group;
// Just jump to the first measurement in the set, and mark that one as active
// with the set of items.
system.commandsManager.run('jumpToMeasurement', {
uid: items[0].uid,
displayMeasurements: items,
group,
});
};
return (
<Accordion
type="multiple"
className="flex-shrink-0 overflow-hidden"
>
{items.map((item, index) => {
const { displayText: details = {} } = item;
return (
<AccordionItem
key={`measurementAccordion:${item.uid}`}
value={item.uid}
>
<MeasurementItem
item={item}
key={`measurementItem:${item.uid}`}
index={index}
onClick={onClick}
/>
<AccordionContent key={`measurementContent:${item.uid}`}>
<div className="ml-7 px-2 py-2">
<div className="text-secondary-foreground flex items-center gap-1 text-base leading-normal">
{details.primary?.length > 0 &&
details.primary.map((detail, index) => (
<span key={`details:${item.uid}:${index}`}>{detail}</span>
))}
</div>
</div>
</AccordionContent>
</AccordionItem>
);
})}
</Accordion>
);
}
@@ -0,0 +1,30 @@
import React from 'react';
import { MeasurementTable } from '@ohif/ui-next';
import { useSystem } from '@ohif/core';
/**
* This is a measurement table that is designed to be nested inside
* the accordion groups.
*/
export default function MeasurementTableNested(props) {
const { title, items, group, customHeader } = props;
const { commandsManager } = useSystem();
const onAction = (e, command, uid) => {
commandsManager.run(command, { uid, annotationUID: uid, displayMeasurements: items });
};
return (
<MeasurementTable
title={title ? title : `Measurements`}
data={items}
onAction={onAction}
{...group}
key={group.key}
>
<MeasurementTable.Header key="measurementTableHeader">
{customHeader && group.isFirst && customHeader({ ...props, items: props.allItems })}
</MeasurementTable.Header>
<MeasurementTable.Body key="measurementTableBody" />
</MeasurementTable>
);
}
@@ -0,0 +1,81 @@
import React, { useState } from 'react';
import { useSystem } from '@ohif/core';
import {
Button,
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
Icons,
} from '@ohif/ui-next';
export function MeasumentsMenu(props) {
const { group, classNames } = props;
if (!group.items?.length) {
console.log('No items to iterate', group.items);
return null;
}
const { items } = group;
const [item] = items;
const { isSelected, isVisible } = item;
const system = useSystem();
const onAction = (event, command, args?) => {
const uid = items.map(item => item.uid);
// Some commands use displayMeasurements and some use items
system.commandsManager.run(command, {
...args,
uid,
displayMeasurements: items,
items,
event,
});
};
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
return (
<div className={`relative ml-2 inline-flex items-center space-x-1 ${classNames}`}>
{/* Visibility Toggle Icon */}
<Button
size="icon"
variant="ghost"
className={`h-6 w-6 transition-opacity ${
isSelected || !isVisible ? 'opacity-100' : 'opacity-50 group-hover:opacity-100'
}`}
aria-label={isVisible ? 'Hide' : 'Show'}
onClick={e => {
e.stopPropagation();
onAction(e, ['jumpToMeasurement', 'toggleVisibilityMeasurement']);
}}
>
{isVisible ? <Icons.Hide className="h-6 w-6" /> : <Icons.Show className="h-6 w-6" />}
</Button>
{/* Actions Dropdown Menu */}
<DropdownMenu onOpenChange={open => setIsDropdownOpen(open)}>
<DropdownMenuTrigger asChild>
<Button
size="icon"
variant="ghost"
className={`h-6 w-6 transition-opacity ${
isSelected || isDropdownOpen ? 'opacity-100' : 'opacity-50 group-hover:opacity-100'
}`}
aria-label="Actions"
onClick={e => e.stopPropagation()} // Prevent row selection on button click
>
<Icons.More className="h-6 w-6" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={e => onAction(e, 'removeMeasurement')}>
<Icons.Delete className="text-foreground" />
<span className="pl-2">Delete</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
);
}
export default MeasumentsMenu;
@@ -0,0 +1,82 @@
import React from 'react';
import AccordionGroup from './AccordionGroup';
import { utils } from '@ohif/core';
import MeasurementTableNested from './MeasurementTableNested';
const { filterNot, filterAdditionalFindings } = utils.MeasurementFilters;
export const MeasurementOrAdditionalFindingSets = [
{
title: 'Measurements',
filter: filterNot(filterAdditionalFindings),
},
{
title: 'Additional Findings',
filter: filterAdditionalFindings,
},
];
/**
* Groups measurements by study in order to allow display and saving by study
* @param {Object} servicesManager
*/
export const groupByNamedSets = (items, grouping) => {
const groups = new Map();
const { namedSets } = grouping;
for (const namedSet of namedSets) {
const name = namedSet.id || namedSet.title;
groups.set(name, {
...grouping,
...namedSet,
items: [],
isFirst: groups.size === 0,
title: name,
key: name,
});
}
items.forEach(item => {
for (const namedSet of namedSets) {
if (namedSet.filter(item)) {
const name = namedSet.id || namedSet.title;
groups.get(name).items.push(item);
return;
}
}
});
for (const namedSet of namedSets) {
const name = namedSet.id || namedSet.title;
if (!groups.get(name).items.length) {
groups.delete(name);
}
}
return groups;
};
export function MeasurementsOrAdditionalFindings(props): React.ReactNode {
const { items, children, grouping = {}, customHeader, group, actions } = props;
return (
<AccordionGroup
grouping={{
groupingFunction: groupByNamedSets,
name: 'measurementsOrAdditional',
namedSets: MeasurementOrAdditionalFindingSets,
StudyInstanceUID: group?.StudyInstanceUID,
...grouping,
}}
items={items}
sourceChildren={children}
>
<AccordionGroup.Accordion noWrapper="true">
<MeasurementTableNested
customHeader={customHeader}
allItems={items}
actions={actions}
/>
</AccordionGroup.Accordion>
</AccordionGroup>
);
}
export default MeasurementsOrAdditionalFindings;
@@ -1,14 +1,16 @@
import React from 'react';
import { useViewportActionCornersContext } from '../contextProviders/ViewportActionCornersProvider';
import { ViewportActionCorners } from '@ohif/ui';
import { useSystem } from '@ohif/core';
export type OHIFViewportActionCornersProps = {
viewportId: string;
};
function OHIFViewportActionCorners({ viewportId }: OHIFViewportActionCornersProps) {
const { servicesManager } = useSystem();
const [viewportActionCornersState] = useViewportActionCornersContext();
const ViewportActionCorners =
servicesManager.services.customizationService.getCustomization('ui.viewportActionCorner');
if (!viewportActionCornersState[viewportId]) {
return null;
}
@@ -0,0 +1,48 @@
import React from 'react';
import { AccordionTrigger, ColorCircle } from '@ohif/ui-next';
import { ChevronDownIcon } from '@radix-ui/react-icons';
function onClickDefault(e) {
const { group, onClick = group?.onClick } = this;
if (!onClick) {
console.log('No onClick function', group);
return;
}
console.log('onClickDefault');
e.preventDefault();
e.stopPropagation();
onClick(e, group);
return false;
}
export default function PanelAccordionTrigger(props) {
const { marginLeft = 8, isActive = false, colorHex, count, text, menu: Menu = null } = props;
return (
<AccordionTrigger
style={{ marginLeft: `${marginLeft}px`, padding: 0 }}
asChild={true}
>
<div className={`inline-flex text-base ${isActive ? 'bg-popover' : 'bg-muted'} flex-grow`}>
<button onClick={onClickDefault.bind(props)}>
<span
className={`inline-flex rounded-l border-r border-black ${isActive ? 'bg-highlight' : 'bg-muted'}`}
>
{count !== undefined ? <span className="px-2">{count}</span> : null}
{colorHex && <ColorCircle colorHex={colorHex} />}
</span>
<span>{text}</span>
</button>
{Menu && (
<Menu
{...props}
classNames="justify-end flex-grow"
/>
)}
<ChevronDownIcon className="text-primary h-4 w-4 shrink-0 transition-transform duration-200" />
</div>
</AccordionTrigger>
);
}
@@ -0,0 +1,90 @@
import React from 'react';
import { useActiveViewportDisplaySets, useSystem } from '@ohif/core';
import AccordionGroup from './AccordionGroup';
import PanelAccordionTrigger from './PanelAccordionTrigger';
import MeasurementItems from './MeasurementItems';
import MeasurementsMenu from './MeasurementsMenu';
/**
* Groups measurements by study in order to allow display and saving by study
* @param {Object} servicesManager
*/
export const groupByDisplaySet = (items, grouping, childProps) => {
const groups = new Map();
const { displaySetService } = childProps.servicesManager.services;
const { activeDisplaySetInstanceUID } = grouping;
items.forEach(item => {
const { displaySetInstanceUID } = item;
if (!groups.has(displaySetInstanceUID)) {
const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID);
groups.set(displaySetInstanceUID, {
header: null,
isSelected: displaySetInstanceUID == activeDisplaySetInstanceUID,
...grouping,
items: [],
key: displaySetInstanceUID,
title: 'Series Measurements',
displaySet,
});
}
groups.get(displaySetInstanceUID).items.push(item);
});
return groups;
};
export function SeriesMeasurementTrigger(props) {
const { group, isSelected, displaySet, menu } = props;
const { SeriesNumber = 1, SeriesDescription } = displaySet;
return (
<PanelAccordionTrigger
text={`Series #${SeriesNumber} ${SeriesDescription}`}
count={group.items.length}
isActive={isSelected}
group={group}
menu={menu}
marginLeft="0"
/>
);
}
export function SeriesMeasurements(props): React.ReactNode {
const { items, grouping = {}, children } = props;
const system = useSystem();
const activeDisplaySets = useActiveViewportDisplaySets(system);
const activeDisplaySetInstanceUID = activeDisplaySets?.[0]?.displaySetInstanceUID;
const onClick = (_e, group) => {
const { items } = group;
system.commandsManager.run('jumpToMeasurement', {
uid: items[0].uid,
displayMeasurements: items,
group,
});
};
// The content of the accordion group will default to the children of the
// parent declaration if present, otherwise to MeasurementItems
return (
<AccordionGroup
grouping={{
groupingFunction: groupByDisplaySet,
activeDisplaySetInstanceUID,
...grouping,
onClick,
}}
items={items}
sourceChildren={children}
>
<AccordionGroup.Trigger asChild={true}>
<SeriesMeasurementTrigger menu={MeasurementsMenu} />
</AccordionGroup.Trigger>
<MeasurementItems />
</AccordionGroup>
);
}
export default SeriesMeasurements;
@@ -0,0 +1,21 @@
import React from 'react';
import { utils } from '@ohif/core';
const { formatDate } = utils;
export function SeriesSummaryFromDisplaySet({ displaySet }) {
if (!displaySet) {
return null;
}
const { SeriesDate, SeriesDescription, SeriesNumber = 1 } = displaySet;
return (
<div className="mx-2 my-0">
<div className="text-foreground pb-1 text-sm">
Series #{SeriesNumber} {SeriesDescription}
</div>
<div className="text-muted-foreground text-sm">{formatDate(SeriesDate)}</div>
</div>
);
}
@@ -0,0 +1,88 @@
import React from 'react';
import { useActiveViewportDisplaySets, useSystem, utils } from '@ohif/core';
// import { AccordionContent, AccordionItem, AccordionTrigger } from '@ohif/ui-next';
import { AccordionGroup } from './AccordionGroup';
import MeasurementsOrAdditionalFindings from './MeasurementsOrAdditionalFindings';
import StudySummaryWithActions from './StudySummaryWithActions';
const { MeasurementFilters } = utils;
/**
* Groups measurements by study in order to allow display and saving by study
* @param {Object} servicesManager
*/
export const groupByStudy = (items, grouping, childProps) => {
const groups = new Map();
const { activeStudyUID } = grouping;
const { displaySetService } = childProps.servicesManager.services;
const getItemStudyInstanceUID = item => {
const displaySet = displaySetService.getDisplaySetByUID(item.displaySetInstanceUID);
return displaySet.instances[0].StudyInstanceUID;
};
let firstSelected, firstGroup;
items.forEach(item => {
const studyUID = getItemStudyInstanceUID(item);
if (!groups.has(studyUID)) {
const items = [];
const filter = MeasurementFilters.filterAnd(
MeasurementFilters.filterMeasurementsByStudyUID(activeStudyUID),
grouping.filter
);
const group = {
...grouping,
items,
displayMeasurements: items,
key: studyUID,
isSelected: studyUID === activeStudyUID,
StudyInstanceUID: activeStudyUID,
filter,
measurementFilter: filter,
};
if (group.isSelected && !firstSelected) {
firstSelected = group;
}
firstGroup ||= group;
groups.set(studyUID, group);
}
if (!firstSelected && firstGroup) {
firstGroup.isSelected = true;
}
const group = groups.get(studyUID);
group.items.push(item);
});
return groups;
};
export function StudyMeasurements(props): React.ReactNode {
const { items, grouping = {}, children } = props;
const system = useSystem();
const activeDisplaySets = useActiveViewportDisplaySets(system);
const activeStudyUID = activeDisplaySets?.[0]?.StudyInstanceUID;
return (
<AccordionGroup
grouping={{
name: 'groupByStudy',
groupingFunction: groupByStudy,
activeStudyUID,
...grouping,
}}
items={items}
value={[activeStudyUID]}
sourceChildren={children}
>
<AccordionGroup.Trigger>
<StudySummaryWithActions />
</AccordionGroup.Trigger>
<MeasurementsOrAdditionalFindings activeStudyUID={activeStudyUID} />
</AccordionGroup>
);
}
export default StudyMeasurements;
@@ -0,0 +1,73 @@
import React from 'react';
import { Button, Icons } from '@ohif/ui-next';
import { useSystem } from '@ohif/core';
export function StudyMeasurementsActions({ items, StudyInstanceUID, measurementFilter, actions }) {
const { commandsManager } = useSystem();
const disabled = !items?.length;
if (disabled) {
return null;
}
return (
<div className="bg-background flex h-9 w-full items-center rounded pr-0.5">
<div className="flex space-x-1">
<Button
size="sm"
variant="ghost"
className="pl-1.5"
onClick={() => {
commandsManager.runCommand('downloadCSVMeasurementsReport', {
StudyInstanceUID,
measurementFilter,
});
}}
>
<Icons.Download className="h-5 w-5" />
<span className="pl-1">CSV</span>
</Button>
<Button
size="sm"
variant="ghost"
className="pl-0.5"
onClick={e => {
e.stopPropagation();
if (actions?.createSR) {
actions.createSR({ StudyInstanceUID, measurementFilter });
return;
}
commandsManager.run('promptSaveReport', {
StudyInstanceUID,
measurementFilter,
});
}}
>
<Icons.Add />
Create SR
</Button>
<Button
size="sm"
variant="ghost"
className="pl-0.5"
onClick={e => {
e.stopPropagation();
if (actions?.onDelete) {
actions.onDelete();
return;
}
commandsManager.runCommand('clearMeasurements', {
measurementFilter,
});
}}
>
<Icons.Delete />
Delete
</Button>
</div>
</div>
);
}
export default StudyMeasurementsActions;
@@ -0,0 +1,26 @@
import React from 'react';
import { DicomMetadataStore, utils } from '@ohif/core';
import { StudySummary } from '@ohif/ui-next';
const { formatDate } = utils;
export function StudySummaryFromMetadata(props) {
const { StudyInstanceUID } = props;
if (!StudyInstanceUID) {
return null;
}
const studyMeta = DicomMetadataStore.getStudy(StudyInstanceUID);
if (!studyMeta?.series?.length) {
return null;
}
const instanceMeta = studyMeta.series[0].instances[0];
const { StudyDate, StudyDescription } = instanceMeta;
return (
<StudySummary
date={formatDate(StudyDate)}
description={StudyDescription}
/>
);
}
@@ -0,0 +1,14 @@
import React from 'react';
import { StudySummaryFromMetadata } from './StudySummaryFromMetadata';
import StudyMeasurementsActions from './StudyMeasurementsActions';
export function StudySummaryWithActions(props) {
return (
<div>
<StudySummaryFromMetadata {...props} />
<StudyMeasurementsActions {...props} />
</div>
);
}
export default StudySummaryWithActions;
@@ -1,7 +1,7 @@
import React, { useEffect, useCallback, useState, ReactElement, useMemo } from 'react';
import PropTypes from 'prop-types';
import debounce from 'lodash.debounce';
import { PanelSection, WindowLevel } from '@ohif/ui';
import { PanelSection, WindowLevel } from '@ohif/ui-next';
import { Enums, eventTarget } from '@cornerstonejs/core';
import { useActiveViewportDisplaySets } from '@ohif/core';
import {
@@ -20,7 +20,7 @@ const ViewportWindowLevel = ({
viewportId: string;
}>): ReactElement => {
const { cornerstoneViewportService } = servicesManager.services;
const [windowLevels, setWindowLevels] = useState([]);
const [windowLevels, setWindowLevels] = useState<any[]>([]);
const [isLoading, setIsLoading] = useState(true);
const displaySets = useActiveViewportDisplaySets({ servicesManager });
@@ -200,29 +200,36 @@ const ViewportWindowLevel = ({
}, [viewportId, cornerstoneViewportService, updateViewportHistograms, displaySetIds, isLoading]);
return (
<PanelSection title="Window Level">
{windowLevels.map((windowLevel, i) => {
if (!windowLevel.histogram) {
return null;
}
<PanelSection defaultOpen={true}>
<PanelSection.Header>Window Level</PanelSection.Header>
<PanelSection.Content className="bg-muted py-1">
{windowLevels.map((windowLevel, i) => {
if (!windowLevel.histogram) {
return null;
}
return (
<WindowLevel
key={windowLevel.volumeId}
title={`${windowLevel.modality}`}
histogram={windowLevel.histogram}
voi={windowLevel.voi}
step={windowLevel.step}
showOpacitySlider={windowLevel.showOpacitySlider}
colormap={windowLevel.colormap}
onVOIChange={voi => handleVOIChange(windowLevel.volumeId, voi)}
opacity={windowLevel.opacity}
onOpacityChange={opacity =>
handleOpacityChange(windowLevel.viewportId, i, windowLevel.volumeId, opacity)
}
/>
);
})}
return (
<WindowLevel
key={windowLevel.volumeId}
histogram={windowLevel.histogram}
voi={windowLevel.voi}
step={windowLevel.step}
showOpacitySlider={windowLevel.showOpacitySlider}
colormap={windowLevel.colormap}
onVOIChange={voi => handleVOIChange(windowLevel.volumeId, voi)}
opacity={windowLevel.opacity}
onOpacityChange={opacity =>
handleOpacityChange(windowLevel.viewportId, i, windowLevel.volumeId, opacity)
}
/>
);
})}
{windowLevels.length === 0 && !isLoading && (
<div className="text-muted-foreground py-2 text-center text-sm">
No window level data available
</div>
)}
</PanelSection.Content>
</PanelSection>
);
};
@@ -1,5 +1,5 @@
import React, { ReactElement, useCallback, useEffect, useState } from 'react';
import { SwitchButton } from '@ohif/ui';
import { Switch } from '@ohif/ui-next';
import { StackViewport, VolumeViewport } from '@cornerstonejs/core';
import { ColorbarProps } from '../../types/Colorbar';
import { utilities } from '@cornerstonejs/core';
@@ -22,7 +22,7 @@ export function setViewportColorbar(
colorbarOptions.ticks = {
position: 'left',
style: {
font: '12px Arial',
font: '13px Inter',
color: '#000000',
maxNumTicks: 8,
tickSize: 5,
@@ -101,12 +101,20 @@ export function Colorbar({
}, [viewportId]);
return (
<div className="all-in-one-menu-item flex w-full justify-center">
<div className="mr-2 w-[28px]"></div>
<SwitchButton
label="Display Color bar"
<div
className="hover:bg-accent flex h-8 w-full flex-shrink-0 cursor-pointer items-center px-2 text-base hover:rounded"
onClick={e => {
e.stopPropagation();
onSetColorbar();
}}
>
<div className="flex w-7 flex-shrink-0 items-center justify-center"></div>
<span className="flex-grow">Display Color bar</span>
<Switch
className="ml-2 flex-shrink-0"
checked={showColorbar}
onChange={() => {
onClick={e => {
e.stopPropagation();
onSetColorbar();
}}
/>
@@ -1,5 +1,6 @@
import React, { ReactElement, useCallback, useEffect, useRef, useState, useMemo } from 'react';
import { AllInOneMenu, ButtonGroup, SwitchButton } from '@ohif/ui';
import React, { ReactElement, useCallback, useEffect, useRef, useState } from 'react';
import { AllInOneMenu, Switch, Tabs, TabsList, TabsTrigger } from '@ohif/ui-next';
import { StackViewport, Types } from '@cornerstonejs/core';
import { ColormapProps } from '../../types/Colormap';
@@ -64,20 +65,9 @@ export function Colormap({
return colormap;
};
const buttons = useMemo(() => {
return displaySets.map((displaySet, index) => ({
children: displaySet.Modality,
key: index,
style: {
minWidth: `calc(100% / ${displaySets.length})`,
fontSize: '0.8rem',
textAlign: 'center',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
},
}));
}, [displaySets]);
const activeIndex = displaySets.findIndex(
ds => ds.displaySetInstanceUID === activeDisplaySetRef.current.displaySetInstanceUID
);
useEffect(() => {
setActiveDisplaySet(displaySets[displaySets.length - 1]);
@@ -85,46 +75,52 @@ export function Colormap({
return (
<>
{buttons.length > 1 && (
<div className="all-in-one-menu-item flex w-full justify-center">
<ButtonGroup
onActiveIndexChange={index => {
{displaySets.length > 1 && (
<div className="flex h-8 w-full flex-shrink-0 items-center justify-center px-2 text-base">
<Tabs
value={String(activeIndex)}
onValueChange={val => {
const index = parseInt(val, 10);
setActiveDisplaySet(displaySets[index]);
setPrePreviewColormap(null);
}}
activeIndex={
displaySets.findIndex(
ds => ds.displaySetInstanceUID === activeDisplaySetRef.current.displaySetInstanceUID
) || 1
}
className="w-[70%] text-[10px]"
>
{buttons.map(({ children, key, style }) => (
<div
key={key}
style={style}
>
{children}
</div>
))}
</ButtonGroup>
<TabsList>
{displaySets.map((ds, i) => (
<TabsTrigger
key={i}
value={String(i)}
>
{ds.Modality}
</TabsTrigger>
))}
</TabsList>
</Tabs>
</div>
)}
<div className="all-in-one-menu-item flex w-full justify-center">
<SwitchButton
label="Preview in viewport"
<div
className="hover:bg-accent flex h-8 w-full flex-shrink-0 cursor-pointer items-center px-2 text-base hover:rounded"
onClick={() => setShowPreview(!showPreview)}
>
<span className="flex-shrink-0">Preview in viewport</span>
<Switch
className="ml-auto flex-shrink-0"
checked={showPreview}
onChange={checked => {
onCheckedChange={checked => {
setShowPreview(checked);
}}
/>
</div>
<AllInOneMenu.DividerItem />
<AllInOneMenu.ItemPanel>
<AllInOneMenu.ItemPanel
maxHeight="calc(100vh - 250px)"
className="min-h-[200px] flex-grow"
>
{colormaps.map((colormap, index) => (
<AllInOneMenu.Item
key={index}
label={colormap.description}
useIconSpace={false}
onClick={() => {
onSetColorLUT({
viewportId,
@@ -1,5 +1,6 @@
import React, { ReactElement, useState, useEffect, useCallback } from 'react';
import { VolumeLightingProps } from '../../types/ViewportPresets';
import { Numeric } from '@ohif/ui-next';
export function VolumeLighting({
servicesManager,
@@ -8,135 +9,75 @@ export function VolumeLighting({
hasShade,
}: VolumeLightingProps): ReactElement {
const { cornerstoneViewportService } = servicesManager.services;
const [ambient, setAmbient] = useState(null);
const [diffuse, setDiffuse] = useState(null);
const [specular, setSpecular] = useState(null);
const [lightingValues, setLightingValues] = useState({
ambient: null,
diffuse: null,
specular: null,
});
const onAmbientChange = useCallback(() => {
commandsManager.runCommand('setVolumeLighting', { viewportId, options: { ambient } });
}, [ambient, commandsManager, viewportId]);
const onDiffuseChange = useCallback(() => {
commandsManager.runCommand('setVolumeLighting', { viewportId, options: { diffuse } });
}, [diffuse, commandsManager, viewportId]);
const onSpecularChange = useCallback(() => {
commandsManager.runCommand('setVolumeLighting', { viewportId, options: { specular } });
}, [specular, commandsManager, viewportId]);
const calculateBackground = value => {
const percentage = ((value - 0) / (1 - 0)) * 100;
return `linear-gradient(to right, #5acce6 0%, #5acce6 ${percentage}%, #3a3f99 ${percentage}%, #3a3f99 100%)`;
};
// Single callback to handle all lighting property changes
const onLightingChange = useCallback(
(property, value) => {
commandsManager.runCommand('setVolumeLighting', {
viewportId,
options: { [property]: value },
});
setLightingValues(prev => ({
...prev,
[property]: value,
}));
},
[commandsManager, viewportId]
);
useEffect(() => {
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
const { actor } = viewport.getActors()[0];
const ambient = actor.getProperty().getAmbient();
const diffuse = actor.getProperty().getDiffuse();
const specular = actor.getProperty().getSpecular();
setAmbient(ambient);
setDiffuse(diffuse);
setSpecular(specular);
}, [viewportId, cornerstoneViewportService]);
const disableOption = hasShade ? '' : 'ohif-disabled !opacity-40';
const disableSlider = !hasShade;
return (
<>
<div
className={`all-in-one-menu-item flex w-full flex-row !items-center justify-between gap-[10px] ${disableOption}`}
>
<label
className="block text-white"
htmlFor="ambient"
>
Ambient
</label>
{ambient !== null && (
<input
className="bg-inputfield-main h-2 w-[120px] cursor-pointer appearance-none rounded-lg"
value={ambient}
onChange={e => {
setAmbient(e.target.value);
onAmbientChange();
}}
id="ambient"
disabled={disableSlider}
max={1}
min={0}
type="range"
step={0.1}
style={{
background: calculateBackground(ambient),
'--thumb-inner-color': '#5acce6',
'--thumb-outer-color': '#090c29',
}}
/>
)}
</div>
<div
className={`all-in-one-menu-item flex w-full flex-row !items-center justify-between gap-[10px] ${disableOption}`}
>
<label
className="block text-white"
htmlFor="diffuse"
>
Diffuse
</label>
{diffuse !== null && (
<input
className="bg-inputfield-main h-2 w-[120px] cursor-pointer appearance-none rounded-lg"
value={diffuse}
onChange={e => {
setDiffuse(e.target.value);
onDiffuseChange();
}}
disabled={disableSlider}
id="diffuse"
max={1}
min={0}
type="range"
step={0.1}
style={{
background: calculateBackground(diffuse),
'--thumb-inner-color': '#5acce6',
'--thumb-outer-color': '#090c29',
}}
/>
)}
</div>
const property = actor.getProperty();
<div
className={`all-in-one-menu-item flex w-full flex-row !items-center justify-between gap-[10px] ${disableOption}`}
>
<label
className="block text-white"
htmlFor="specular"
>
Specular
</label>
{specular !== null && (
<input
className="bg-inputfield-main h-2 w-[120px] cursor-pointer appearance-none rounded-lg"
value={specular}
disabled={disableSlider}
onChange={e => {
setSpecular(e.target.value);
onSpecularChange();
}}
id="specular"
max={1}
min={0}
type="range"
step={0.1}
style={{
background: calculateBackground(specular),
'--thumb-inner-color': '#5acce6',
'--thumb-outer-color': '#090c29',
}}
/>
)}
</div>
</>
const values = {
ambient: property.getAmbient(),
diffuse: property.getDiffuse(),
specular: property.getSpecular(),
};
setLightingValues(values);
}, [viewportId, cornerstoneViewportService]);
const disableOption = hasShade ? '' : 'ohif-disabled !opacity-40';
// Configuration for our lighting properties
const lightingProperties = [
{ key: 'ambient', label: 'Ambient' },
{ key: 'diffuse', label: 'Diffuse' },
{ key: 'specular', label: 'Specular' },
];
return (
<div className="my-1 mt-2 flex flex-col space-y-2">
{lightingProperties.map(
({ key, label }) =>
lightingValues[key] !== null && (
<div
key={key}
className={`w-full pl-2 pr-1 ${disableOption}`}
>
<Numeric.Container
mode="singleRange"
min={0}
max={1}
step={0.1}
value={lightingValues[key]}
onChange={value => onLightingChange(key, value)}
>
<div className="flex flex-row items-center">
<Numeric.Label className="w-16">{label}</Numeric.Label>
<Numeric.SingleRange sliderClassName="mx-2 flex-grow" />
</div>
</Numeric.Container>
</div>
)
)}
</div>
);
}
@@ -1,5 +1,5 @@
import React, { ReactElement, useState } from 'react';
import { AllInOneMenu } from '@ohif/ui';
import { AllInOneMenu } from '@ohif/ui-next';
import { VolumeRenderingOptionsProps } from '../../types/ViewportPresets';
import { VolumeRenderingQuality } from './VolumeRenderingQuality';
import { VolumeShift } from './VolumeShift';
@@ -26,11 +26,11 @@ export function VolumeRenderingOptions({
commandsManager={commandsManager}
servicesManager={servicesManager}
/>
<div className="all-in-one-menu-item mt-2 flex !h-[20px] w-full justify-start">
<div className="text-aqua-pale text-[13px]">LIGHTING</div>
<div className="mt-2 flex h-8 !h-[20px] w-full flex-shrink-0 items-center justify-start px-2 text-base">
<div className="text-muted-foreground text-sm">Lighting</div>
</div>
<div className="bg-primary-dark mt-1 mb-1 h-[2px] w-full"></div>
<div className="all-in-one-menu-item flex w-full justify-center">
<div className="bg-background mt-1 mb-1 h-px w-full"></div>
<div className="hover:bg-accent flex h-8 w-full flex-shrink-0 items-center px-2 text-base hover:rounded">
<VolumeShade
commandsManager={commandsManager}
servicesManager={servicesManager}
@@ -1,4 +1,4 @@
import { AllInOneMenu } from '@ohif/ui';
import { AllInOneMenu } from '@ohif/ui-next';
import { Icons } from '@ohif/ui-next';
import React, { ReactElement } from 'react';
import { VolumeRenderingPresetsProps } from '../../types/ViewportPresets';
@@ -10,21 +10,19 @@ export function VolumeRenderingPresets({
commandsManager,
volumeRenderingPresets,
}: VolumeRenderingPresetsProps): ReactElement {
const { uiModalService } = servicesManager.services;
const { uiDialogService } = servicesManager.services;
const onClickPresets = () => {
uiModalService.show({
uiDialogService.show({
id: 'volume-rendering-presets',
content: VolumeRenderingPresetsContent,
title: 'Rendering Presets',
movable: true,
isDraggable: true,
contentProps: {
onClose: uiModalService.hide,
presets: volumeRenderingPresets,
viewportId,
commandsManager,
},
containerDimensions: 'h-[543px] w-[460px]',
contentDimensions: 'h-[493px] w-[460px] pl-[12px] pr-[12px]',
});
};
@@ -1,29 +1,24 @@
import { ButtonEnums } from '@ohif/ui';
import { Icons } from '@ohif/ui-next';
import { Icons, FooterAction } from '@ohif/ui-next';
import React, { ReactElement, useState, useCallback } from 'react';
import { Button, InputFilterText } from '@ohif/ui';
import { PresetDialog } from '@ohif/ui-next';
import { ViewportPreset, VolumeRenderingPresetsContentProps } from '../../types/ViewportPresets';
interface Props extends VolumeRenderingPresetsContentProps {
hide: () => void;
}
export function VolumeRenderingPresetsContent({
presets,
viewportId,
commandsManager,
onClose,
}: VolumeRenderingPresetsContentProps): ReactElement {
const [filteredPresets, setFilteredPresets] = useState(presets);
hide,
}: Props): ReactElement {
const [searchValue, setSearchValue] = useState('');
const [selectedPreset, setSelectedPreset] = useState<ViewportPreset | null>(null);
const handleSearchChange = useCallback(
(value: string) => {
setSearchValue(value);
const filtered = value
? presets.filter(preset => preset.name.toLowerCase().includes(value.toLowerCase()))
: presets;
setFilteredPresets(filtered);
},
[presets]
);
const handleSearchChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
setSearchValue(event.target.value);
}, []);
const handleApply = useCallback(
props => {
@@ -34,62 +29,54 @@ export function VolumeRenderingPresetsContent({
[commandsManager]
);
const filteredPresets = searchValue
? presets.filter(preset => preset.name.toLowerCase().includes(searchValue.toLowerCase()))
: presets;
const formatLabel = (label: string, maxChars: number) => {
return label.length > maxChars ? `${label.slice(0, maxChars)}...` : label;
};
return (
<div className="flex min-h-full w-full flex-col justify-between">
<div className="border-secondary-light h-[433px] w-full overflow-hidden rounded border bg-black px-2.5">
<div className="flex h-[46px] w-full items-center justify-start">
<div className="h-[26px] w-[200px]">
<InputFilterText
value={searchValue}
onDebounceChange={handleSearchChange}
placeholder={'Search all'}
/>
</div>
</div>
<div className="ohif-scrollbar overflow h-[385px] w-full overflow-y-auto">
<div className="grid grid-cols-4 gap-3 pt-2 pr-3">
{filteredPresets.map((preset, index) => (
<div
key={index}
className="flex cursor-pointer flex-col items-start"
onClick={() => {
setSelectedPreset(preset);
handleApply({ preset: preset.name, viewportId });
}}
>
<Icons.ByName
name={preset.name}
className={
selectedPreset?.name === preset.name
? 'border-primary-light h-[75px] w-[95px] max-w-none rounded border-2'
: 'hover:border-primary-light h-[75px] w-[95px] max-w-none rounded border-2 border-black'
}
/>
<label className="text-aqua-pale mt-2 text-left text-xs">
{formatLabel(preset.name, 11)}
</label>
</div>
))}
</div>
</div>
</div>
<footer className="flex h-[60px] w-full items-center justify-end">
<div className="flex">
<Button
name="Cancel"
size={ButtonEnums.size.medium}
type={ButtonEnums.type.secondary}
onClick={onClose}
>
{' '}
Cancel{' '}
</Button>
</div>
</footer>
</div>
<PresetDialog className="h-[500px]">
<PresetDialog.PresetBody>
<PresetDialog.PresetFilter>
<PresetDialog.PresetSearch
value={searchValue}
onChange={handleSearchChange}
placeholder="Search all"
/>
</PresetDialog.PresetFilter>
<PresetDialog.PresetGrid>
{filteredPresets.map((preset, index) => (
<div
key={index}
className="flex cursor-pointer flex-col items-start"
onClick={() => {
setSelectedPreset(preset);
handleApply({ preset: preset.name, viewportId });
}}
>
<Icons.ByName
name={preset.name}
className={
selectedPreset?.name === preset.name
? 'border-highlight h-[75px] w-[95px] max-w-none rounded border-2'
: 'hover:border-highlight h-[75px] w-[95px] max-w-none rounded border-2 border-black'
}
/>
<label className="text-muted-foreground mt-1 text-left text-xs">
{formatLabel(preset.name, 11)}
</label>
</div>
))}
</PresetDialog.PresetGrid>
</PresetDialog.PresetBody>
<FooterAction className="mt-4 flex-shrink-0">
<FooterAction.Right>
<FooterAction.Secondary onClick={hide}>Cancel</FooterAction.Secondary>
</FooterAction.Right>
</FooterAction>
</PresetDialog>
);
}
@@ -1,5 +1,6 @@
import React, { ReactElement, useCallback, useState, useEffect } from 'react';
import { VolumeRenderingQualityProps } from '../../types/ViewportPresets';
import { Numeric } from '@ohif/ui-next';
export function VolumeRenderingQuality({
volumeRenderingQualityRange,
@@ -22,11 +23,6 @@ export function VolumeRenderingQuality({
[commandsManager, viewportId]
);
const calculateBackground = value => {
const percentage = ((value - 0) / (1 - 0)) * 100;
return `linear-gradient(to right, #5acce6 0%, #5acce6 ${percentage}%, #3a3f99 ${percentage}%, #3a3f99 100%)`;
};
useEffect(() => {
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
const { actor } = viewport.getActors()[0];
@@ -41,33 +37,26 @@ export function VolumeRenderingQuality({
setQuality(Math.sqrt(averageSpacing / (sampleDistance * 0.5)));
}
}, [cornerstoneViewportService, viewportId]);
return (
<>
<div className="all-in-one-menu-item flex w-full flex-row !items-center justify-between gap-[10px]">
<label
className="block text-white"
htmlFor="volume"
>
Quality
</label>
{quality !== null && (
<input
className="bg-inputfield-main h-2 w-[120px] cursor-pointer appearance-none rounded-lg"
value={quality}
id="volume"
max={max}
<div className="my-1 mt-2 flex flex-col space-y-2">
{quality !== null && (
<div className="w-full pl-2 pr-1">
<Numeric.Container
mode="singleRange"
min={min}
type="range"
max={max}
step={step}
onChange={e => onChange(parseInt(e.target.value, 10))}
style={{
background: calculateBackground((quality - min) / (max - min)),
'--thumb-inner-color': '#5acce6',
'--thumb-outer-color': '#090c29',
}}
/>
)}
</div>
</>
value={quality}
onChange={onChange}
>
<div className="flex flex-row items-center">
<Numeric.Label className="w-16">Quality</Numeric.Label>
<Numeric.SingleRange sliderClassName="mx-2 flex-grow" />
</div>
</Numeric.Container>
</div>
)}
</div>
);
}
@@ -1,5 +1,5 @@
import React, { ReactElement, useCallback, useEffect, useState } from 'react';
import { SwitchButton } from '@ohif/ui';
import { Switch } from '@ohif/ui-next';
import { VolumeShadeProps } from '../../types/ViewportPresets';
export function VolumeShade({
@@ -28,15 +28,18 @@ export function VolumeShade({
}, [viewportId, cornerstoneViewportService]);
return (
<SwitchButton
key={key}
label="Shade"
checked={shade}
onChange={() => {
setShade(!shade);
onClickShade(!shade);
onShadeChange(!shade);
}}
/>
<>
<span className="flex-grow">Shade</span>
<Switch
className="ml-2 flex-shrink-0"
key={key}
checked={shade}
onCheckedChange={() => {
setShade(!shade);
onClickShade(!shade);
onShadeChange(!shade);
}}
/>
</>
);
}
@@ -1,5 +1,6 @@
import React, { ReactElement, useCallback, useEffect, useState, useRef } from 'react';
import { VolumeShiftProps } from '../../types/ViewportPresets';
import { Numeric } from '@ohif/ui-next';
export function VolumeShift({
viewportId,
@@ -46,48 +47,32 @@ export function VolumeShift({
viewportId,
shift: shiftDifference,
});
setShift(newShift);
},
[commandsManager, viewportId, viewport]
);
const calculateBackground = value => {
const percentage = ((value - 0) / (1 - 0)) * 100;
return `linear-gradient(to right, #5acce6 0%, #5acce6 ${percentage}%, #3a3f99 ${percentage}%, #3a3f99 100%)`;
};
return (
<>
<div className="all-in-one-menu-item flex w-full flex-row !items-center justify-between gap-[10px]">
<label
className="block text-white"
htmlFor="shift"
>
Shift
</label>
{step !== null && (
<input
className="bg-inputfield-main h-2 w-[120px] cursor-pointer appearance-none rounded-lg"
<div className="my-1 mt-2 flex flex-col space-y-2">
{step !== null && minShift !== null && maxShift !== null && (
<div className="w-full pl-2 pr-1">
<Numeric.Container
mode="singleRange"
min={minShift}
max={maxShift}
step={step}
value={shift}
onChange={e => {
const shiftValue = parseInt(e.target.value, 10);
setShift(shiftValue);
onChangeRange(shiftValue);
}}
id="shift"
onChange={onChangeRange}
onMouseDown={() => setIsBlocking(true)}
onMouseUp={() => setIsBlocking(false)}
max={maxShift}
min={minShift}
type="range"
step={step}
style={{
background: calculateBackground((shift - minShift) / (maxShift - minShift)),
'--thumb-inner-color': '#5acce6',
'--thumb-outer-color': '#090c29',
}}
/>
)}
</div>
</>
>
<div className="flex flex-row items-center">
<Numeric.Label className="w-16">Shift</Numeric.Label>
<Numeric.SingleRange sliderClassName="mx-2 flex-grow" />
</div>
</Numeric.Container>
</div>
)}
</div>
);
}
@@ -1,5 +1,5 @@
import React, { ReactElement, useCallback } from 'react';
import { AllInOneMenu } from '@ohif/ui';
import { AllInOneMenu } from '@ohif/ui-next';
import { WindowLevelPreset } from '../../types/WindowLevel';
import { CommandsManager } from '@ohif/core';
import { useTranslation } from 'react-i18next';
@@ -45,6 +45,7 @@ export function WindowLevel({
key={`${modality}-${index}`}
label={preset.description}
secondaryLabel={`${preset.window} / ${preset.level}`}
useIconSpace={false}
onClick={() => onSetWindowLevel(preset)}
/>
))}
@@ -1,7 +1,8 @@
import React, { ReactElement, useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import classNames from 'classnames';
import { AllInOneMenu, useViewportGrid } from '@ohif/ui';
import { AllInOneMenu } from '@ohif/ui-next';
import { useViewportGrid } from '@ohif/ui-next';
import { Colormap } from './Colormap';
import { Colorbar } from './Colorbar';
import { setViewportColorbar } from './Colorbar';
@@ -50,7 +51,7 @@ export function WindowLevelActionMenu({
const { colorbarService, cornerstoneViewportService } = servicesManager.services;
const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId);
const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId);
const backgroundColor = viewportInfo.getViewportOptions().background;
const backgroundColor = viewportInfo?.getViewportOptions().background;
const isLight = backgroundColor ? utilities.isEqual(backgroundColor, [1, 1, 1]) : false;
const { t } = useTranslation('WindowLevelActionMenu');
@@ -118,8 +119,8 @@ export function WindowLevelActionMenu({
iconClassName={classNames(
// Visible on hover and for the active viewport
activeViewportId === viewportId ? 'visible' : 'invisible group-hover/pane:visible',
'flex shrink-0 cursor-pointer rounded active:text-white text-primary-light',
isLight ? ' hover:bg-secondary-dark' : 'hover:bg-secondary-light/60'
'flex shrink-0 cursor-pointer rounded active:text-foreground text-highlight',
isLight ? ' hover:bg-primary/30' : 'hover:bg-primary/30'
)}
menuStyle={{ maxHeight: vpHeight - 32, minWidth: 218 }}
onVisibilityChange={() => {
@@ -143,8 +144,10 @@ export function WindowLevelActionMenu({
key="colorLUTPresets"
itemLabel="Color LUT"
itemIcon="icon-color-lut"
className="flex h-[calc(100%-32px)] flex-col"
>
<Colormap
className="flex h-full w-full flex-col"
colormaps={colormaps}
viewportId={viewportId}
displaySets={displaySets.filter(ds => !nonWLModalities.includes(ds.Modality))}
@@ -2,21 +2,21 @@
// configured via the customization service.
const defaultWindowLevelPresets = {
CT: [
{ description: 'Soft tissue', window: '400', level: '40' },
{ description: 'Lung', window: '1500', level: '-600' },
{ description: 'Liver', window: '150', level: '90' },
{ description: 'Bone', window: '2500', level: '480' },
{ description: 'Brain', window: '80', level: '40' },
{ id: 'ct-soft-tissue', description: 'Soft tissue', window: '400', level: '40' },
{ id: 'ct-lung', description: 'Lung', window: '1500', level: '-600' },
{ id: 'ct-liver', description: 'Liver', window: '150', level: '90' },
{ id: 'ct-bone', description: 'Bone', window: '2500', level: '480' },
{ id: 'ct-brain', description: 'Brain', window: '80', level: '40' },
],
PT: [
{ description: 'Default', window: '5', level: '2.5' },
{ description: 'SUV', window: '0', level: '3' },
{ description: 'SUV', window: '0', level: '5' },
{ description: 'SUV', window: '0', level: '7' },
{ description: 'SUV', window: '0', level: '8' },
{ description: 'SUV', window: '0', level: '10' },
{ description: 'SUV', window: '0', level: '15' },
{ id: 'pt-default', description: 'Default', window: '5', level: '2.5' },
{ id: 'pt-suv-3', description: 'SUV', window: '0', level: '3' },
{ id: 'pt-suv-5', description: 'SUV', window: '0', level: '5' },
{ id: 'pt-suv-7', description: 'SUV', window: '0', level: '7' },
{ id: 'pt-suv-8', description: 'SUV', window: '0', level: '8' },
{ id: 'pt-suv-10', description: 'SUV', window: '0', level: '10' },
{ id: 'pt-suv-15', description: 'SUV', window: '0', level: '15' },
],
};
Loaded 100 of 961 files, more files were not shown because too many files have changed in this diff. Show more