From 2ab641c3825f1e862c03211f33a0fbbaad37c963 Mon Sep 17 00:00:00 2001 From: fuboki10 Date: Sat, 16 Mar 2024 19:11:25 +0200 Subject: [PATCH] Squashed commit of the following: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 08815909c707044b66a86757e5955af3ba116448 Author: ohif-bot Date: Fri Mar 15 13:52:45 2024 +0000 chore(version): Update package versions [skip ci] commit ed2234a67ca24a4eb3ea37b017d9c7fc772151df Author: ohif-bot Date: Fri Mar 15 13:52:34 2024 +0000 chore(version): version.json [skip ci] commit f7fe91c5f6c4f05f3f3f5f640d3a119bd40a5870 Author: Pedro H. Köhler Date: Fri Mar 15 10:35:29 2024 -0300 feat(delete measurement): icon for measurement table (#3775) commit 21ec6860f28cf9af859e4ad2e6b7085958a42286 Author: ohif-bot Date: Fri Mar 8 13:38:48 2024 +0000 chore(version): Update package versions [skip ci] commit ac474b7f8fbe396786ffd3414ef51d52547d9e30 Author: ohif-bot Date: Fri Mar 8 13:38:41 2024 +0000 chore(version): version.json [skip ci] commit e485d68fd4619ce7187113cbe59e47f9523dbcc8 Author: EricB Date: Fri Mar 8 14:27:37 2024 +0100 fix(cli): mode creation template (#3876) (#3981) commit 5089229ff960d32f968ed2bab0d89f6fbaf4affa Author: ohif-bot Date: Tue Mar 5 16:15:56 2024 +0000 chore(version): Update package versions [skip ci] commit df09176d63bf93b49affff8543cd6aae98abba00 Author: ohif-bot Date: Tue Mar 5 16:15:48 2024 +0000 chore(version): version.json [skip ci] commit bb603bd1f8a2ee1fd22292cf9977d96d2eeed718 Author: EricB Date: Tue Mar 5 17:04:28 2024 +0100 docs: Typo (#3975) commit 82a4a7d37727459ae2ddfbea9f78abecea928ff0 Author: ohif-bot Date: Wed Feb 28 17:25:27 2024 +0000 chore(version): Update package versions [skip ci] commit c27fa9c8025097c23b4a8934a3d088c21b692734 Author: ohif-bot Date: Wed Feb 28 17:25:19 2024 +0000 chore(version): version.json [skip ci] commit 4cdfdae8149166cf9dc91a55c0d7f2a224e55d8f Author: EricB Date: Wed Feb 28 18:13:46 2024 +0100 fix(docs): Minor typos in hpModule.md (#3962) commit 596340803a10a6349e35e43e896259464614f01c Author: ohif-bot Date: Thu Feb 22 19:54:20 2024 +0000 chore(version): Update package versions [skip ci] commit ff4e17f5c2c5fc752b20b9af9a4ec6524ffa22f4 Author: ohif-bot Date: Thu Feb 22 19:54:11 2024 +0000 chore(version): version.json [skip ci] commit 21e8a2bd0b7cc72f90a31e472d285d761be15d30 Author: Bill Wallace Date: Thu Feb 22 14:42:46 2024 -0500 fix(demo): Deploy issue (#3951) commit 2a442e30ebc088688ab46642a5d63c7328b9454f Author: ohif-bot Date: Wed Feb 21 18:59:56 2024 +0000 chore(version): Update package versions [skip ci] commit 8e8a66bd29001b95a25768e2e5f85af21e5d6c7d Author: ohif-bot Date: Wed Feb 21 18:59:49 2024 +0000 chore(version): version.json [skip ci] commit b3a0faf5f5f0a1993b2b017eb4cc1216164ea2c6 Author: Alireza Date: Wed Feb 21 13:49:23 2024 -0500 feat(resize): Optimize resizing process and maintain zoom level (#3889) commit a2a0090eeb5f4d736bb34039bc0d2ceeca933a23 Author: ohif-bot Date: Wed Feb 14 14:02:25 2024 +0000 chore(version): Update package versions [skip ci] commit ea552027c5d1e21599744034172ee9a0e69428db Author: ohif-bot Date: Wed Feb 14 14:02:17 2024 +0000 chore(version): version.json [skip ci] commit 0eac386a31a5d6965536360aa65a44769c1a5740 Author: Ibrahim <93064150+IbrahimCSAE@users.noreply.github.com> Date: Wed Feb 14 08:51:17 2024 -0500 feat(errorboundary): format stack trace properly (#3931) commit 40b590d75a7c24c443d0c521689efc30d513a01e Author: ohif-bot Date: Mon Feb 5 19:01:43 2024 +0000 chore(version): Update package versions [skip ci] commit 01df1763ce9254cd7d3ce0338b1c564bef6607a9 Author: ohif-bot Date: Mon Feb 5 19:01:34 2024 +0000 chore(version): version.json [skip ci] commit 5bba98ed848bdf46b5ba4fc4708527cced3308b5 Author: Igor Octaviano Date: Mon Feb 5 15:46:47 2024 -0300 fix: 🐛 Sort merge results based on default data source (input) (#3903) commit 16785936d9d45d3322a030097add549a0c285727 Author: ohif-bot Date: Mon Jan 22 20:52:46 2024 +0000 chore(version): Update package versions [skip ci] commit 3deb73d642d351d4211139a54e987c14ea112471 Author: ohif-bot Date: Mon Jan 22 20:52:38 2024 +0000 chore(version): version.json [skip ci] commit ef46b5a70409053d95a60e0fc80e5e363f38d999 Author: m00n620 <50647994+m00n620@users.noreply.github.com> Date: Mon Jan 22 15:42:51 2024 -0500 fix toggleHpTools to support split primary button (#3900) Fixes a small bug in toggling buttons with hanging protocols. commit ed2bf23cd2acef57845d09ccc9f3c9bcb037ac2a Author: ohif-bot Date: Mon Jan 22 15:07:51 2024 +0000 chore(version): Update package versions [skip ci] commit 2ff2f3f34d5f451eac02b44f19bb7b8d5c5baf97 Author: ohif-bot Date: Mon Jan 22 15:07:43 2024 +0000 chore(version): version.json [skip ci] commit a47aeb8bd729dcb8d2cfc13b27a31b0dd88f11ad Author: Patrick Wespi Date: Mon Jan 22 15:58:22 2024 +0100 fix: catch errors in getPTImageIdInstanceMetadata (#3897) commit 94f40192f5f05c7eecafd26b44ac50c51bc646dd Author: ohif-bot Date: Mon Jan 22 14:55:27 2024 +0000 chore(version): Update package versions [skip ci] commit 1c43aa791ac65a0cc4cdfbe4e7a9aea6690a5f5f Author: ohif-bot Date: Mon Jan 22 14:55:17 2024 +0000 chore(version): version.json [skip ci] commit e4a116b074fcb85c8cbcc9db44fdec565f3386db Author: Alireza Date: Mon Jan 22 09:44:35 2024 -0500 fix(viewport-sync): remember synced viewports bw stack and volume and RENAME StackImageSync to ImageSliceSync (#3849) commit 2460d28afcb8e823f662bc4cc70238773b5c9e6a Author: ohif-bot Date: Fri Jan 19 13:30:55 2024 +0000 chore(version): Update package versions [skip ci] commit 4a958b8989f5608037f891910399b0ea80d0bafa Author: ohif-bot Date: Fri Jan 19 13:30:47 2024 +0000 chore(version): version.json [skip ci] commit 31b837fa90f631d4984482c6e952373fbb8bdbfc Author: Pedro H. Köhler Date: Fri Jan 19 10:21:22 2024 -0300 fix: is same orientaiton (#3905) commit 9bb00d71faf49edd91a346b1cf00578494e1cc18 Author: ohif-bot Date: Wed Jan 17 16:01:31 2024 +0000 chore(version): Update package versions [skip ci] commit 40bbe33a9d68f99a3adb4ff21c0c9c4d35f59fa7 Author: ohif-bot Date: Wed Jan 17 16:01:24 2024 +0000 chore(version): version.json [skip ci] commit 911d67283536b2fe7930948f2819ea0ad66e2a32 Author: Igor Octaviano Date: Wed Jan 17 12:51:49 2024 -0300 fix: 🐛 Check merge key for merge data source (#3901) commit 9c553fded431b22a0704e0b58375707f41608a8f Author: ohif-bot Date: Fri Jan 12 16:10:06 2024 +0000 chore(version): Update package versions [skip ci] commit 144dd3939340919db47470c600052adc1218a790 Author: ohif-bot Date: Fri Jan 12 16:09:58 2024 +0000 chore(version): version.json [skip ci] commit df2f50748e98283db458b0cb2fb20931d536b161 Author: AbishekBista9 <133620692+AbishekBista9@users.noreply.github.com> Date: Fri Jan 12 21:45:17 2024 +0545 docs: Update architecture.md (#3891) commit 83a15234081abf3eddd856aed8cee87bb1f066ec Author: ohif-bot Date: Fri Jan 12 13:58:08 2024 +0000 chore(version): Update package versions [skip ci] commit 5a62a983e545b27890621a4d4dbac86f11c3d767 Author: ohif-bot Date: Fri Jan 12 13:57:58 2024 +0000 chore(version): version.json [skip ci] commit d00a86b022742ea089d246d06cfd691f43b64412 Author: Bill Wallace Date: Fri Jan 12 08:48:15 2024 -0500 fix: Update CS3D to fix second render (#3892) commit 710a2dca722ddf0290cdd2f38856f91e88aea9b5 Author: ohif-bot Date: Tue Jan 9 19:31:20 2024 +0000 chore(version): Update package versions [skip ci] commit 9100b1f02e548dd5f92d10d74e490d020803fdfb Author: ohif-bot Date: Tue Jan 9 19:31:10 2024 +0000 chore(version): version.json [skip ci] commit 0049f4c0303f0b6ea995972326fc8784259f5a47 Author: Igor Octaviano Date: Tue Jan 9 16:22:00 2024 -0300 feat(hp): enable OHIF to run with partial metadata for large studies at the cost of less effective hanging protocol (#3804) Co-authored-by: rodrigobasilio2022 Co-authored-by: rodrigobasilio2022 <114958722+rodrigobasilio2022@users.noreply.github.com> commit b5cdf88a57cdb69759a7d337fe3904575df32d92 Author: ohif-bot Date: Tue Jan 9 17:45:39 2024 +0000 chore(version): Update package versions [skip ci] commit c793df727b42d9e1709b030dcedc9889419dfedd Author: ohif-bot Date: Tue Jan 9 17:45:30 2024 +0000 chore(version): version.json [skip ci] commit 1456a493d66c90c787b022256c9f2846afb115fc Author: Alireza Date: Tue Jan 9 12:36:29 2024 -0500 feat(transferSyntax): prefer server transcoded transfer syntax for all images (#3883) commit 7c551f50432dca86c716468c461098e2f55021a6 Author: ohif-bot Date: Tue Jan 9 15:24:36 2024 +0000 chore(version): Update package versions [skip ci] commit 0b2c2a235f37246f40fff05438d42889a7758b85 Author: ohif-bot Date: Tue Jan 9 15:24:28 2024 +0000 chore(version): version.json [skip ci] commit b1efe40aa146e4052cc47b3f774cabbb47a8d1a6 Author: Alireza Date: Tue Jan 9 10:15:56 2024 -0500 fix(segmentation): upgrade cs3d to fix various segmentation bugs (#3885) commit a8a0bdb2df8ad91ca403b220b295f57dafb05e9c Author: ohif-bot Date: Mon Jan 8 19:34:59 2024 +0000 chore(version): Update package versions [skip ci] commit 6878c1a2648b5f76b419293a29dfd51749b36788 Author: ohif-bot Date: Mon Jan 8 19:34:49 2024 +0000 chore(version): version.json [skip ci] commit d181eb4fdc064ef5c5fada42abb6d9a362284b01 Author: AbishekBista9 <133620692+AbishekBista9@users.noreply.github.com> Date: Tue Jan 9 01:09:42 2024 +0545 docs: Update user-account-control.md (#3877) commit fd809e4a0b53ba43dd69457952431409ae7ffe0c Author: ohif-bot Date: Mon Jan 8 19:21:13 2024 +0000 chore(version): Update package versions [skip ci] commit 4c29670d769146786361db843c71410b66028919 Author: ohif-bot Date: Mon Jan 8 19:21:04 2024 +0000 chore(version): version.json [skip ci] commit f58725ce40685f7297181ef98d81bc28420c8291 Author: Igor Octaviano Date: Mon Jan 8 16:12:26 2024 -0300 feat: Add on mode init hook (#3882) commit 5a1cb842f03c590c9d5c44e36f255664291e0d3a Author: ohif-bot Date: Mon Jan 8 19:08:04 2024 +0000 chore(version): Update package versions [skip ci] commit c5bd79552a3e478b978a8e87296713d6891424ee Author: ohif-bot Date: Mon Jan 8 19:07:56 2024 +0000 chore(version): version.json [skip ci] commit 61bf22c6f80e764bdf5c3b56bb0124a95aa0f793 Author: Sofien-Sellami <73444179+Sofien-Sellami@users.noreply.github.com> Date: Mon Jan 8 19:59:20 2024 +0100 feat(ui): sidePanel expandedWidth (#3728) commit 3d985480720f0973013bb854f36989706f4a6b3b Author: ohif-bot Date: Mon Jan 8 18:53:43 2024 +0000 chore(version): Update package versions [skip ci] commit bf9770a55c966808e061fe64ab7c6aef0d339935 Author: ohif-bot Date: Mon Jan 8 18:53:35 2024 +0000 chore(version): version.json [skip ci] commit 2049c0936c86f819604c243d3dc7b3fe971b5b2c Author: Pedro H. Köhler Date: Mon Jan 8 15:45:24 2024 -0300 feat: improve disableEditing flag (#3875) Co-authored-by: Igor Octaviano commit 7d5607073d71189384f767725631fed60605c596 Author: ohif-bot Date: Mon Jan 8 18:24:06 2024 +0000 chore(version): Update package versions [skip ci] commit 1b6910bf764b7d11046bdd793939c788c5f9b997 Author: ohif-bot Date: Mon Jan 8 18:23:58 2024 +0000 chore(version): version.json [skip ci] commit 98650302c7575f0aea386e32cfc4112c378035e6 Author: Bill Wallace Date: Mon Jan 8 13:15:54 2024 -0500 fix: PDF display request in v3 (#3878) Co-authored-by: Alireza commit bf846c94c378f04b9f44dcd71be3f056dbcfe0b5 Author: Salim Kanoun Date: Mon Jan 8 19:09:22 2024 +0100 fix: convert radian to degree value for mip rotation (#3881) commit b5dc9047ba5dadc1ab7c8d2daf3822977c9160e6 Author: ohif-bot Date: Mon Jan 8 17:37:17 2024 +0000 chore(version): Update package versions [skip ci] commit 636d8b0458a61be051519bace92c9891b7ba43d6 Author: ohif-bot Date: Mon Jan 8 17:37:08 2024 +0000 chore(version): version.json [skip ci] commit e8858f3eb55552f695af4a55980f9ae2e9af7291 Author: Celian-abd <101793092+Celian-abd@users.noreply.github.com> Date: Mon Jan 8 18:28:22 2024 +0100 fix: colormap for stack viewports via HangingProtocol (#3866) commit eb7c950d7e0dee35c0c51ac77e549798278ddcc4 Author: ohif-bot Date: Fri Dec 15 02:56:03 2023 +0000 chore(version): Update package versions [skip ci] commit c6214d66b98a03d2b530479142a95f94ce419e06 Author: ohif-bot Date: Fri Dec 15 02:55:55 2023 +0000 chore(version): version.json [skip ci] commit 0eb502aaf0d95d7515e23ed0e03b140ac17bb862 Author: Alireza Date: Thu Dec 14 21:47:15 2023 -0500 chore(version): upgrade cornerstone3D versions to fix orientation (#3854) commit 44a5a24d4f204b990b79a36242778977e8bf4bcc Author: ohif-bot Date: Thu Dec 14 03:48:22 2023 +0000 chore(version): Update package versions [skip ci] commit b0bf5dc780bd0949cb63e2ae4045252a11632b2e Author: ohif-bot Date: Thu Dec 14 03:48:15 2023 +0000 chore(version): version.json [skip ci] commit 5915fada83d89c9acddeffc0f04a006aeeee2576 Author: Alireza Date: Wed Dec 13 22:39:41 2023 -0500 chore(version): upgrade cornerstone3D versions (#3853) commit 2fa335c6d9c109b5e7f8bdd2c646d48cbbd914c4 Author: ohif-bot Date: Wed Dec 13 21:37:11 2023 +0000 chore(version): Update package versions [skip ci] commit de4ad88a09314777bf9d5932d907a82d66cd3bbe Author: ohif-bot Date: Wed Dec 13 21:37:03 2023 +0000 chore(version): version.json [skip ci] commit 6ca13c0a4cb5a95bbb52b0db902b5dbf72f8aa6e Author: Sofien-Sellami <73444179+Sofien-Sellami@users.noreply.github.com> Date: Wed Dec 13 22:28:20 2023 +0100 fix(icon-style): Ensure consistent icon dimensions (#3727) commit 0177b625ba86760168bc4db58c8a109aa9ee83cb Author: Pedro H. Köhler Date: Wed Dec 13 18:18:49 2023 -0300 feat(overlay): add inline binary overlays (#3852) commit c4064acfbcd389b8ed88e55e21e72c66017dcc60 Author: ohif-bot Date: Wed Dec 13 20:27:22 2023 +0000 chore(version): Update package versions [skip ci] commit 7a05d169ef7add1d43e8e4ef3026fde50b48430f Author: ohif-bot Date: Wed Dec 13 20:27:14 2023 +0000 chore(version): version.json [skip ci] commit 13d0c883f8310a2d0a626e1a4942415bef7d8f9e Author: dxlin Date: Wed Dec 13 15:18:15 2023 -0500 docs: Update HangingProtocolService.md doc sameAs example to contain constr… (#3765) commit c45f60dce2092a86bf6fe145933d8ab589e587b6 Author: ohif-bot Date: Wed Dec 13 20:11:48 2023 +0000 chore(version): Update package versions [skip ci] commit 7bb2713c5c77464320de5dc74714636e2ba42563 Author: ohif-bot Date: Wed Dec 13 20:11:39 2023 +0000 chore(version): version.json [skip ci] commit 97cb1f94e184798d75b54a4446815b8aef10d63e Author: Celian-abd <101793092+Celian-abd@users.noreply.github.com> Date: Wed Dec 13 21:03:09 2023 +0100 fix(dicom-seg) : Fix the missing color tag when loading Dicom-Seg (#3822) Co-authored-by: Alireza commit f99645f3a1f1ddf4bf674afa76dfe00a928130db Author: ohif-bot Date: Wed Dec 13 19:55:36 2023 +0000 chore(version): Update package versions [skip ci] commit ae16c7f9723fc9f6a8fb61c1f51db2cc2f89b0dd Author: ohif-bot Date: Wed Dec 13 19:55:28 2023 +0000 chore(version): version.json [skip ci] commit 9c6e1cd305c334f8e3a2d3d9ac6fb4bba1d34c67 Author: Salim Kanoun Date: Wed Dec 13 20:47:12 2023 +0100 Fix(HPService): custom image load performed is now reset to false on HP exit (#3809) commit 7f0ca857fa2c7fa0990c8a1e952e5f2c63d94765 Author: ohif-bot Date: Wed Dec 13 19:29:55 2023 +0000 chore(version): Update package versions [skip ci] commit 920256d3a40a6b4b0bedfdeb6433cb501c92622c Author: ohif-bot Date: Wed Dec 13 19:29:48 2023 +0000 chore(version): version.json [skip ci] commit e1f55e65f2d2a34136ad5d0b1ada77d337a0ea23 Author: Pedro H. Köhler Date: Wed Dec 13 16:21:46 2023 -0300 feat(customizationService): Enable saving and loading of private tags in SRs (#3842) commit d1c07d90f0aaed2d74d6669b03de5bafb337d731 Author: ohif-bot Date: Wed Dec 13 15:29:16 2023 +0000 chore(version): Update package versions [skip ci] commit b1a04ecac3ccdf2ca9aea9ea59b6eb1e238aa01a Author: ohif-bot Date: Wed Dec 13 15:29:09 2023 +0000 chore(version): version.json [skip ci] commit f707b4ebc996f379cd30337badc06b07e6e35ac5 Author: Sofien-Sellami <73444179+Sofien-Sellami@users.noreply.github.com> Date: Wed Dec 13 16:17:48 2023 +0100 feat(config): Add activateViewportBeforeInteraction parameter for viewport interaction customization (#3847) commit 805c53270f243ec61f142a3ffa0af500021cd5ec Author: Pedro H. Köhler Date: Wed Dec 13 11:25:41 2023 -0300 fix: address and improve system vulnerabilities (#3851) commit d14a8f0199db95cd9e85866a011b64d6bf830d57 Author: Pavel Date: Mon Dec 11 19:43:58 2023 +0500 feat(i18n): enhanced i18n support (#3761) commit 5a5c4f58afc7457a2240489055e89a0da7642771 Author: ohif-bot Date: Fri Dec 8 14:35:30 2023 +0000 chore(version): Update package versions [skip ci] commit 6e5d8b691e7fd6c7d784f87a91adc09a16bcf989 Author: ohif-bot Date: Fri Dec 8 14:35:23 2023 +0000 chore(version): version.json [skip ci] commit 59576d695d4d26601d35c43f73d602f0b12d72bf Author: Joe Boccanfuso <109477394+jbocce@users.noreply.github.com> Date: Fri Dec 8 09:25:57 2023 -0500 feat(HP): Added new 3D hanging protocols to be used in the new layout selector (#3844) commit 78d4bea04df03236c04e580902bdde06d9e013f2 Author: ohif-bot Date: Wed Dec 6 13:37:37 2023 +0000 chore(version): Update package versions [skip ci] commit 806c2f4b3606b7ba4f44f9005d9c64a18cb6918d Author: ohif-bot Date: Wed Dec 6 13:37:29 2023 +0000 chore(version): version.json [skip ci] commit 6651008fbb35dabd5991c7f61128e6ef324012df Author: Alireza Date: Wed Dec 6 08:28:02 2023 -0500 fix(auth): fix the issue with oauth at a non root path (#3840) commit 74c98b6e9f5a851da34a16ee5de1793b17f5b517 Author: ohif-bot Date: Tue Nov 28 18:18:13 2023 +0000 chore(version): Update package versions [skip ci] commit ecee25b1d5444213eccbc656717d3c32f51d5d17 Author: ohif-bot Date: Tue Nov 28 18:18:03 2023 +0000 chore(version): version.json [skip ci] commit f1a67647aed635437b188cea7cf5d5a8fb974bbe Author: Alireza Date: Tue Nov 28 13:04:30 2023 -0500 fix(SM): drag and drop is now fixed for SM (#3813) commit 4f9a00f872daf9d9a58f7ba7a3221254b1736383 Author: ohif-bot Date: Mon Nov 27 13:36:36 2023 +0000 chore(version): Update package versions [skip ci] commit 7673a887e4ae8d4855cecf09670276c5d87abdc8 Author: ohif-bot Date: Mon Nov 27 13:36:29 2023 +0000 chore(version): version.json [skip ci] commit 924affa7b5d420c2f91522a075cecbb3c78e8f52 Author: Joe Boccanfuso <109477394+jbocce@users.noreply.github.com> Date: Mon Nov 27 08:27:38 2023 -0500 fix(cine): Set cine disabled on mode exit. (#3812) commit abac6f583e8adfdd578729ad2a2982af585a4b19 Author: ohif-bot Date: Fri Nov 24 22:41:51 2023 +0000 chore(version): Update package versions [skip ci] commit 36ba20d6e8c553c8bfc9aec0bd266a64c165d628 Author: ohif-bot Date: Fri Nov 24 22:41:42 2023 +0000 chore(version): version.json [skip ci] commit 9d1884d7d8b6b2a1cdc26965a96995838aa72682 Author: Bill Wallace Date: Fri Nov 24 17:32:19 2023 -0500 fix: Update the CS3D packages to add the most recent HTJ2K TSUIDS (#3806) commit 622f18bd3ea0c8c76fc3febab261ce883e5825b7 Author: ohif-bot Date: Fri Nov 24 13:56:33 2023 +0000 chore(version): Update package versions [skip ci] commit 8b6256e76fa61a135374fcb72c6c730ddf9f134e Author: ohif-bot Date: Fri Nov 24 13:56:24 2023 +0000 chore(version): version.json [skip ci] commit c4ff2c2f09546ce8b72eab9c5e7beed611e3cab0 Author: Igor Octaviano Date: Fri Nov 24 10:46:54 2023 -0300 feat: Merge Data Source (#3788) Add the ability to merge two different series queries to generate a complete study query result. Provides basic support for other types of merges, but those aren't yet added as full features. commit 1be5613946e2882a40b6517cf327e0f73d834eff Author: ohif-bot Date: Tue Nov 21 17:47:19 2023 +0000 chore(version): Update package versions [skip ci] commit 6edb1c1e4c00f1e0135592e4f431d52d9fb60e87 Author: ohif-bot Date: Tue Nov 21 17:47:12 2023 +0000 chore(version): version.json [skip ci] commit 404b0a5d535182d1ae44e33f7232db500a7b2c16 Author: Pedro H. Köhler Date: Tue Nov 21 14:38:08 2023 -0300 feat(events): broadcast series summary metadata (#3798) commit 786898fd63cccbb0857dea7accaa3814876880f0 Author: ohif-bot Date: Tue Nov 21 14:15:45 2023 +0000 chore(version): Update package versions [skip ci] commit dfa851600c18cdb3c721dc9815fdbf3c9d58bee4 Author: ohif-bot Date: Tue Nov 21 14:15:37 2023 +0000 chore(version): version.json [skip ci] commit 00e751933ac6d611a34773fa69594243f1b99082 Author: Bill Wallace Date: Tue Nov 21 09:05:25 2023 -0500 fix(DICOM Overlay): The overlay data wasn't being refreshed on change (#3793) commit c4e22c251f2af66f3bc629795339cd3d394f9c89 Author: ohif-bot Date: Tue Nov 21 02:33:40 2023 +0000 chore(version): Update package versions [skip ci] commit 3cf4b6dea508612c5da1b711edf259b9f68cc9ad Author: ohif-bot Date: Tue Nov 21 02:33:32 2023 +0000 chore(version): version.json [skip ci] commit 8c8924af373d906773f5db20defe38628cacd4a0 Author: Bill Wallace Date: Mon Nov 20 21:23:42 2023 -0500 fix(metadata): to handle cornerstone3D update for htj2k (#3783) commit 04121e37132d179a7a8f6aa613132a8ac228a136 Author: ohif-bot Date: Sat Nov 18 01:18:27 2023 +0000 chore(version): Update package versions [skip ci] commit d1eef6dbdb9c81941fdced80592495c756a1d7e4 Author: ohif-bot Date: Sat Nov 18 01:18:18 2023 +0000 chore(version): version.json [skip ci] commit d83beb7c62c1d5be19c54e08d23883f112147fe1 Author: Joe Boccanfuso <109477394+jbocce@users.noreply.github.com> Date: Fri Nov 17 20:08:38 2023 -0500 feat(docs): Added various training videos to support the OHIF CLI tools (#3794) commit 7867b29742b91310d50b36812214b7fca7089dfd Author: ohif-bot Date: Wed Nov 15 14:46:52 2023 +0000 chore(version): Update package versions [skip ci] commit 4a6f1bd4078a26bec68420880b2e2d631c6c80b4 Author: ohif-bot Date: Wed Nov 15 14:46:44 2023 +0000 chore(version): version.json [skip ci] commit b694228dd535e4b97cb86a1dc085b6e8716bdaf3 Author: Igor Octaviano Date: Wed Nov 15 11:37:09 2023 -0300 feat(url): Add SeriesInstanceUIDs wado query param (#3746) commit 013068bcb02ae3dc7458df506d3d1275b0b5e753 Author: ohif-bot Date: Mon Nov 13 15:52:59 2023 +0000 chore(version): Update package versions [skip ci] commit 3fbc23b04c14c86370c4de36dedd4cd56ee1c1ed Author: ohif-bot Date: Mon Nov 13 15:52:50 2023 +0000 chore(version): version.json [skip ci] commit 323401418e7ccab74655ba02f990bbe0ed4e523b Author: Igor Octaviano Date: Mon Nov 13 12:42:55 2023 -0300 fix: 🐛 Run error handler for failed image requests (#3773) commit fc438e90ae494c3be26ee53385c8d226c1e783c2 Author: ohif-bot Date: Mon Nov 13 14:00:34 2023 +0000 chore(version): Update package versions [skip ci] commit 9b7f38467b0292d4e1a0eab422b7650990259c2e Author: ohif-bot Date: Mon Nov 13 14:00:27 2023 +0000 chore(version): version.json [skip ci] commit fd1251f751d8147b8a78c7f4d81c67ba69769afa Author: Bill Wallace Date: Mon Nov 13 08:50:58 2023 -0500 fix(overlay): Overlays aren't shown on undefined origin (#3781) commit 14793493a49a975c1ff723f368e239e787016b99 Author: ohif-bot Date: Fri Nov 10 22:00:19 2023 +0000 chore(version): Update package versions [skip ci] commit 73d884870776f9ec4a9634f6cfcc0d4f9ee5b3cd Author: ohif-bot Date: Fri Nov 10 22:00:11 2023 +0000 chore(version): version.json [skip ci] commit 43b1c17209502e4876ad59bae09ed9442eda8024 Author: Alireza Date: Fri Nov 10 16:51:23 2023 -0500 feat(dicomJSON): Add Loading Other Display Sets and JSON Metadata Generation script (#3777) commit 5b513fd30ecf244c0124abcb21441ec89eaf72eb Author: ohif-bot Date: Fri Nov 10 14:07:30 2023 +0000 chore(version): Update package versions [skip ci] commit 26c9567c38c1d0a99cfc5aeb8c15d9bc3f2a1758 Author: ohif-bot Date: Fri Nov 10 14:07:23 2023 +0000 chore(version): version.json [skip ci] commit 8bbcd0e692e25917c1b6dd94a39fac834c812fca Author: Pedro H. Köhler Date: Fri Nov 10 10:54:40 2023 -0300 fix(path): upgrade docusaurus for security (#3780) commit 8c2f495405599924fb497d93cb9f87a80d8b8dd4 Author: ohif-bot Date: Thu Nov 9 04:13:50 2023 +0000 chore(version): Update package versions [skip ci] commit e1e1e60b7da2522a2749c49339dcd75394b64a6a Author: ohif-bot Date: Thu Nov 9 04:13:43 2023 +0000 chore(version): version.json [skip ci] commit 8af10468035f1f59e0a21e579d50ad63c8cbf7ad Author: edwardyangxin Date: Wed Nov 8 20:04:55 2023 -0800 fix(arrow): ArrowAnnotate text key cause validation error (#3771) commit 4bbba871f5831b2cccee7ce48c6ff4a982803617 Author: ohif-bot Date: Wed Nov 8 21:09:51 2023 +0000 chore(version): Update package versions [skip ci] commit 73e804c311b0cdc1a92c623f6966f22471ecbd96 Author: ohif-bot Date: Wed Nov 8 21:09:42 2023 +0000 chore(version): version.json [skip ci] commit 442f99d5eb2ceece7def20e14da59af1dd7d8442 Author: Pedro H. Köhler Date: Wed Nov 8 18:00:18 2023 -0300 feat: add VolumeViewport rotation (#3776) commit 9575a6f54cbe7ba4ea698075dc238850a0042f21 Author: ohif-bot Date: Wed Nov 8 14:28:55 2023 +0000 chore(version): Update package versions [skip ci] commit dc345d187520811f998bb03d8b15406c73b7cb3e Author: ohif-bot Date: Wed Nov 8 14:28:48 2023 +0000 chore(version): version.json [skip ci] commit bf252bcec2aae3a00479fdcb732110b344bcf2c0 Author: Alireza Date: Wed Nov 8 09:19:49 2023 -0500 feat(hp callback): Add viewport ready callback (#3772) Co-authored-by: Ouwen Huang commit 1a68c06c3bb65f8890cc5acaab76237c9b471064 Author: ohif-bot Date: Fri Nov 3 16:29:50 2023 +0000 chore(version): Update package versions [skip ci] commit a2361fa615d4d298291411bd400c2bce33a3fbdd Author: ohif-bot Date: Fri Nov 3 16:29:42 2023 +0000 chore(version): version.json [skip ci] commit ee67bcc0f55450ccddaa543389180f505e121af5 Author: Alireza Date: Fri Nov 3 12:19:24 2023 -0400 docs(faq): FAQ and Segmentation Mode Fix for some studies (#3762) commit f9f16bc69f70559ccf73c274fd4e0811795ee343 Author: ohif-bot Date: Thu Nov 2 16:17:30 2023 +0000 chore(version): Update package versions [skip ci] commit a29f5bc9d31e83b912f84271bd4f452a89c3dc3f Author: ohif-bot Date: Thu Nov 2 16:17:20 2023 +0000 chore(version): version.json [skip ci] commit b23eeff93745769e67e60c33d75293d6242c5ec9 Author: Joe Boccanfuso <109477394+jbocce@users.noreply.github.com> Date: Thu Nov 2 12:07:52 2023 -0400 fix(thumbnail): Avoid multiple promise creations for thumbnails (#3756) commit 09ff293f5f28c8384cfdfcc56b9d5caef0511520 Author: ohif-bot Date: Tue Oct 31 19:49:41 2023 +0000 chore(version): Update package versions [skip ci] commit 71a30dc9f23b1898727bc4a7a560b8094aebf7b7 Author: ohif-bot Date: Tue Oct 31 19:49:31 2023 +0000 chore(version): version.json [skip ci] commit 330e11c7ff0151e1096e19b8ffdae7d64cae280e Author: Pavel Date: Wed Nov 1 00:36:45 2023 +0500 feat(i18n): enhanced i18n support (#3730) commit c73a403cdcbc687981308d03462d2d2d10f73df5 Author: ohif-bot Date: Mon Oct 30 15:01:05 2023 +0000 chore(version): Update package versions [skip ci] commit 5b3b6eed16fe857fc239a7785e6f73f93706a23d Author: ohif-bot Date: Mon Oct 30 15:00:58 2023 +0000 chore(version): version.json [skip ci] commit 2a15ef0e44b7b4d8bbf5cb9363db6e523201c681 Author: Joe Boccanfuso <109477394+jbocce@users.noreply.github.com> Date: Mon Oct 30 10:47:43 2023 -0400 feat(filters): save worklist query filters to session storage so that they persist between navigation to the viewer and back (#3749) Co-authored-by: ladeirarodolfo <39910206+ladeirarodolfo@users.noreply.github.com> commit db395852b6fc6cd5c265a9282e5eee5bd6f951b7 Author: Joe Boccanfuso <109477394+jbocce@users.noreply.github.com> Date: Mon Oct 30 10:44:21 2023 -0400 fix(measurement service): Implemented correct check of schema keys in _isValidMeasurment. (#3750) commit 423ba7c2694e9d993b8d896a6649eef6ca07a6d4 Author: ohif-bot Date: Wed Oct 25 17:17:55 2023 +0000 chore(version): Update package versions [skip ci] commit cafba997bf19c0233826822c3b27b89d43d0eff1 Author: ohif-bot Date: Wed Oct 25 17:17:47 2023 +0000 chore(version): version.json [skip ci] commit dd6d9768bbca1d3cc472e8c1e6d85822500b96ef Author: Bill Wallace Date: Wed Oct 25 13:08:12 2023 -0400 fix(toolbar): allow customizable toolbar for active viewport and allow active tool to be deactivated via a click (#3608) Co-authored-by: Joe Boccanfuso commit 2145b429209c923ccac09bc21e99dff61ba205ac Author: ohif-bot Date: Tue Oct 24 17:20:41 2023 +0000 chore(version): Update package versions [skip ci] commit a5ad1cc02a6595c24c9475bba389359afb7dea4c Author: ohif-bot Date: Tue Oct 24 17:20:32 2023 +0000 chore(version): version.json [skip ci] commit d98439fe7f3825076dbc87b664a1d1480ff414d3 Author: Joe Boccanfuso <109477394+jbocce@users.noreply.github.com> Date: Tue Oct 24 13:06:12 2023 -0400 fix(sr): dcm4chee requires the patient name for an SR to match what is in the original study (#3739) commit 3bbbcea0d7c5891f0aa31625a9935cbafc9e415f Author: ohif-bot Date: Mon Oct 23 14:03:11 2023 +0000 chore(version): Update package versions [skip ci] commit 6e993511b74e19a2b9af222aff4d271ac7bb9270 Author: ohif-bot Date: Mon Oct 23 14:03:01 2023 +0000 chore(version): version.json [skip ci] commit eec9c33b76f86b2a9effe9ad45831b52c9302063 Author: bluesteel23 <132684122+bluesteel23@users.noreply.github.com> Date: Mon Oct 23 09:53:43 2023 -0400 docs(azure): Azure static Deployment details (#3740) commit c31c7f8e7911f16aaae49ce9a4ca50f79ebbfc9f Author: ohif-bot Date: Mon Oct 23 13:23:07 2023 +0000 chore(version): Update package versions [skip ci] commit a7125f9385075f3a399e51c78f113c4a29c94a1e Author: ohif-bot Date: Mon Oct 23 13:22:59 2023 +0000 chore(version): version.json [skip ci] commit 49514aedfe0498b5bd505193106a9745a6a5b5e6 Author: edwardyangxin Date: Mon Oct 23 06:13:24 2023 -0700 fix(recipes): package.json script orthanc:up docker-compose path (#3741) commit 43c882ac87f395f3a7bb88660b025975d4ce6573 Author: ohif-bot Date: Thu Oct 19 17:39:23 2023 +0000 chore(version): Update package versions [skip ci] commit 912b3c7c5305e0e972357ed4e8db42c471f8a271 Author: ohif-bot Date: Thu Oct 19 17:39:15 2023 +0000 chore(version): version.json [skip ci] commit d9258eca70587cf4dc18be4e56c79b16bae73d6d Author: Joe Boccanfuso <109477394+jbocce@users.noreply.github.com> Date: Thu Oct 19 13:29:44 2023 -0400 fix(cine): Use the frame rate specified in DICOM and optionally auto play cine (#3735) Co-authored-by: rareramos Co-authored-by: Doug Horner Co-authored-by: Rehan <58819707+Rayhan-011@users.noreply.github.com> commit fa599d05fb31300dc757224d95339890ba46ad55 Author: ohif-bot Date: Thu Oct 19 17:10:43 2023 +0000 chore(version): Update package versions [skip ci] commit 5f8325e33870447242586555edb96e2c072fda1b Author: ohif-bot Date: Thu Oct 19 17:10:36 2023 +0000 chore(version): version.json [skip ci] commit 93d798db99c0dee53ef73c376f8a74ac3049cf3f Author: Joe Boccanfuso <109477394+jbocce@users.noreply.github.com> Date: Thu Oct 19 13:02:03 2023 -0400 fix(calibration): No calibration popup caused by perhaps an unused code optimization for production builds (#3736) commit 33f125940863607f8dba82c71b27a43f35431dd5 Author: ohif-bot Date: Thu Oct 12 21:18:38 2023 +0000 chore(version): Update package versions [skip ci] commit c54658a320dc855c5b0319b025e1d307a6fd7116 Author: ohif-bot Date: Thu Oct 12 21:18:31 2023 +0000 chore(version): version.json [skip ci] commit 55002fbd712b6470b2217f75588e6a43366803e6 Author: Alireza Date: Thu Oct 12 17:09:44 2023 -0400 chore(version): move to the next beta version [BUMP BETA] commit 3f9ee1451b8b911cbcc6d8743947e03c001dbe40 Author: ohif-bot Date: Wed Oct 11 16:10:32 2023 +0000 chore(version): Update package versions [skip ci] commit 70ea13ec1f30c47ae60a5af6f265513b86e89bf5 Author: ohif-bot Date: Wed Oct 11 16:10:25 2023 +0000 chore(version): version.json [skip ci] commit 157b88c909d3289cb89ace731c1f9a19d40797ac Author: Alireza Date: Wed Oct 11 12:01:32 2023 -0400 fix(display messages): broken after timings (#3719) commit 75c3f941772debeddd280a21fc3570d926ae7e5f Author: ohif-bot Date: Wed Oct 11 14:19:25 2023 +0000 chore(version): Update package versions [skip ci] commit 6874d5f0b9b6f53900c290f7fe2d2ba5675be964 Author: ohif-bot Date: Wed Oct 11 14:19:16 2023 +0000 chore(version): version.json [skip ci] commit a3f2a1a7b0d16bfcc0ecddc2ab731e54c5e377c8 Author: Alireza Date: Wed Oct 11 10:10:29 2023 -0400 fix(export): wrong export for the tmtv RT function (#3715) commit 8067f0c62c2f5c03265f19e39f5a3888f24a8012 Author: ohif-bot Date: Tue Oct 10 19:30:50 2023 +0000 chore(version): Update package versions [skip ci] commit a894b6ffc599b9e2c0c35ed010bebe2ce96aba87 Author: ohif-bot Date: Tue Oct 10 19:30:41 2023 +0000 chore(version): version.json [skip ci] commit c3a5847dcd3dce4f1c8d8b11af95f79e3f93f70d Author: Joe Boccanfuso <109477394+jbocce@users.noreply.github.com> Date: Tue Oct 10 15:20:47 2023 -0400 fix(i18n): display set(s) are two words for English messages (#3711) commit 132938bbe075b84a239d4610e5e95703c820858d Author: ohif-bot Date: Tue Oct 10 17:32:24 2023 +0000 chore(version): Update package versions [skip ci] commit 5ba63bd010aaa1fa5d28f86a240af0fd1aa06fa8 Author: ohif-bot Date: Tue Oct 10 17:32:14 2023 +0000 chore(version): version.json [skip ci] commit 7c57f67844b790fc6e47ac3f9708bf9d576389c8 Author: Alireza Date: Tue Oct 10 13:21:17 2023 -0400 fix(modules): add stylus loader as an option to be uncommented (#3710) commit dc929d3df592375e9f1873fa7da7474e5dbc9cea Author: ohif-bot Date: Tue Oct 10 14:36:12 2023 +0000 chore(version): Update package versions [skip ci] commit 5a3af831680331aee542b2cc6220edad74f55797 Author: ohif-bot Date: Tue Oct 10 14:36:03 2023 +0000 chore(version): version.json [skip ci] commit a9a6ad50eae67b43b8b34efc07182d788cacdcfe Author: Joe Boccanfuso <109477394+jbocce@users.noreply.github.com> Date: Tue Oct 10 10:25:30 2023 -0400 fix(segmentation): Various fixes for segmentation mode and other (#3709) commit f8455056cdcf32d35d55ca7f78d3f601da78b76d Author: ohif-bot Date: Tue Oct 10 00:58:12 2023 +0000 chore(version): Update package versions [skip ci] commit f9c3d510d371cafb299b1280e658b723b8068883 Author: ohif-bot Date: Tue Oct 10 00:58:05 2023 +0000 chore(version): version.json [skip ci] commit 52f34c64d014f433ec1661a39b47e7fb27f15332 Author: Alireza Date: Mon Oct 9 20:49:10 2023 -0400 fix(voi): should publish voi change event on reset (#3707) commit 7d87766ac40cd5d24930cbb18c67b3e19df18661 Author: ohif-bot Date: Mon Oct 9 17:58:57 2023 +0000 chore(version): Update package versions [skip ci] commit 820bb83b70a97c7f9b5a6a98b0a6ba966d72282d Author: ohif-bot Date: Mon Oct 9 17:58:49 2023 +0000 chore(version): version.json [skip ci] commit 0a42d573bbca7f2551a831a46d3aa6b56674a580 Author: Alireza Date: Mon Oct 9 13:48:31 2023 -0400 fix(modality unit): fix the modality unit per target via upgrade of cs3d (#3706) commit f9f33f74c19e6f75d0d930c7135f4197da5984b4 Author: ohif-bot Date: Mon Oct 9 15:13:37 2023 +0000 chore(version): Update package versions [skip ci] commit 6a5fe59136f08b74e1fd5f3d1b34d8519b8b5f55 Author: ohif-bot Date: Mon Oct 9 15:13:28 2023 +0000 chore(version): version.json [skip ci] commit 4911e4796cef5e22cb7cc0ca73dc5c956bc75339 Author: Alireza Date: Mon Oct 9 11:02:49 2023 -0400 fix(segmentation): do not use SAB if not specified (#3705) commit 349c503f0488ec374ed2f223a22b15720db484ca Author: ohif-bot Date: Fri Oct 6 19:14:47 2023 +0000 chore(version): Update package versions [skip ci] commit a6a6aed361c00b394f4e3eee14dc16a5c3e1380b Author: ohif-bot Date: Fri Oct 6 19:14:40 2023 +0000 chore(version): version.json [skip ci] commit 40673f64b36b1150149c55632aa1825178a39e65 Author: dxlin Date: Fri Oct 6 15:00:04 2023 -0400 feat(Segmentation): download RTSS from Labelmap(#3692) Co-authored-by: Alireza commit eab42c1032150474c64e6afa6cbf343eecf7ea60 Author: ohif-bot Date: Fri Oct 6 03:49:04 2023 +0000 chore(version): Update package versions [skip ci] commit a790be9f5a30f53b2ed68c6b0696247612dd35f4 Author: ohif-bot Date: Fri Oct 6 03:48:56 2023 +0000 chore(version): version.json [skip ci] commit 8bc12a37d0353160ae5ea4624dc0b244b7d59c07 Author: Alireza Date: Thu Oct 5 23:39:56 2023 -0400 fix(bugs): fixing lots of bugs regarding release candidate (#3700) commit 29aeb070e062d5b44ec5d60541ee922cf21f47de Author: ohif-bot Date: Fri Oct 6 02:22:47 2023 +0000 chore(version): Update package versions [skip ci] commit cdb66dcdef7fe4c0d8552fd06f0870d9938e79b6 Author: ohif-bot Date: Fri Oct 6 02:22:39 2023 +0000 chore(version): version.json [skip ci] commit 1fd98d922094d10fe0c6e9df726314ec9fce49e8 Author: Joe Boccanfuso <109477394+jbocce@users.noreply.github.com> Date: Thu Oct 5 22:13:08 2023 -0400 fix(segmentation scroll): and hydration bugs (#3701) commit 748acaf9c1c272a24a2f1bdc5410908e6a88c111 Author: ohif-bot Date: Wed Oct 4 18:25:03 2023 +0000 chore(version): Update package versions [skip ci] commit 3f27c3820f083c963398f524e08654cda2241905 Author: ohif-bot Date: Wed Oct 4 18:24:55 2023 +0000 chore(version): version.json [skip ci] commit c1d5ee7e3f7f4c0c6bed9ae81eba5519741c5155 Author: Joe Boccanfuso <109477394+jbocce@users.noreply.github.com> Date: Wed Oct 4 14:11:09 2023 -0400 fix(measurement and microscopy): various small fixes for measurement and microscopy side panel (#3696) commit 646bef1d930c480c78521e5688a2b02560e6129e Author: ohif-bot Date: Wed Oct 4 17:22:52 2023 +0000 chore(version): Update package versions [skip ci] commit 83c001f8e4614b155e62a656bc89bb2df2273b40 Author: ohif-bot Date: Wed Oct 4 17:22:43 2023 +0000 chore(version): version.json [skip ci] commit ebe8f71da22c1d24b58f889c5d803951e19817b6 Author: Alireza Date: Wed Oct 4 13:11:31 2023 -0400 feat(locale): add German translations - community PR (#3697) Co-authored-by: pwespi commit 17f528bcde0e7f5915b1732d12b78706a10f50ad Author: ohif-bot Date: Wed Oct 4 16:34:07 2023 +0000 chore(version): Update package versions [skip ci] commit ed82511acc71bab82befb4e0cdd46bd7dfd5dcf7 Author: ohif-bot Date: Wed Oct 4 16:33:58 2023 +0000 chore(version): version.json [skip ci] commit 745050a28ec7c2ef2e9a4d4e590040050b2177b2 Author: Alireza Date: Wed Oct 4 12:22:26 2023 -0400 feat(locale): Added Turkish language support (tr-TR) - Community PR (#3695) Co-authored-by: Ahmet Altay <46381367+ahmetaltay33@users.noreply.github.com> commit 953c564d986d822941d677af1a4d0eb295e593a6 Author: ohif-bot Date: Wed Oct 4 16:17:52 2023 +0000 chore(version): Update package versions [skip ci] commit c418185f0b029f5aae109ff1026ee39cfbb4fd95 Author: ohif-bot Date: Wed Oct 4 16:17:44 2023 +0000 chore(version): version.json [skip ci] commit 29748d46a14d23817dbe196e0f64363fc61a8aed Author: wangxuan Date: Thu Oct 5 00:07:30 2023 +0800 fix(translation): Side panel translate fix (#3156) commit a755cce7f1113a420903ef181e0dc6e87f6b02f2 Author: ohif-bot Date: Wed Oct 4 15:37:52 2023 +0000 chore(version): Update package versions [skip ci] commit 42b7d9983c89269e279403b69cdc350f2b28a0eb Author: ohif-bot Date: Wed Oct 4 15:37:44 2023 +0000 chore(version): version.json [skip ci] commit 28cec04ff43b81e218c3e9addef4665b3833a6fe Author: Joe Boccanfuso <109477394+jbocce@users.noreply.github.com> Date: Wed Oct 4 11:27:55 2023 -0400 fix(cli): Add npm packaged mode not working (#3689) commit 01d843f26709268602593c18359aa136544ceacd Author: ohif-bot Date: Tue Oct 3 21:11:31 2023 +0000 chore(version): Update package versions [skip ci] commit 15eca78b68c00bac4b9744be7a54133d0bbce014 Author: ohif-bot Date: Tue Oct 3 21:11:23 2023 +0000 chore(version): version.json [skip ci] commit 108383b9ef51e4bef82d9c932b9bc7aa5354e799 Author: Bill Wallace Date: Tue Oct 3 17:01:00 2023 -0400 feat(debug): Add timing information about time to first image/all images, and query time (#3681) commit 1b207f14d230e725d3621af2a1695ecf916d61e3 Author: ohif-bot Date: Tue Oct 3 19:28:23 2023 +0000 chore(version): Update package versions [skip ci] commit 38cd3d8afc401c08c621dea13baa788cf10bb56a Author: ohif-bot Date: Tue Oct 3 19:28:14 2023 +0000 chore(version): version.json [skip ci] commit 5e7fe91617d7399f85702d82e7bfa028b8010a89 Author: Alireza Date: Tue Oct 3 15:18:15 2023 -0400 feat(displayArea): add display area to hanging protocol (#3691) Co-authored-by: Ouwen Huang commit 64f68f58c2ec8d74927eeb0995e51aba009f5efa Author: ohif-bot Date: Tue Oct 3 18:36:48 2023 +0000 chore(version): Update package versions [skip ci] commit 49bc3466615c0b558edd50d6038a88cc099239a6 Author: ohif-bot Date: Tue Oct 3 18:36:39 2023 +0000 chore(version): version.json [skip ci] commit 5cc1dc9b9c304de39d59d3ad45fefdd0dc2f7049 Author: Yaroslav Halchenko Date: Tue Oct 3 11:26:31 2023 -0700 docs(links): Adding references to two used markdown links (#3650) commit 2a086a699abb9b24c834d49b52fe3f5f7d419da3 Author: ohif-bot Date: Tue Oct 3 18:12:49 2023 +0000 chore(version): Update package versions [skip ci] commit 58f214bca56a4724ffb5aee84f6ce53a5955e3ec Author: ohif-bot Date: Tue Oct 3 18:12:41 2023 +0000 chore(version): version.json [skip ci] commit 4dc2acdefa872dd1d8df47f465e9e9656f95f67f Author: rodrigobasilio2022 <114958722+rodrigobasilio2022@users.noreply.github.com> Date: Tue Oct 3 15:02:21 2023 -0300 fix(editing): regression bug in disable editing (#3687) commit 4c88a4a2fda8a1b6e81126b8f4fe6ee01a5bb7cb Author: ohif-bot Date: Tue Oct 3 16:35:17 2023 +0000 chore(version): Update package versions [skip ci] commit 06facd8ea29162a7f96107b603bfb45136a28cc6 Author: ohif-bot Date: Tue Oct 3 16:35:08 2023 +0000 chore(version): version.json [skip ci] commit eb22328fc05d06fc4411805e7a30f826659d796a Author: Edward Son Date: Tue Oct 3 12:23:40 2023 -0400 fix(typescript error): Change pubSubServiceInterface file type to typescript (#3546) Co-authored-by: edward65 commit b33ad0c7e9a59687f5a938c29c6d4268df5249f7 Author: ohif-bot Date: Tue Oct 3 15:55:03 2023 +0000 chore(version): Update package versions [skip ci] commit 3415cf909f21bc22ced7ad734068e37e2a9a5b14 Author: ohif-bot Date: Tue Oct 3 15:54:54 2023 +0000 chore(version): version.json [skip ci] commit e36a6043315e900eeb6ce183772c7f852f478e96 Author: Joe Boccanfuso <109477394+jbocce@users.noreply.github.com> Date: Tue Oct 3 11:44:56 2023 -0400 fix(dicom overlay): Handle special cases of ArrayBuffer for various DICOM overlay attributes. (#3684) Co-authored-by: Alireza commit 8a335bd03d14ba87d65d7468d93f74040aa828d9 Author: Bill Wallace Date: Tue Oct 3 10:59:09 2023 -0400 fix(StackSync): Miscellaneous fixes for stack image sync (#3663) commit b3429729f18d4531240e33fb31b437a044945b62 Author: ohif-bot Date: Tue Oct 3 14:22:22 2023 +0000 chore(version): Update package versions [skip ci] commit e2e806f188eb52854e3e6632db9f5db0b156e70b Author: ohif-bot Date: Tue Oct 3 14:22:12 2023 +0000 chore(version): version.json [skip ci] commit 1129c155d2c7d46c98a5df7c09879aa3d459fa7e Author: Alireza Date: Tue Oct 3 10:11:32 2023 -0400 fix(config): support more values for the useSharedArrayBuffer (#3688) commit c9c57b04d3a344d4d82a99385e1edbf3acb84cbd Author: ohif-bot Date: Fri Sep 29 22:04:50 2023 +0000 chore(version): Update package versions [skip ci] commit d09938b72a2932d3f11d157549a8cb23e68fee3d Author: ohif-bot Date: Fri Sep 29 22:04:41 2023 +0000 chore(version): version.json [skip ci] commit a67d72de85238b369a18c010bf6d147daefc6df5 Author: Alireza Date: Fri Sep 29 17:54:16 2023 -0400 fix(no sab): should work when shared array buffer is not required (#3686) commit 3dabee23429081bd320d84e96146ce724373aa8c Author: ohif-bot Date: Fri Sep 29 14:51:58 2023 +0000 chore(version): Update package versions [skip ci] commit 66abdace30ef5ea7a646fee7d77c40d8332dd5b9 Author: ohif-bot Date: Fri Sep 29 14:51:49 2023 +0000 chore(version): version.json [skip ci] commit dc73b187484da029a2664bb1302f30137c973b8c Author: Joe Boccanfuso <109477394+jbocce@users.noreply.github.com> Date: Fri Sep 29 10:35:30 2023 -0400 fix(cli): various fixes for adding custom modes and extensions (#3683) Co-authored-by: Alireza commit 52da92fed48bc04db3dab44a2e6d5cdfaaed3f47 Author: ohif-bot Date: Tue Sep 26 20:58:57 2023 +0000 chore(version): Update package versions [skip ci] commit cfbb3cfded90e513630f2af9f2fd953f5ddb4ef5 Author: ohif-bot Date: Tue Sep 26 20:58:49 2023 +0000 chore(version): version.json [skip ci] commit 86f54d0d07042750a863ae876aa8dd5fb16029a5 Author: Joe Boccanfuso <109477394+jbocce@users.noreply.github.com> Date: Tue Sep 26 16:49:06 2023 -0400 fix(toggleOneUp): fixed one up for main tmtv layout (#3677) commit 485cf037a1b2e437d834590509617c70b39d1310 Author: ohif-bot Date: Tue Sep 26 19:38:00 2023 +0000 chore(version): Update package versions [skip ci] commit 3d38122b84e1ba11e862d9948a743810edf5ff0b Author: ohif-bot Date: Tue Sep 26 19:37:51 2023 +0000 chore(version): version.json [skip ci] commit 8feb5ee3f9c1f4d9ddc3bbf9dd8af5ffbf88abfc Author: Alireza Date: Tue Sep 26 15:27:24 2023 -0400 chore(publish): fix timeout problem without lerna (#3679) commit 1f906963e75b9eaae45c93025bb5e7245cec0a17 Author: ohif-bot Date: Tue Sep 26 15:54:37 2023 +0000 chore(version): Update package versions [skip ci] commit 9a4aa1a5aa043832fb0418a07afc89b34d0a4f3b Author: ohif-bot Date: Tue Sep 26 15:54:28 2023 +0000 chore(version): version.json [skip ci] commit fcd323fa8c4d27eb4a614903e65ca98a67c83542 Author: Alireza Date: Tue Sep 26 11:43:37 2023 -0400 chore(publish): fix timeout problem (#3678) commit b6e33224c86de46490787c157d494addbcf72ae6 Author: ohif-bot Date: Tue Sep 26 13:03:46 2023 +0000 chore(version): Update package versions [skip ci] commit b8e519a4a9b884eaeab0f14d5824fcb1050cfe71 Author: ohif-bot Date: Tue Sep 26 13:03:37 2023 +0000 chore(version): version.json [skip ci] commit 0bf9949f45ab9e376e07a1f82e48a6d2047bb787 Author: Alireza Date: Tue Sep 26 08:53:40 2023 -0400 chore(build): try to publish to npm again (#3676) commit 3431b103e2e8afc3a0413525afcae358ec15d1ca Author: ohif-bot Date: Tue Sep 26 03:23:59 2023 +0000 chore(version): Update package versions [skip ci] commit 00918d86d3597ffe7321ce6f0c4169815491ef19 Author: ohif-bot Date: Tue Sep 26 03:23:52 2023 +0000 chore(version): version.json [skip ci] commit c86b129f58e0b26aa9b27b6df6ba61a5d8d93847 Author: Alireza Date: Mon Sep 25 23:13:43 2023 -0400 chore(build): try to publish to npm (#3675) commit 10ca35d5f497021abd562d457d11818474d02868 Author: ohif-bot Date: Fri Sep 22 20:00:44 2023 +0000 chore(version): Update package versions [skip ci] commit a308f7b4b6fe1f725c08b88a887d48d68b7840cc Author: ohif-bot Date: Fri Sep 22 20:00:36 2023 +0000 chore(version): version.json [skip ci] commit 04ca10d8779dd15454920002f3d48afa8830de8a Author: Sofien-Sellami <73444179+Sofien-Sellami@users.noreply.github.com> Date: Fri Sep 22 20:48:12 2023 +0100 fix(react-select): update react select package (#3622) commit 85c899b399e2521480724be145538993721b9378 Author: Joe Boccanfuso <109477394+jbocce@users.noreply.github.com> Date: Fri Sep 22 15:36:44 2023 -0400 feat(SidePanel): new side panel tab look-and-feel (#3657) commit 48bbd6281a497ea68670239f5426a10ee6c56dc1 Author: Alireza Date: Fri Sep 22 10:23:44 2023 -0400 feat(segmentation mode): Add create, and export SEG with Brushes (#3632) commit 896866e9d0083452a9b1428502b4484a64415aa3 Author: ohif-bot Date: Fri Sep 22 13:40:25 2023 +0000 chore(version): Update package versions [skip ci] commit a02a5ded9a93cd877a7080e19723a375f6692129 Author: ohif-bot Date: Fri Sep 22 13:40:16 2023 +0000 chore(version): version.json [skip ci] commit 2bd3b26a6aa54b211ef988f3ad64ef1fe5648bab Author: Alireza Date: Fri Sep 22 09:31:45 2023 -0400 perf(memory): add 16 bit texture via configuration - reduces memory by half (#3662) Co-authored-by: Ouwen Huang commit 034a6d76969cdb9dd9198df3a535b530cbb51f31 Author: ohif-bot Date: Thu Sep 21 20:53:19 2023 +0000 chore(version): Update package versions [skip ci] commit 2fc28978dad12fb228cfe8b9da6d7d7cc7f962d3 Author: ohif-bot Date: Thu Sep 21 20:53:11 2023 +0000 chore(version): version.json [skip ci] commit 221dedde5dd4df086276406a9fa2da1cc23b4eb1 Author: Joe Boccanfuso <109477394+jbocce@users.noreply.github.com> Date: Thu Sep 21 16:45:15 2023 -0400 fix(mpr): Return the original/raw hanging protocol when fetching and preserving the current active protocol. (#3670) commit 807bd663b35ebb25031a843cb3616936c124867c Author: ohif-bot Date: Thu Sep 21 11:38:01 2023 +0000 chore(version): Update package versions [skip ci] commit 03185045977bb17dd9969d5fe0dea8b7b022304d Author: ohif-bot Date: Thu Sep 21 11:37:54 2023 +0000 chore(version): version.json [skip ci] commit d609ae1edb7830cf18cedd05c375e8ac43088fc5 Author: Alireza Date: Thu Sep 21 07:30:01 2023 -0400 docs(url): fix incorrect url param in the doc (#3667) commit 2132f0777ee10f025fae620dbf9155ca399d5a70 Author: ohif-bot Date: Tue Sep 19 14:19:42 2023 +0000 chore(version): Update package versions [skip ci] commit 10ecd4997bd4cecc992f25b6ad2608aba7eb98b1 Author: ohif-bot Date: Tue Sep 19 14:19:34 2023 +0000 chore(version): version.json [skip ci] commit 2d7721cb581f55dc49e3baeca2411b18dd78ad74 Author: Alireza Date: Tue Sep 19 10:11:32 2023 -0400 fix(keyCloak): fix openresty keycloak deployment recipe (#3655) Co-authored-by: Joe Boccanfuso commit c49b8332749087473dcd49d05222c855b785d294 Author: ohif-bot Date: Mon Sep 18 15:10:25 2023 +0000 chore(version): Update package versions [skip ci] commit 3279df543f614549bc486eb454ece1bd1023f6f3 Author: ohif-bot Date: Mon Sep 18 15:10:17 2023 +0000 chore(version): version.json [skip ci] commit 2737903386cf97399473e0fa64fe53ad14da155a Author: Joe Boccanfuso <109477394+jbocce@users.noreply.github.com> Date: Mon Sep 18 11:02:27 2023 -0400 fix(DicomJson): retrieve.series.metadata method should be async (#3659) commit 9ccdaef812ee71ad67655446e1a11c08c0856132 Author: ohif-bot Date: Fri Sep 15 14:58:43 2023 +0000 chore(version): Update package versions [skip ci] commit 8c6764a9367659e7d81eb5fa1600ece6e37bc487 Author: ohif-bot Date: Fri Sep 15 14:58:36 2023 +0000 chore(version): version.json [skip ci] commit 38af3112ec1f94f36c0ef64ff1cf9d21c0981c81 Author: Bill Wallace Date: Fri Sep 15 10:50:23 2023 -0400 fix(measurements): Update the calibration tool to match changes in CS3D (#3505) This change makes the OHIF side consistent with the CS3D user calibration settings, and will correctly display px and rounding values consistent with the CS3D display. commit bd7f9592eaec1cc7c8347bb48419f449bed675ac Author: ohif-bot Date: Tue Sep 12 14:38:15 2023 +0000 chore(version): Update package versions [skip ci] commit fd74e46280306e35e622e15b06bc67aa2420ecb2 Author: ohif-bot Date: Tue Sep 12 14:38:08 2023 +0000 chore(version): version.json [skip ci] commit 74e62a176374f720080d4e777972f70e7f2d8b2b Author: Ibrahim <93064150+IbrahimCSAE@users.noreply.github.com> Date: Tue Sep 12 10:30:02 2023 -0400 fix(health imaging): studies not loading from healthimaging if imagepositionpatient is missing (#3646) commit 7eda0c5f46d6cc38e53db318698f8ac3eb3ae2e1 Author: ohif-bot Date: Tue Sep 12 11:49:05 2023 +0000 chore(version): Update package versions [skip ci] commit 15e1af252645e5ac582f324586869ee14be4010a Author: ohif-bot Date: Tue Sep 12 11:48:58 2023 +0000 chore(version): version.json [skip ci] commit 11ca5b6eae62465ba1656b5ea0c6c6c9f3b02a55 Author: Yaroslav Halchenko Date: Tue Sep 12 07:40:38 2023 -0400 docs(spelling): Add codespell config + github action, run and fix a good number of typos (#3645) commit 64079e0720120a43ee5c1f7c3be2636afe7c5293 Author: ohif-bot Date: Tue Sep 12 02:02:13 2023 +0000 chore(version): Update package versions [skip ci] commit 7f683a336b51e525fc4134a2bcbaffd48788227b Author: ohif-bot Date: Tue Sep 12 02:02:04 2023 +0000 chore(version): version.json [skip ci] commit 0d10f46b885fe54ec3dae1848134da658eb6280a Author: Joe Boccanfuso <109477394+jbocce@users.noreply.github.com> Date: Mon Sep 11 21:50:32 2023 -0400 fix(suv): import calculate-suv library version that prevents SUV calculation for a zero PatientWeight (#3638) commit ca19baeafd2982c93e28677822717407bca40d0c Author: ohif-bot Date: Tue Sep 12 01:39:48 2023 +0000 chore(version): Update package versions [skip ci] commit 3e73ddf4c14cc54b5384d2fbcac7bcb536b6e9d3 Author: ohif-bot Date: Tue Sep 12 01:39:41 2023 +0000 chore(version): version.json [skip ci] commit c0fb2b489209fe7500e1310ad1d6320e6aaeafd5 Author: lokeshnano <37370941+lokeshnano@users.noreply.github.com> Date: Tue Sep 12 07:02:22 2023 +0530 chore(gitignore): add vercel ignore (#3640) commit 2a7787132adf4d878c0801e9d618519a690b3f62 Author: ohif-bot Date: Mon Sep 11 14:40:31 2023 +0000 chore(version): Update package versions [skip ci] commit 3cc35e7ed50acdbff35e6c70c11ae5ed3ba6c9c8 Author: ohif-bot Date: Mon Sep 11 14:40:22 2023 +0000 chore(version): version.json [skip ci] commit de0a74273371ca0f39e8678a098a94db8d88e7a5 Author: Joe Boccanfuso <109477394+jbocce@users.noreply.github.com> Date: Mon Sep 11 10:31:40 2023 -0400 chore(prettier): fix end-of-line warnings (#3642) commit c158279a5349fb372d25d7130531f89891d16f59 Author: ohif-bot Date: Mon Sep 11 13:19:30 2023 +0000 chore(version): Update package versions [skip ci] commit 12acc396d6007ebaaa63eeababee8387aaa3bf6d Author: ohif-bot Date: Mon Sep 11 13:19:23 2023 +0000 chore(version): version.json [skip ci] commit a6e668f8d40d74b99ff4df47812d4928a4549e01 Author: Joe Boccanfuso <109477394+jbocce@users.noreply.github.com> Date: Mon Sep 11 09:11:25 2023 -0400 docs(google cloud healthcare): Added recipe docs for setting up Google Cloud Healthcare API (#3641) commit 6dd74b63717d6367d9dc0018cb3aba3151223063 Author: ohif-bot Date: Wed Sep 6 18:17:59 2023 +0000 chore(version): Update package versions [skip ci] commit 908fb8e14f16d680dd048ce32571528c26a015c4 Author: ohif-bot Date: Wed Sep 6 18:17:50 2023 +0000 chore(version): version.json [skip ci] commit 94f7cfb08e3490488394efc42ef089ebe55e86be Author: Alireza Date: Wed Sep 6 14:09:26 2023 -0400 fix(hotkeys): preserve hotkeys if changed, and reduce re-rendering (#3635) commit 6c8364835b2bf40dd9613e064574002bf6a55f49 Author: ohif-bot Date: Wed Sep 6 13:53:48 2023 +0000 chore(version): Update package versions [skip ci] commit a39929d4d51f382b199dc2c8fb9e02f2771e7a93 Author: ohif-bot Date: Wed Sep 6 13:53:40 2023 +0000 chore(version): version.json [skip ci] commit dd6a8812d131cfa9ed7dc8ca6c4068762d4c8d07 Author: Salim Kanoun Date: Wed Sep 6 15:46:01 2023 +0200 export(checkbox): add missing checkbox export (#3629) commit ce7292936733fac9a72134f41158d58192ffabca Author: ohif-bot Date: Wed Sep 6 13:34:21 2023 +0000 chore(version): Update package versions [skip ci] commit 38ab1ae5a654ed2f82c14e499de68ccbca416dc9 Author: ohif-bot Date: Wed Sep 6 13:34:13 2023 +0000 chore(version): version.json [skip ci] commit 69115da06d2d437b57e66608b435bb0bc919a90f Author: M.D <26860200+md-prog@users.noreply.github.com> Date: Wed Sep 6 09:24:17 2023 -0400 feat(ImageOverlayViewerTool): add ImageOverlayViewer tool that can render image overlay (pixel overlay) of the DICOM images (#3163) Co-authored-by: Joe Boccanfuso commit ef58893c27a53a1a856a45496246ffe286aed5b0 Author: ohif-bot Date: Tue Sep 5 17:13:27 2023 +0000 chore(version): Update package versions [skip ci] commit 7987f0effa1850c2a0159bd9457f0e3c1f098fc3 Author: ohif-bot Date: Tue Sep 5 17:13:19 2023 +0000 chore(version): version.json [skip ci] commit 3ce72254b390f32c9aa207a0589e688805e2659d Author: Joe Boccanfuso <109477394+jbocce@users.noreply.github.com> Date: Tue Sep 5 13:05:36 2023 -0400 fix(nginx archive recipe): Fixes to various configuration files. (#3624) Co-authored-by: Alireza commit 6d47dce4ddfd7ef0dac1eaf73a7c07ac029601a9 Author: ohif-bot Date: Fri Sep 1 20:26:45 2023 +0000 chore(version): Update package versions [skip ci] commit f7cddd08424d765bc5cfc792f9d7366b4a1943e6 Author: ohif-bot Date: Fri Sep 1 20:26:38 2023 +0000 chore(version): version.json [skip ci] commit 9045ddaedca2eb852d8e0652dc6c403db12ce05b Author: Alireza Date: Fri Sep 1 16:19:39 2023 -0400 chore(prettier): full repo linting and code reformatting once and for all (#3627) commit 01500a2d4c79eae6048edc00e1bdfc80fc9d6736 Author: ohif-bot Date: Wed Aug 30 22:46:26 2023 +0000 chore(version): version.json [skip ci] commit b170e7f5ae6621f17caa0dcb518a8af52328899e Author: Alireza Date: Wed Aug 30 18:38:57 2023 -0400 chore(prettier): update prettier to fix publish (#3623) commit 2d7da9c0bb15b39fb0dc4ee19d2d3c2c53f0373c Author: ohif-bot Date: Wed Aug 30 20:54:03 2023 +0000 chore(version): version.json [skip ci] commit 4c6ff873e887cc30ffc09223f5cb99e5f94c9cdd Author: Alireza Date: Wed Aug 30 16:46:55 2023 -0400 feat(grid): remove viewportIndex and only rely on viewportId (#3591) Co-authored-by: Bill Wallace commit 5dafac7c9203c31a47f81021a6e72195527e0bdc Author: ohif-bot Date: Wed Aug 30 13:38:56 2023 +0000 chore(version): Update package versions [skip ci] commit 7ecaf0da5dea3a32d8b9b75247b04b775dbda033 Author: ohif-bot Date: Wed Aug 30 13:38:49 2023 +0000 chore(version): version.json [skip ci] commit adedc8c382e18a2e86a569e3d023cc55a157363f Author: Joe Boccanfuso <109477394+jbocce@users.noreply.github.com> Date: Wed Aug 30 09:27:48 2023 -0400 feat(data source UI config): Popup the configuration dialogue whenever a data source is not fully configured (#3620) commit 5856ae2464f107270d70d67ae527473c41cf3c67 Author: ohif-bot Date: Tue Aug 29 16:44:37 2023 +0000 chore(version): Update package versions [skip ci] commit 84b7de5d9621d389bbd2944eef02f56d3abae5e9 Author: ohif-bot Date: Tue Aug 29 16:44:29 2023 +0000 chore(version): version.json [skip ci] commit 35fc30c5359d8199cc38ffa670c08687d2672f11 Author: mccle <104479423+mccle@users.noreply.github.com> Date: Tue Aug 29 12:36:41 2023 -0400 fix(OpenIdConnectRoutes): fix handleUnauthenticated (#3617) commit eafc716d005b3acf21392a7560c522c8fad0be41 Author: ohif-bot Date: Tue Aug 29 16:33:17 2023 +0000 chore(version): Update package versions [skip ci] commit 88d695cf4febd4426c8055a17b2a64a2b85b62ed Author: ohif-bot Date: Tue Aug 29 16:33:10 2023 +0000 chore(version): version.json [skip ci] commit 44f101d3f2b3204b67e31f4e4939062e65a246ee Author: Joe Boccanfuso <109477394+jbocce@users.noreply.github.com> Date: Tue Aug 29 12:21:21 2023 -0400 fix(PT Metadata): Allow for PatientWeight to be missing from the metadata (#3621) commit a9d6869198aec859c1253348c3399cc6e1bb21d0 Author: ohif-bot Date: Tue Aug 29 15:27:52 2023 +0000 chore(version): Update package versions [skip ci] commit 77c102f993645349938f0b51b96bfa44731e239b Author: ohif-bot Date: Tue Aug 29 15:27:46 2023 +0000 chore(version): version.json [skip ci] commit 75f61f85fe2a21d8988b1b34ea74cf9d6d78ea0b Author: dxlin Date: Tue Aug 29 11:20:01 2023 -0400 fi(buffer): buffer undefined (#3590) commit 9fbdf37f44a6829a130e9d5bda388f2a4d856556 Author: ohif-bot Date: Fri Aug 25 18:27:00 2023 +0000 chore(version): Update package versions [skip ci] commit 7ecd2f162823ac25539315ff98a11421d1c11ee1 Author: ohif-bot Date: Fri Aug 25 18:26:53 2023 +0000 chore(version): version.json [skip ci] commit a336992971c07552c9dbb6e1de43169d37762ef1 Author: Joe Boccanfuso <109477394+jbocce@users.noreply.github.com> Date: Fri Aug 25 14:19:17 2023 -0400 feat(cloud data source config): GUI and API for configuring a cloud data source with Google cloud healthcare implementation (#3589) commit a2ef2b0fcb0ae4a8c02ef07fd97dd6bd6607c509 Author: ohif-bot Date: Wed Aug 23 13:43:54 2023 +0000 chore(version): Update package versions [skip ci] commit eb57c2f8bdacde9b54821a0404673a7e793bc1a2 Author: ohif-bot Date: Wed Aug 23 13:43:46 2023 +0000 chore(version): version.json [skip ci] commit 65b49aeb1b5f38224e4892bdf32453500ee351f8 Author: Alireza Date: Wed Aug 23 09:35:44 2023 -0400 fix(memory leak): array buffer was sticking around in volume viewports (#3611) commit fe0ee050ed4236a8a4d58a23410f99165331605c Author: ohif-bot Date: Wed Aug 16 20:56:02 2023 +0000 chore(version): Update package versions [skip ci] commit ee6f39978757d100e272789e21a24cb153614aae Author: ohif-bot Date: Wed Aug 16 20:55:59 2023 +0000 chore(version): version.json [skip ci] commit b117a54aa10ee09e72e7675c6fb83cd8f49c26f7 Author: Igor Octaviano Date: Wed Aug 16 17:48:13 2023 -0300 fix(rgb): Fix rgb inconsistencies by updating cs3d image loader (#3604) commit ede6300099758b93dbf3ae4fda04476e1ad3cecf Author: ohif-bot Date: Wed Aug 16 16:59:14 2023 +0000 chore(version): Update package versions [skip ci] commit a8717c8625fb1889e184f6925cb651c4ba4f0692 Author: ohif-bot Date: Wed Aug 16 16:59:10 2023 +0000 chore(version): version.json [skip ci] commit 0f9b36906f6f67580879d76cb12f2a320883d4a9 Author: Joe Boccanfuso <109477394+jbocce@users.noreply.github.com> Date: Wed Aug 16 12:51:26 2023 -0400 feat(thumbnail highlight): Thumbnails of hydrated series are now highlighted (#3594) commit 3fe676638bee9b3ad59c84c10a1cf159dddd1a7d Author: ohif-bot Date: Wed Aug 16 15:44:41 2023 +0000 chore(version): Update package versions [skip ci] commit d69dab70e17ae723d6f6cd502f5c2acbdd425c22 Author: ohif-bot Date: Wed Aug 16 15:44:38 2023 +0000 chore(version): version.json [skip ci] commit f845f87716512da78b44c633378d16027813d7b9 Author: rodrigobasilio2022 <114958722+rodrigobasilio2022@users.noreply.github.com> Date: Wed Aug 16 12:37:28 2023 -0300 feat(handler): Add handler for unsupported sopclassUIDs (#3601) commit de6976df3b1a429386ea092f6971e0f828ec6f2c Author: ohif-bot Date: Wed Aug 16 14:47:46 2023 +0000 chore(version): Update package versions [skip ci] commit ea9903e92b1ddbac7c16f8bc247768a031fe168f Author: ohif-bot Date: Wed Aug 16 14:47:42 2023 +0000 chore(version): version.json [skip ci] commit ed0b01cd39930bb24f827608857adc0799fac3bf Author: Igor Octaviano Date: Wed Aug 16 11:40:19 2023 -0300 doc(README.md): Acknowledge IDC (README.md) #1136 (#3576) commit 2c891dae60a646245c7c41cc19f593f38e2c5124 Author: ohif-bot Date: Wed Aug 16 14:33:56 2023 +0000 chore(version): Update package versions [skip ci] commit bb4297af357871fbc9e15ab38534668659ae3783 Author: ohif-bot Date: Wed Aug 16 14:33:52 2023 +0000 chore(version): version.json [skip ci] commit 3c4795d8cbc009b528c038f9ceeb209608c5e3db Author: rodrigobasilio2022 <114958722+rodrigobasilio2022@users.noreply.github.com> Date: Wed Aug 16 11:26:28 2023 -0300 feat(read-only config): read only ui for SEG/RT/SR (#3586) commit c6d5f719e7b21995a260781dca5b6a9da4352a1c Author: ohif-bot Date: Wed Aug 16 14:22:41 2023 +0000 chore(version): Update package versions [skip ci] commit a5bfae6e277334427aacd7aad33dd9849054365b Author: ohif-bot Date: Wed Aug 16 14:22:37 2023 +0000 chore(version): version.json [skip ci] commit 53c16b928f64c3e134c2a201a592539508e01863 Author: Bill Wallace Date: Wed Aug 16 10:15:12 2023 -0400 feat(hp priors): Study comparison hanging protocol (#3579) commit ecbe7c56e04ab316563281f1e7b972ae3c91ed2b Author: ohif-bot Date: Wed Aug 16 03:06:31 2023 +0000 chore(version): Update package versions [skip ci] commit c4007d4d0dfc3354f55bffe877a02e6244e53d17 Author: ohif-bot Date: Wed Aug 16 03:06:27 2023 +0000 chore(version): version.json [skip ci] commit 19408c987fa705aa8782a30820c0c012d0f3132f Author: rodrigobasilio2022 <114958722+rodrigobasilio2022@users.noreply.github.com> Date: Tue Aug 15 23:59:29 2023 -0300 feat(errorHandling): Create fallback page for Server, Study and Series (#3592) commit 9c51f17c60c7e57ff2b8c2504a0bd5edfb7dc7c9 Author: ohif-bot Date: Tue Aug 15 11:29:22 2023 +0000 chore(version): Update package versions [skip ci] commit e664fffbc0af81cdea279d02a1feb8b89b995603 Author: ohif-bot Date: Tue Aug 15 11:29:18 2023 +0000 chore(version): version.json [skip ci] commit 884577a23d9b44505321fbb09dc5c0a42aaa69a1 Author: Bill Wallace Date: Tue Aug 15 07:20:45 2023 -0400 fix: Integration test instability (#3597) There are a couple of changes here which should improve stability of the tests. Hopefully that will be sufficient, but it is difficult to tell for sure if it is complete. commit 799d0793fd34c67438404b3d0773c67e6fa22108 Author: ohif-bot Date: Mon Aug 14 15:07:20 2023 +0000 chore(version): Update package versions [skip ci] commit 7074c6d806d71436a68c655fb8f11de97668d712 Author: ohif-bot Date: Mon Aug 14 15:07:16 2023 +0000 chore(version): version.json [skip ci] commit 41dc424d4a7d94adc8a9fea01359eaffce197275 Author: Patrick D. Lloyd Date: Mon Aug 14 07:46:41 2023 -0700 fix(Docker): Fix ARM64 Docker builds by adding node-gyp dependencies (#3595) (#3596) commit 88ef7b733fddbb244549e0e20e9cba40ebf25d06 Author: ohif-bot Date: Fri Aug 11 22:23:34 2023 +0000 chore(version): Update package versions [skip ci] commit 2c38f6ba8c7d4436073dd0b1f317714e3af2eb14 Author: ohif-bot Date: Fri Aug 11 22:23:31 2023 +0000 chore(version): version.json [skip ci] commit 5302e5b62be22eb503d4fe067b435f7934284c39 Author: rodrigobasilio2022 <114958722+rodrigobasilio2022@users.noreply.github.com> Date: Fri Aug 11 19:16:25 2023 -0300 feat(Thumbnail): Display set messages support & displaying of series inconsistencies in the thumbnail (#3499) Co-authored-by: Igor Octaviano --- .circleci/config.yml | 382 +- .codespellrc | 6 + .docker/Nginx-Orthanc/config/nginx.conf | 48 - .docker/Nginx-Orthanc/config/orthanc.json | 89 - .docker/Nginx-Orthanc/docker-compose.yml | 23 - .../volumes/orthanc-db/.gitignore | 2 - .dockerignore | 7 + .eslintrc.json | 7 +- .github/ISSUE_TEMPLATE/bug-report.yml | 14 +- .github/ISSUE_TEMPLATE/feature-request.yml | 8 +- .github/pull_request_template.md | 2 +- .github/stale.yml | 5 +- .github/workflows/codespell.yml | 22 + .gitignore | 3 + .gitmodules | 1 + .netlify/www/index.html | 36 +- .node-version | 2 +- .prettierrc | 7 +- .scripts/dicom-json-generator.js | 265 + .vscode/settings.json | 2 +- .webpack/helpers/excludeNodeModulesExcept.js | 4 +- .webpack/rules/cssToJavaScript.js | 4 +- .webpack/rules/stylusToJavaScript.js | 10 + .webpack/webpack.base.js | 69 +- CHANGELOG.md | 1217 ++ Dockerfile | 5 +- README.md | 12 +- babel.config.js | 8 +- commit.txt | 2 +- extensions/_example/src/index.js | 138 - .../.webpack/webpack.prod.js | 11 +- extensions/cornerstone-dicom-rt/CHANGELOG.md | 938 ++ .../cornerstone-dicom-rt/babel.config.js | 6 +- extensions/cornerstone-dicom-rt/package.json | 12 +- .../src/getSopClassHandlerModule.js | 26 +- extensions/cornerstone-dicom-rt/src/index.tsx | 6 +- .../cornerstone-dicom-rt/src/loadRTStruct.js | 76 +- .../src/utils/_hydrateRT.ts | 70 - .../src/utils/initRTToolGroup.ts | 11 +- .../src/utils/promptHydrateRT.ts | 19 +- .../viewports/OHIFCornerstoneRTViewport.tsx | 100 +- .../src/viewports/_getStatusComponent.tsx | 22 +- .../.webpack/webpack.prod.js | 16 +- extensions/cornerstone-dicom-seg/CHANGELOG.md | 966 ++ .../cornerstone-dicom-seg/babel.config.js | 6 +- extensions/cornerstone-dicom-seg/package.json | 15 +- .../src/commandsModule.ts | 435 + .../src/getHangingProtocolModule.ts | 1 - .../src/getPanelModule.tsx | 70 + .../src/getSopClassHandlerModule.js | 98 +- .../cornerstone-dicom-seg/src/index.tsx | 44 +- extensions/cornerstone-dicom-seg/src/init.ts | 5 + .../src/panels/PanelSegmentation.tsx | 207 +- .../src/panels/SegmentationToolbox.tsx | 425 + .../src/panels/callInputDialog.tsx | 2 +- .../src/panels/colorPickerDialog.css | 3 + .../src/panels/colorPickerDialog.tsx | 58 + .../src/utils/_hydrateSEG.ts | 69 - .../src/utils/dicomlabToRGB.ts | 4 +- .../src/utils/hydrationUtils.ts | 190 + .../src/utils/initSEGToolGroup.ts | 11 +- .../src/utils/promptHydrateSEG.ts | 20 +- .../viewports/OHIFCornerstoneSEGViewport.tsx | 119 +- .../src/viewports/_getStatusComponent.tsx | 22 +- .../.webpack/webpack.prod.js | 8 +- extensions/cornerstone-dicom-sr/CHANGELOG.md | 983 ++ extensions/cornerstone-dicom-sr/package.json | 20 +- .../src/commandsModule.js | 55 +- .../src/getHangingProtocolModule.ts | 1 - .../src/getSopClassHandlerModule.ts | 247 +- extensions/cornerstone-dicom-sr/src/index.tsx | 10 +- extensions/cornerstone-dicom-sr/src/init.ts | 4 +- .../src/tools/DICOMSRDisplayTool.ts | 74 +- .../src/tools/modules/dicomSRModule.js | 8 +- .../src/utils/addMeasurement.ts | 48 +- .../src/utils/addToolInstance.ts | 6 +- .../utils/createReferencedImageDisplaySet.ts | 16 +- .../findInstanceMetadataBySopInstanceUid.js | 3 +- .../utils/findMostRecentStructuredReport.js | 12 +- .../utils/getFilteredCornerstoneToolState.ts | 21 +- .../getLabelFromDCMJSImportedToolData.js | 4 +- .../src/utils/hydrateStructuredReport.js | 95 +- .../src/utils/isRehydratable.js | 16 +- .../viewports/OHIFCornerstoneSRViewport.tsx | 127 +- .../cornerstone/.webpack/webpack.prod.js | 8 +- extensions/cornerstone/CHANGELOG.md | 1047 ++ extensions/cornerstone/package.json | 22 +- .../src/Viewport/OHIFCornerstoneViewport.tsx | 309 +- .../Viewport/Overlays/CornerstoneOverlays.tsx | 16 +- .../Overlays/CustomizableViewportOverlay.tsx | 87 +- .../Overlays/ViewportImageScrollbar.tsx | 49 +- .../ViewportImageSliceLoadingIndicator.tsx | 28 +- .../Overlays/ViewportOrientationMarkers.tsx | 40 +- .../src/Viewport/Overlays/ViewportOverlay.tsx | 46 +- .../src/Viewport/Overlays/utils.ts | 14 +- extensions/cornerstone/src/commandsModule.ts | 275 +- .../src/components/CinePlayer/CinePlayer.tsx | 80 +- .../components/DicomUpload/DicomUpload.css | 23 +- .../components/DicomUpload/DicomUpload.tsx | 35 +- .../DicomUpload/DicomUploadProgress.tsx | 136 +- .../DicomUpload/DicomUploadProgressItem.tsx | 33 +- .../src/getHangingProtocolModule.ts | 329 +- extensions/cornerstone/src/hps/fourUp.ts | 130 + extensions/cornerstone/src/hps/main3D.ts | 156 + extensions/cornerstone/src/hps/mpr.ts | 153 + .../src/hps/mprAnd3DVolumeViewport.ts | 138 + extensions/cornerstone/src/hps/only3D.ts | 61 + extensions/cornerstone/src/hps/primary3D.ts | 156 + .../cornerstone/src/hps/primaryAxial.ts | 131 + extensions/cornerstone/src/index.tsx | 18 +- extensions/cornerstone/src/init.tsx | 251 +- extensions/cornerstone/src/initContextMenu.ts | 42 +- .../cornerstone/src/initCornerstoneTools.js | 12 + extensions/cornerstone/src/initDoubleClick.ts | 18 +- .../cornerstone/src/initMeasurementService.js | 36 +- .../cornerstone/src/initWADOImageLoader.js | 16 +- .../CornerstoneCacheService.ts | 61 +- .../RTSTRUCT/mapROIContoursToRTStructData.ts | 43 +- .../SegmentationService.ts | 731 +- .../SegmentationServiceTypes.ts | 4 +- .../SyncGroupService/SyncGroupService.ts | 50 +- .../ToolGroupService/ToolGroupService.ts | 110 +- .../CornerstoneViewportService.ts | 813 +- .../ViewportService/IViewportService.ts | 31 +- .../src/services/ViewportService/Viewport.ts | 81 +- extensions/cornerstone/src/state.ts | 14 +- .../src/tools/CalibrationLineTool.ts | 31 +- .../src/tools/ImageOverlayViewerTool.tsx | 269 + .../src/tools/OverlayPlaneModuleProvider.ts | 40 + .../cornerstone/src/types/Presentation.ts | 48 +- extensions/cornerstone/src/types/index.ts | 7 +- .../utils/CornerstoneViewportDownloadForm.tsx | 101 +- .../src/utils/DicomFileUploader.ts | 30 +- .../cornerstone/src/utils/callInputDialog.tsx | 8 +- .../src/utils/dicomLoaderService.js | 18 +- .../utils/getActiveViewportEnabledElement.ts | 8 +- .../src/utils/getCornerstoneBlendMode.ts | 4 +- .../src/utils/getCornerstoneOrientation.ts | 4 +- .../src/utils/getCornerstoneViewportType.ts | 8 +- .../src/utils/getInterleavedFrames.js | 2 +- .../cornerstone/src/utils/getNthFrames.js | 14 +- .../calculateViewportRegistrations.ts | 4 +- .../imageSliceSync/toggleImageSliceSync.ts | 81 + .../cornerstone/src/utils/initViewTiming.ts | 52 + .../src/utils/interleaveCenterLoader.ts | 30 +- .../src/utils/interleaveTopToBottom.ts | 30 +- .../utils/measurementServiceMappings/Angle.ts | 45 +- .../ArrowAnnotate.ts | 26 +- .../Bidirectional.ts | 54 +- .../measurementServiceMappings/CircleROI.ts | 53 +- .../measurementServiceMappings/CobbAngle.ts | 45 +- .../EllipticalROI.ts | 56 +- .../measurementServiceMappings/Length.ts | 31 +- .../PlanarFreehandROI.ts | 23 +- .../RectangleROI.ts | 53 +- .../measurementServiceMappingsFactory.ts | 11 +- .../utils/getDisplayUnit.js | 3 + .../measurementServiceMappings/utils/index.ts | 2 + .../utils/selection.ts | 14 +- extensions/cornerstone/src/utils/nthLoader.ts | 30 +- ...oveToolGroupSegmentationRepresentations.ts | 4 +- .../utils/stackSync/toggleStackImageSync.ts | 115 - extensions/default/.webpack/webpack.dev.js | 1 - extensions/default/.webpack/webpack.prod.js | 8 +- extensions/default/CHANGELOG.md | 1029 ++ extensions/default/babel.config.js | 1 + extensions/default/jest.config.js | 17 + extensions/default/package.json | 14 +- .../default/src/Actions/createReportAsync.tsx | 38 +- .../DataSourceConfigurationComponent.tsx | 121 + .../DataSourceConfigurationModalComponent.tsx | 195 + .../src/Components/ItemListComponent.tsx | 86 + .../src/Components/SidePanelWithServices.tsx | 63 + .../ContextMenuController.tsx | 34 +- .../ContextMenuItemsBuilder.test.js | 18 +- .../ContextMenuItemsBuilder.ts | 25 +- .../src/CustomizableContextMenu/types.ts | 4 +- .../GoogleCloudDataSourceConfigurationAPI.ts | 236 + .../default/src/DicomJSONDataSource/index.js | 53 +- .../default/src/DicomLocalDataSource/index.js | 16 +- .../src/DicomTagBrowser/DicomTagBrowser.css | 4 +- .../src/DicomTagBrowser/DicomTagBrowser.tsx | 96 +- .../src/DicomTagBrowser/DicomTagTable.tsx | 61 +- .../src/DicomWebDataSource/dcm4cheeReject.js | 4 +- .../default/src/DicomWebDataSource/index.js | 214 +- .../default/src/DicomWebDataSource/qido.js | 17 +- .../retrieveStudyMetadata.js | 65 +- .../utils/StaticWadoClient.ts | 26 +- .../utils/fixBulkDataURI.ts | 5 +- .../DicomWebDataSource/utils/getImageId.js | 7 +- .../utils/retrieveMetadataFiltered.js | 56 + .../wado/retrieveMetadata.js | 23 +- .../wado/retrieveMetadataLoader.js | 20 +- .../wado/retrieveMetadataLoaderAsync.js | 122 +- .../wado/retrieveMetadataLoaderSync.js | 10 +- .../src/DicomWebProxyDataSource/index.js | 24 +- .../default/src/MergeDataSource/index.test.js | 203 + .../default/src/MergeDataSource/index.ts | 293 + .../default/src/MergeDataSource/types.ts | 46 + .../default/src/Panels/ActionButtons.tsx | 16 +- .../default/src/Panels/DataSourceSelector.tsx | 14 +- .../src/Panels/PanelMeasurementTable.tsx | 77 +- .../default/src/Panels/PanelStudyBrowser.tsx | 185 +- .../src/Panels/WrappedPanelStudyBrowser.tsx | 18 +- .../src/Panels/createReportDialogPrompt.tsx | 73 +- extensions/default/src/Panels/debounce.js | 4 +- extensions/default/src/Panels/index.js | 8 +- extensions/default/src/Toolbar/Toolbar.tsx | 66 +- .../src/Toolbar/ToolbarButtonWithServices.tsx | 75 + .../default/src/Toolbar/ToolbarDivider.tsx | 4 +- .../src/Toolbar/ToolbarLayoutSelector.tsx | 73 +- .../src/Toolbar/ToolbarSplitButton.tsx | 3 - .../ToolbarSplitButtonWithServices.tsx | 186 + .../default/src/ViewerLayout/ViewerHeader.tsx | 118 + extensions/default/src/ViewerLayout/index.tsx | 159 +- extensions/default/src/commandsModule.ts | 226 +- .../default/src/findViewportsByPosition.ts | 33 +- .../default/src/getCustomizationModule.tsx | 36 +- .../default/src/getDataSourcesModule.js | 6 + .../default/src/getDisplaySetMessages.ts | 48 + .../getDisplaySetsFromUnsupportedSeries.js | 30 + .../default/src/getHangingProtocolModule.js | 9 +- .../default/src/getLayoutTemplateModule.js | 7 +- .../src/getPTImageIdInstanceMetadata.ts | 55 +- extensions/default/src/getPanelModule.tsx | 15 +- .../default/src/getSopClassHandlerModule.js | 21 +- extensions/default/src/getToolbarModule.tsx | 18 +- extensions/default/src/hpCompare.ts | 188 + extensions/default/src/hpMNGrid.ts | 2 +- extensions/default/src/index.ts | 9 +- extensions/default/src/init.ts | 66 +- .../src/utils/calculateScanAxisNormal.ts | 20 + .../utils/findSRWithSameSeriesDescription.ts | 14 +- extensions/default/src/utils/getDirectURL.js | 18 +- .../default/src/utils/reuseCachedLayouts.ts | 32 +- .../validations/areAllImageComponentsEqual.ts | 24 + .../validations/areAllImageDimensionsEqual.ts | 25 + .../areAllImageOrientationsEqual.ts | 25 + .../validations/areAllImagePositionsEqual.ts | 69 + .../validations/areAllImageSpacingEqual.ts | 64 + .../src/utils/validations/checkMultiframe.ts | 25 + .../utils/validations/checkSingleFrames.ts | 35 + extensions/dicom-microscopy/.prettierrc | 7 +- .../dicom-microscopy/.webpack/webpack.dev.js | 1 - extensions/dicom-microscopy/CHANGELOG.md | 938 ++ extensions/dicom-microscopy/babel.config.js | 6 +- extensions/dicom-microscopy/package.json | 12 +- .../src/DicomMicroscopySRSopClassHandler.js | 56 +- .../src/DicomMicroscopySopClassHandler.js | 17 +- .../src/DicomMicroscopyViewport.css | 14 +- .../src/DicomMicroscopyViewport.tsx | 71 +- .../MicroscopyPanel/MicroscopyPanel.tsx | 122 +- .../ViewportOverlay/ViewportOverlay.css | 2 +- .../src/components/ViewportOverlay/index.tsx | 18 +- .../src/components/ViewportOverlay/utils.ts | 17 +- .../dicom-microscopy/src/getCommandsModule.ts | 47 +- .../dicom-microscopy/src/getPanelModule.tsx | 7 +- extensions/dicom-microscopy/src/index.tsx | 38 +- .../src/services/MicroscopyService.ts | 103 +- .../src/tools/viewerManager.js | 57 +- .../src/utils/RoiAnnotation.js | 18 +- .../src/utils/areaOfPolygon.js | 4 +- .../src/utils/callInputDialog.tsx | 2 +- .../src/utils/cleanDenaturalizedDataset.ts | 24 +- .../dicom-microscopy/src/utils/constructSR.ts | 103 +- .../coordinateFormatScoord3d2Geometry.js | 5 +- .../src/utils/dicomWebClient.ts | 29 +- .../src/utils/getSourceDisplaySet.js | 12 +- .../dicom-microscopy/src/utils/loadSR.js | 55 +- extensions/dicom-pdf/.webpack/webpack.prod.js | 11 +- extensions/dicom-pdf/CHANGELOG.md | 926 ++ extensions/dicom-pdf/package.json | 10 +- .../dicom-pdf/src/getSopClassHandlerModule.js | 30 +- extensions/dicom-pdf/src/index.tsx | 8 +- .../viewports/OHIFCornerstonePdfViewport.tsx | 8 +- .../dicom-video/.webpack/webpack.prod.js | 11 +- extensions/dicom-video/CHANGELOG.md | 926 ++ extensions/dicom-video/package.json | 10 +- .../src/getSopClassHandlerModule.js | 46 +- extensions/dicom-video/src/index.tsx | 8 +- .../OHIFCornerstoneVideoViewport.tsx | 14 +- .../.webpack/webpack.prod.js | 11 +- extensions/measurement-tracking/CHANGELOG.md | 990 ++ extensions/measurement-tracking/package.json | 18 +- .../src/_shared/createReportAsync.tsx | 73 - .../src/_shared/createReportDialogPrompt.tsx | 87 - .../TrackedMeasurementsContext.tsx | 87 +- .../hydrateStructuredReport.tsx | 16 +- .../measurementTrackingMachine.js | 49 +- .../promptBeginTracking.js | 26 +- .../promptHydrateStructuredReport.js | 37 +- .../promptSaveReport.js | 62 +- .../promptTrackNewSeries.js | 28 +- .../promptTrackNewStudy.js | 35 +- .../src/getContextModule.tsx | 15 +- .../src/getPanelModule.tsx | 18 +- .../src/getViewportModule.tsx | 10 +- .../PanelMeasurementTableTracking/index.tsx | 100 +- .../PanelStudyBrowserTracking.tsx | 247 +- .../PanelStudyBrowserTracking/index.tsx | 22 +- .../viewports/TrackedCornerstoneViewport.tsx | 117 +- .../test-extension/.webpack/webpack.dev.js | 1 - .../test-extension/.webpack/webpack.prod.js | 10 +- extensions/test-extension/CHANGELOG.md | 929 ++ extensions/test-extension/package.json | 10 +- .../src/custom-attribute/maxNumImageFrames.ts | 4 +- .../src/custom-attribute/sameAs.ts | 13 +- .../contextMenuCodeItem.ts | 2 +- .../src/getCustomizationModule.ts | 6 +- extensions/test-extension/src/hpTestSwitch.ts | 239 + extensions/test-extension/src/index.tsx | 12 + extensions/tmtv/.webpack/webpack.dev.js | 2 +- extensions/tmtv/.webpack/webpack.prod.js | 11 +- extensions/tmtv/CHANGELOG.md | 938 ++ extensions/tmtv/package.json | 10 +- extensions/tmtv/src/Panels/PanelPetSUV.tsx | 34 +- .../ExportReports.tsx | 20 +- .../PanelROIThresholdSegmentation.tsx | 38 +- .../ROIThresholdConfiguration.tsx | 50 +- .../segmentationEditHandler.tsx | 2 +- extensions/tmtv/src/commandsModule.js | 101 +- .../tmtv/src/getHangingProtocolModule.js | 1 - extensions/tmtv/src/getPanelModule.tsx | 6 +- extensions/tmtv/src/index.tsx | 7 +- extensions/tmtv/src/init.js | 12 +- extensions/tmtv/src/utils/calculateSUVPeak.ts | 36 +- extensions/tmtv/src/utils/calculateTMTV.ts | 10 +- extensions/tmtv/src/utils/colormaps/index.js | 10714 +++------------ .../src/utils/createAndDownloadTMTVReport.js | 9 +- .../RTStructureSet/RTSSReport.js | 262 - .../RTStructureSet/dicomRTAnnotationExport.js | 10 +- .../measurements/AnnotationToPointData.js | 58 - .../RectangleROIStartEndThreshold.js | 56 - .../tmtv/src/utils/getThresholdValue.ts | 12 +- extensions/tmtv/src/utils/hpViewports.ts | 21 + .../RectangleROIStartEndThreshold.js | 12 +- jest.config.js | 2 +- lerna.json | 5 +- modes/basic-dev-mode/.webpack/webpack.dev.js | 1 - modes/basic-dev-mode/.webpack/webpack.prod.js | 9 +- modes/basic-dev-mode/CHANGELOG.md | 933 ++ modes/basic-dev-mode/package.json | 19 +- modes/basic-dev-mode/src/index.js | 19 +- modes/basic-test-mode/.webpack/webpack.dev.js | 3 +- .../basic-test-mode/.webpack/webpack.prod.js | 10 +- modes/basic-test-mode/CHANGELOG.md | 946 ++ modes/basic-test-mode/package.json | 25 +- .../src/{index.js => index.ts} | 89 +- .../{initToolGroups.js => initToolGroups.ts} | 126 +- modes/basic-test-mode/src/moreTools.ts | 231 + modes/basic-test-mode/src/moreToolsMpr.ts | 142 + .../{toolbarButtons.js => toolbarButtons.ts} | 256 +- modes/longitudinal/.webpack/webpack.dev.js | 1 - modes/longitudinal/.webpack/webpack.prod.js | 8 +- modes/longitudinal/CHANGELOG.md | 948 ++ modes/longitudinal/package.json | 25 +- modes/longitudinal/src/index.js | 103 +- modes/longitudinal/src/initToolGroups.js | 130 +- modes/longitudinal/src/moreTools.ts | 298 + modes/longitudinal/src/moreToolsMpr.ts | 242 + modes/longitudinal/src/toolbarButtons.ts | 340 + modes/microscopy/.prettierrc | 7 +- modes/microscopy/.webpack/webpack.dev.js | 3 - modes/microscopy/.webpack/webpack.prod.js | 8 +- modes/microscopy/CHANGELOG.md | 930 ++ modes/microscopy/babel.config.js | 6 +- modes/microscopy/package.json | 11 +- modes/microscopy/src/index.tsx | 18 +- modes/segmentation/.gitignore | 104 + modes/segmentation/.prettierrc | 11 + modes/segmentation/.webpack/webpack.prod.js | 62 + modes/segmentation/CHANGELOG.md | 763 ++ modes/segmentation/LICENSE | 9 + modes/segmentation/README.md | 7 + modes/segmentation/babel.config.js | 44 + modes/segmentation/package.json | 70 + modes/segmentation/src/id.js | 5 + modes/segmentation/src/index.tsx | 189 + modes/segmentation/src/initToolGroups.ts | 108 + .../src/toolbarButtons.ts} | 256 +- modes/tmtv/.webpack/webpack.dev.js | 1 - modes/tmtv/.webpack/webpack.prod.js | 9 +- modes/tmtv/CHANGELOG.md | 936 ++ modes/tmtv/package.json | 21 +- modes/tmtv/src/index.js | 35 +- modes/tmtv/src/initToolGroups.js | 176 +- modes/tmtv/src/toolbarButtons.js | 21 +- .../src/utils/setCrosshairsConfiguration.js | 4 +- modes/tmtv/src/utils/setFusionActiveVolume.js | 4 +- package.json | 42 +- platform/app/.all-contributorsrc | 46 +- platform/app/.gitignore | 1 + .../docker-compose-dcm4che.env | 0 .../docker-compose.yml | 0 .../etc/localtime | 0 .../etc/timezone | 0 .../nginx-proxy/conf/nginx.conf | 0 .../OpenResty-Orthanc-Keycloak/.dockerignore | 16 - .../config/nginx.conf | 4 +- .../config/ohif-keycloak-realm.json | 120 +- .../docker-compose.yml | 6 +- .../OpenResty-Orthanc-Keycloak/dockerfile | 48 +- .../ohif/login/resources/css/styles.css | 24 +- .../.recipes/OpenResty-Orthanc/.dockerignore | 16 - .../OpenResty-Orthanc/config/nginx.conf | 2 + .../OpenResty-Orthanc/docker-compose.yml | 2 +- .../app/.recipes/OpenResty-Orthanc/dockerfile | 23 +- .../app/.webpack/rules/extractStyleChunks.js | 16 +- platform/app/.webpack/webpack.pwa.js | 13 +- .../app/.webpack/writePluginImportsFile.js | 70 +- platform/app/CHANGELOG.md | 1053 ++ platform/app/babel.config.js | 2 +- platform/app/cypress.config.ts | 2 +- platform/app/cypress/fixtures/example.json | 2 +- .../cypress/integration/MultiStudy.spec.js | 26 + .../integration/OHIFPdfDisplay.spec.js | 12 +- .../integration/OHIFVideoDisplay.spec.js | 15 +- .../customization/HangingProtocol.spec.js | 21 +- .../customization/OHIFDoubleClick.spec.js | 34 +- .../OHIFContextMenuCustomization.spec.js | 29 +- .../OHIFCornerstoneHotkeys.spec.js | 10 +- .../OHIFCornerstoneToolbar.spec.js | 108 +- .../OHIFDownloadSnapshotFile.spec.js | 16 +- .../OHIFGeneralViewer.spec.js | 28 +- .../OHIFMeasurementPanel.spec.js | 43 +- .../OHIFStudyBrowser.spec.js | 28 +- .../study-list/OHIFStudyList.spec.js | 83 +- .../cypress/integration/volume/MPR.spec.js | 44 +- platform/app/cypress/plugins/index.js | 2 +- platform/app/cypress/support/DragSimulator.js | 14 +- platform/app/cypress/support/aliases.js | 26 +- platform/app/cypress/support/commands.js | 226 +- platform/app/jestBabelTransform.js | 4 +- platform/app/netlify.toml | 2 +- platform/app/package.json | 48 +- platform/app/pluginConfig.json | 3 + platform/app/postcss.config.js | 2 +- platform/app/public/_redirects | 2 +- .../assets/yandex-browser-manifest.json | 2 +- platform/app/public/config/aws.js | 1 - platform/app/public/config/default.js | 40 +- platform/app/public/config/default_16bit.js | 189 + .../app/public/config/dicomweb_relative.js | 1 - .../docker_openresty-orthanc-keycloak.js | 37 +- .../public/config/docker_openresty-orthanc.js | 13 +- platform/app/public/config/e2e.js | 91 +- platform/app/public/config/google.js | 4 +- platform/app/public/config/idc.js | 3 +- platform/app/public/config/local_orthanc.js | 3 +- platform/app/public/config/local_static.js | 109 +- platform/app/public/config/multiple.js | 3 - platform/app/public/config/netlify.js | 30 +- platform/app/public/es6-shim.min.js | 3571 ++++- platform/app/public/html-templates/index.html | 55 +- .../app/public/html-templates/rollbar.html | 297 +- platform/app/public/init-service-worker.js | 8 +- platform/app/public/oidc-client.min.js | 10896 +++++++++++++++- platform/app/public/polyfill.min.js | 185 +- platform/app/public/serve.json | 12 + platform/app/public/silent-refresh.html | 27 +- platform/app/src/App.tsx | 22 +- platform/app/src/__tests__/globalSetup.js | 4 +- platform/app/src/appInit.js | 17 +- platform/app/src/components/ViewportGrid.tsx | 171 +- platform/app/src/index.js | 5 +- platform/app/src/routes/DataSourceWrapper.tsx | 57 +- platform/app/src/routes/Debug.tsx | 35 +- platform/app/src/routes/Local/Local.tsx | 36 +- .../app/src/routes/Local/dicomFileLoader.js | 11 +- .../app/src/routes/Local/filesToStudies.js | 6 +- platform/app/src/routes/Mode/Compose.tsx | 4 +- platform/app/src/routes/Mode/Mode.tsx | 219 +- platform/app/src/routes/Mode/studiesList.ts | 10 +- platform/app/src/routes/NotFound/NotFound.tsx | 7 +- .../src/routes/SignoutCallbackComponent.tsx | 4 +- platform/app/src/routes/WorkList/WorkList.tsx | 92 +- .../app/src/routes/WorkList/filtersMeta.js | 16 +- platform/app/src/routes/index.tsx | 64 +- platform/app/src/service-worker.js | 6 +- .../app/src/utils/OpenIdConnectRoutes.tsx | 59 +- platform/app/src/utils/isSeriesFilterUsed.ts | 16 + platform/app/tailwind.config.js | 323 +- platform/cli/CHANGELOG.md | 941 ++ platform/cli/package.json | 6 +- platform/cli/src/commands/addExtension.js | 4 +- platform/cli/src/commands/addExtensions.js | 4 +- platform/cli/src/commands/addMode.js | 10 +- platform/cli/src/commands/createPackage.js | 34 +- platform/cli/src/commands/linkPackage.js | 14 +- platform/cli/src/commands/removeExtension.js | 7 +- platform/cli/src/commands/removeExtensions.js | 4 +- platform/cli/src/commands/unlinkPackage.js | 15 +- .../commands/utils/createDirectoryContents.js | 6 +- .../cli/src/commands/utils/editPackageJson.js | 6 +- ...OhifExtensionsToRemoveAfterRemovingMode.js | 32 +- platform/cli/src/commands/utils/index.js | 5 +- .../private/manipulatePluginConfigFile.js | 4 +- platform/cli/src/commands/utils/validate.js | 28 +- platform/cli/src/index.js | 26 +- platform/cli/templates/extension/.prettierrc | 7 +- .../cli/templates/extension/dependencies.json | 7 +- .../cli/templates/extension/src/index.tsx | 60 +- platform/cli/templates/mode/.prettierrc | 7 +- .../templates/mode/.webpack/webpack.prod.js | 6 + platform/cli/templates/mode/babel.config.js | 6 +- platform/cli/templates/mode/dependencies.json | 8 +- platform/cli/templates/mode/src/index.tsx | 11 +- platform/core/.all-contributorsrc | 42 +- platform/core/.webpack/webpack.dev.js | 2 - platform/core/.webpack/webpack.prod.js | 7 +- platform/core/CHANGELOG.md | 1047 ++ platform/core/README.md | 2 +- platform/core/babel.config.js | 2 +- platform/core/package.json | 12 +- .../core/src/DICOMWeb/getAttribute.test.js | 4 +- .../DICOMWeb/getAuthorizationHeader.test.js | 4 +- .../core/src/DICOMWeb/getModalities.test.js | 8 +- platform/core/src/DICOMWeb/getName.test.js | 7 +- platform/core/src/DICOMWeb/getNumber.test.js | 4 +- platform/core/src/DICOMWeb/getString.test.js | 4 +- platform/core/src/DICOMWeb/index.js | 9 +- .../core/src/__mocks__/dicomweb-client.js | 4 +- .../core/src/classes/CommandsManager.test.js | 25 +- platform/core/src/classes/CommandsManager.ts | 31 +- .../core/src/classes/HotkeysManager.test.js | 21 +- platform/core/src/classes/HotkeysManager.ts | 19 +- platform/core/src/classes/ImageSet.ts | 6 +- ...etadataProvider.js => MetadataProvider.ts} | 189 +- platform/core/src/enums/TimingEnum.ts | 24 + platform/core/src/enums/index.ts | 3 + .../src/extensions/ExtensionManager.test.js | 32 +- .../core/src/extensions/ExtensionManager.ts | 66 +- platform/core/src/index.test.js | 3 + platform/core/src/index.ts | 6 + platform/core/src/log.js | 21 +- .../src/services/CineService/CineService.ts | 5 + .../CustomizationService.test.js | 59 +- .../CustomizationService.ts | 33 +- .../DicomMetadataStore/DicomMetadataStore.ts | 59 +- .../createSeriesMetadata.js | 21 +- .../DicomMetadataStore/createStudyMetadata.js | 56 +- .../DisplaySetService/DisplaySetMessage.ts | 50 + .../DisplaySetService/DisplaySetService.ts | 140 +- .../services/DisplaySetService/IDisplaySet.ts | 1 + .../src/services/DisplaySetService/index.ts | 2 + .../HangingProtocolService/HPMatcher.js | 33 +- .../HangingProtocolService.test.js | 17 +- .../HangingProtocolService.ts | 420 +- .../HangingProtocolService/ProtocolEngine.js | 14 +- .../custom-attribute/isDisplaySetFromUrl.ts | 9 +- .../numberOfDisplaySetsWithImages.ts | 3 +- .../seriesDescriptionsFromDisplaySets.ts | 3 +- .../lib/displayConstraint.js | 3 +- .../HangingProtocolService/lib/validator.js | 63 +- .../lib/validator.test.js | 402 +- .../MeasurementService.test.js | 85 +- .../MeasurementService/MeasurementService.ts | 110 +- .../core/src/services/ServicesManager.test.js | 12 +- platform/core/src/services/ServicesManager.ts | 4 +- .../StateSyncService/StateSyncService.ts | 2 +- .../services/ToolBarService/ToolbarService.ts | 210 +- .../core/src/services/UIModalService/index.ts | 5 +- .../services/UINotificationService/index.ts | 5 +- .../services/UIViewportDialogService/index.js | 25 +- .../UserAuthenticationService.js | 3 +- .../ViewportGridService.ts | 53 +- .../ViewportGridService/getPresentationIds.ts | 22 +- .../src/services/ViewportGridService/index.ts | 2 + ...Interface.js => pubSubServiceInterface.ts} | 9 +- platform/core/src/services/index.ts | 4 +- platform/core/src/string.js | 5 +- platform/core/src/types/Command.ts | 4 +- .../src/types/DataSourceConfigurationAPI.ts | 68 + platform/core/src/types/DisplaySet.ts | 15 + platform/core/src/types/HangingProtocol.ts | 33 +- platform/core/src/types/IPubSub.ts | 5 +- platform/core/src/types/PanelModule.ts | 13 +- platform/core/src/types/index.ts | 37 +- platform/core/src/utils/Queue.js | 2 +- platform/core/src/utils/absoluteUrl.test.js | 4 +- platform/core/src/utils/addAccessors.js | 66 + .../core/src/utils/combineFrameInstance.ts | 32 +- platform/core/src/utils/debounce.js | 4 +- platform/core/src/utils/downloadCSVReport.js | 18 +- platform/core/src/utils/formatDate.js | 3 +- .../core/src/utils/generateAcceptHeader.ts | 6 +- platform/core/src/utils/getImageId.js | 6 +- .../core/src/utils/hierarchicalListUtils.js | 6 +- .../src/utils/hierarchicalListUtils.test.js | 24 +- .../core/src/utils/hotkeys/pausePlugin.js | 6 +- .../core/src/utils/hotkeys/recordPlugin.js | 12 +- platform/core/src/utils/index.js | 4 + platform/core/src/utils/index.test.js | 2 + platform/core/src/utils/isDicomUid.test.js | 6 +- .../src/utils/isDisplaySetReconstructable.js | 92 +- platform/core/src/utils/isImage.test.js | 108 +- .../core/src/utils/isLowPriorityModality.js | 8 +- .../core/src/utils/isLowPriorityModality.ts | 8 +- platform/core/src/utils/makeCancelable.js | 4 +- platform/core/src/utils/makeDeferred.js | 2 +- .../fetchPaletteColorLookupTableData.js | 28 +- .../getPixelSpacingInformation.js | 26 +- .../utils/metadataProvider/unpackOverlay.js | 3 +- platform/core/src/utils/objectPath.js | 6 +- .../core/src/utils/progressTrackingUtils.js | 34 +- .../src/utils/progressTrackingUtils.test.js | 6 +- platform/core/src/utils/resolveObjectPath.js | 4 +- .../core/src/utils/resolveObjectPath.test.js | 8 +- platform/core/src/utils/roundNumber.js | 35 +- platform/core/src/utils/sopClassDictionary.js | 39 +- platform/core/src/utils/sortBy.js | 2 +- .../core/src/utils/sortInstancesByPosition.ts | 12 +- platform/core/src/utils/sortStudy.ts | 24 +- platform/core/src/utils/splitComma.ts | 6 +- .../subscribeToNextViewportGridChange.ts | 11 +- platform/docs/CHANGELOG.md | 1007 ++ platform/docs/docs/README.md | 2 +- .../img/data-source-configuration-ui.png | Bin 0 -> 27337 bytes .../docs/assets/img/filtering-worklist.png | Bin 0 -> 57297 bytes .../assets/img/google-create-credentials.png | Bin 0 -> 28262 bytes .../docs/assets/img/google-enable-apis.png | Bin 0 -> 14380 bytes ...oogle-healthcare-service-agent-warning.png | Bin 0 -> 16584 bytes .../assets/img/google-manually-add-scopes.png | Bin 0 -> 29920 bytes .../assets/img/google-oauth-consent-steps.png | Bin 0 -> 4369 bytes .../assets/img/google-projects-drop-down.png | Bin 0 -> 5713 bytes .../img/google-provided-accounts-checkbox.png | Bin 0 -> 3987 bytes platform/docs/docs/assets/img/large-pt-ct.png | Bin 0 -> 703818 bytes .../assets/img/memory-profiling-regular.png | Bin 0 -> 149650 bytes .../assets/img/preferSizeOverAccuracy.png | Bin 0 -> 101118 bytes platform/docs/docs/assets/img/webgl-int16.png | Bin 0 -> 144574 bytes .../docs/assets/img/webgl-report-norm16.png | Bin 0 -> 464814 bytes .../docs/configuration/configurationFiles.md | 50 +- .../dataSources/configuration-ui.md | 177 + .../configuration/dataSources/dicom-json.md | 39 + .../configuration/dataSources/dicom-web.md | 11 +- .../configuration/dataSources/introduction.md | 2 +- .../configuration/dataSources/static-files.md | 6 +- platform/docs/docs/configuration/url.md | 21 +- .../docs/docs/deployment/authorization.md | 2 +- .../docs/deployment/build-for-production.md | 4 +- platform/docs/docs/deployment/cors.md | 7 +- .../docs/docs/deployment/custom-url-access.md | 74 + platform/docs/docs/deployment/docker.md | 4 +- .../deployment/google-cloud-healthcare.md | 139 + platform/docs/docs/deployment/iframe.md | 8 +- platform/docs/docs/deployment/index.md | 4 +- .../docs/deployment/nginx--image-archive.md | 4 +- .../docs/docs/deployment/static-assets.md | 18 +- .../docs/deployment/user-account-control.md | 292 + .../docs/docs/development/architecture.md | 5 +- ...tegration.md => continuous-integration.md} | 2 +- .../docs/docs/development/contributing.md | 2 +- platform/docs/docs/development/link.md | 2 +- platform/docs/docs/development/ohif-cli.md | 4 + platform/docs/docs/development/our-process.md | 4 +- platform/docs/docs/development/testing.md | 2 +- .../docs/docs/development/video-tutorials.md | 68 + platform/docs/docs/faq.md | 252 +- platform/docs/docs/migration-guide.md | 4 +- .../docs/docs/platform/extensions/index.md | 6 +- .../extensions/modules/data-source.md | 44 +- .../platform/extensions/modules/hpModule.md | 147 +- .../extensions/modules/layout-template.md | 2 +- .../platform/extensions/modules/viewport.md | 50 +- .../docs/platform/internationalization.md | 4 +- platform/docs/docs/platform/modes/index.md | 2 +- .../docs/docs/platform/modes/lifecycle.md | 9 + .../services/data/DisplaySetService.md | 11 + .../services/data/HangingProtocolService.md | 11 +- .../services/data/MeasurementService.md | 2 +- .../services/data/SegmentationService.md | 4 +- .../services/data/StateSyncService.md | 2 +- .../platform/services/data/ToolbarService.md | 2 + .../docs/docs/platform/services/data/index.md | 4 +- .../services/ui/customization-service.md | 62 +- .../services/ui/ui-viewport-dialog-service.md | 2 +- .../services/ui/viewport-grid-service.md | 10 +- platform/docs/docs/release-notes.md | 199 +- platform/docs/docs/resources.md | 13 +- platform/docs/docusaurus.config.js | 38 +- platform/docs/netlify.toml | 2 +- platform/docs/package.json | 23 +- platform/docs/pluginOHIFWebpackConfig.js | 2 +- platform/docs/src/css/custom.css | 8 +- platform/docs/src/pages/help.md | 2 +- platform/docs/src/utils/getMockedStudies.js | 3 +- platform/docs/tailwind.config.js | 383 +- .../essentials/meteor-packages.md | 2 +- .../version-1.0-deprecated/faq/technical.md | 2 +- .../version-2.0-deprecated/Architecture.md | 2 +- .../deployment/index.md | 4 +- .../recipes/build-for-production.md | 2 +- .../recipes/nginx--image-archive.md | 6 +- .../deployment/recipes/static-assets.md | 2 +- .../recipes/user-account-control.md | 2 +- ...tegration.md => continuous-integration.md} | 6 +- .../development/getting-started.md | 2 +- .../development/testing.md | 2 +- .../extensions/index.md | 2 +- .../extensions/modules/commands.md | 4 +- .../extensions/modules/toolbar.md | 2 +- .../extensions/modules/viewport.md | 4 +- .../faq/browser-support.md | 2 +- .../faq/scope-of-project.md | 6 +- .../version-2.0-deprecated/help.md | 2 +- .../version-2.0-deprecated/our-process.md | 2 +- .../viewer/internationalization.md | 4 +- platform/i18n/.webpack/webpack.dev.js | 1 - platform/i18n/.webpack/webpack.prod.js | 2 - platform/i18n/CHANGELOG.md | 947 ++ platform/i18n/babel.config.js | 2 +- platform/i18n/package.json | 4 +- platform/i18n/src/config.js | 14 +- platform/i18n/src/index.js | 2 +- .../src/locales/ar/UserPreferencesModal.json | 2 +- platform/i18n/src/locales/de/AboutModal.json | 14 + platform/i18n/src/locales/de/Buttons.json | 50 + platform/i18n/src/locales/de/CineDialog.json | 8 + platform/i18n/src/locales/de/Common.json | 16 + platform/i18n/src/locales/de/DatePicker.json | 5 + platform/i18n/src/locales/de/Header.json | 8 + .../i18n/src/locales/de/MeasurementTable.json | 9 + platform/i18n/src/locales/de/StudyList.json | 13 + .../src/locales/de/UserPreferencesModal.json | 11 + .../src/locales/de/ViewportDownloadForm.json | 14 + platform/i18n/src/locales/de/index.js | 25 + .../i18n/src/locales/en-US/AboutModal.json | 14 +- platform/i18n/src/locales/en-US/Buttons.json | 2 +- .../i18n/src/locales/en-US/CineDialog.json | 2 +- platform/i18n/src/locales/en-US/Common.json | 5 +- .../en-US/DataSourceConfiguration.json | 24 + .../i18n/src/locales/en-US/DatePicker.json | 8 +- .../i18n/src/locales/en-US/ErrorBoundary.json | 8 + .../src/locales/en-US/HotkeysValidators.json | 6 + .../src/locales/en-US/MeasurementTable.json | 7 +- platform/i18n/src/locales/en-US/Messages.json | 16 + platform/i18n/src/locales/en-US/Modes.json | 8 + .../src/locales/en-US/SegmentationTable.json | 18 + .../i18n/src/locales/en-US/StudyItem.json | 3 + .../i18n/src/locales/en-US/StudyList.json | 17 +- .../src/locales/en-US/ThumbnailTracked.json | 5 + .../src/locales/en-US/TooltipClipboard.json | 4 + .../en-US/TrackedCornerstoneViewport.json | 6 + .../locales/en-US/UserPreferencesModal.json | 6 +- .../locales/en-US/ViewportDownloadForm.json | 2 +- platform/i18n/src/locales/en-US/index.js | 20 + platform/i18n/src/locales/es/AboutModal.json | 8 +- platform/i18n/src/locales/es/Buttons.json | 4 +- platform/i18n/src/locales/es/CineDialog.json | 2 +- platform/i18n/src/locales/es/Common.json | 2 +- platform/i18n/src/locales/es/Header.json | 2 +- platform/i18n/src/locales/es/StudyList.json | 7 +- .../src/locales/es/UserPreferencesModal.json | 6 +- platform/i18n/src/locales/fr/Buttons.json | 4 +- platform/i18n/src/locales/fr/CineDialog.json | 2 +- platform/i18n/src/locales/fr/Common.json | 2 +- platform/i18n/src/locales/fr/Header.json | 2 +- .../src/locales/fr/UserPreferencesModal.json | 6 +- platform/i18n/src/locales/index.js | 4 + platform/i18n/src/locales/ja-JP/Buttons.json | 4 +- .../i18n/src/locales/ja-JP/CineDialog.json | 2 +- platform/i18n/src/locales/ja-JP/Common.json | 2 +- platform/i18n/src/locales/ja-JP/Header.json | 2 +- .../locales/ja-JP/UserPreferencesModal.json | 6 +- platform/i18n/src/locales/nl/Buttons.json | 2 +- platform/i18n/src/locales/nl/Common.json | 2 +- platform/i18n/src/locales/nl/Header.json | 2 +- .../i18n/src/locales/pt-BR/AboutModal.json | 10 +- platform/i18n/src/locales/pt-BR/Buttons.json | 4 +- .../i18n/src/locales/pt-BR/CineDialog.json | 2 +- platform/i18n/src/locales/pt-BR/Common.json | 2 +- .../i18n/src/locales/pt-BR/DatePicker.json | 2 +- platform/i18n/src/locales/pt-BR/Header.json | 2 +- platform/i18n/src/locales/pt-BR/Messages.json | 15 + .../locales/pt-BR/UserPreferencesModal.json | 6 +- platform/i18n/src/locales/pt-BR/index.js | 2 + .../i18n/src/locales/test-LNG/AboutModal.json | 12 +- .../i18n/src/locales/test-LNG/Buttons.json | 2 +- .../i18n/src/locales/test-LNG/CineDialog.json | 2 +- .../i18n/src/locales/test-LNG/Common.json | 5 +- .../i18n/src/locales/test-LNG/DatePicker.json | 8 +- .../src/locales/test-LNG/ErrorBoundary.json | 8 + .../locales/test-LNG/HotkeysValidators.json | 6 + .../locales/test-LNG/MeasurementTable.json | 4 +- .../i18n/src/locales/test-LNG/Messages.json | 16 + platform/i18n/src/locales/test-LNG/Modes.json | 7 +- .../locales/test-LNG/SegmentationTable.json | 18 + .../i18n/src/locales/test-LNG/StudyItem.json | 3 + .../i18n/src/locales/test-LNG/StudyList.json | 16 +- .../locales/test-LNG/ThumbnailTracked.json | 5 + .../locales/test-LNG/TooltipClipboard.json | 4 + .../test-LNG/TrackedCornerstoneViewport.json | 6 + .../test-LNG/UserPreferencesModal.json | 4 +- .../test-LNG/ViewportDownloadForm.json | 2 +- platform/i18n/src/locales/test-LNG/index.js | 40 +- .../i18n/src/locales/tr-TR/AboutModal.json | 14 + platform/i18n/src/locales/tr-TR/Buttons.json | 43 + .../i18n/src/locales/tr-TR/CineDialog.json | 8 + platform/i18n/src/locales/tr-TR/Common.json | 16 + .../i18n/src/locales/tr-TR/DatePicker.json | 5 + platform/i18n/src/locales/tr-TR/Header.json | 8 + .../src/locales/tr-TR/MeasurementTable.json | 9 + .../i18n/src/locales/tr-TR/StudyList.json | 10 + .../locales/tr-TR/UserPreferencesModal.json | 9 + .../locales/tr-TR/ViewportDownloadForm.json | 14 + platform/i18n/src/locales/tr-TR/index.js | 25 + platform/i18n/src/locales/vi/Buttons.json | 4 +- platform/i18n/src/locales/vi/CineDialog.json | 2 +- platform/i18n/src/locales/vi/Common.json | 2 +- platform/i18n/src/locales/vi/Header.json | 2 +- platform/i18n/src/locales/vi/StudyList.json | 4 +- .../src/locales/vi/UserPreferencesModal.json | 6 +- platform/i18n/src/locales/zh/AboutModal.json | 1 + platform/i18n/src/locales/zh/Buttons.json | 31 +- platform/i18n/src/locales/zh/CineDialog.json | 2 +- platform/i18n/src/locales/zh/Common.json | 2 +- platform/i18n/src/locales/zh/ContextMenu.json | 4 + platform/i18n/src/locales/zh/DatePicker.json | 1 + platform/i18n/src/locales/zh/Dialog.json | 6 + .../i18n/src/locales/zh/ErrorBoundary.json | 7 + platform/i18n/src/locales/zh/Header.json | 2 +- platform/i18n/src/locales/zh/Local.json | 4 + .../i18n/src/locales/zh/MeasurementTable.json | 11 +- platform/i18n/src/locales/zh/Modals.json | 19 + platform/i18n/src/locales/zh/Modes.json | 5 + .../i18n/src/locales/zh/Notification.json | 18 + platform/i18n/src/locales/zh/PatientInfo.json | 8 + platform/i18n/src/locales/zh/SidePanel.json | 6 + .../i18n/src/locales/zh/StudyBrowser.json | 6 + platform/i18n/src/locales/zh/StudyList.json | 25 +- .../src/locales/zh/UserPreferencesModal.json | 6 +- .../src/locales/zh/ViewportDownloadForm.json | 1 + platform/i18n/src/locales/zh/index.js | 30 +- platform/i18n/src/utils.js | 1 + platform/ui/.storybook/main.ts | 18 +- platform/ui/.storybook/manager-head.html | 4 +- platform/ui/.storybook/preview.tsx | 8 +- platform/ui/.webpack/webpack.prod.js | 3 +- platform/ui/17dd54813d5acc10bf8f.wasm | Bin 0 -> 5637936 bytes platform/ui/CHANGELOG.md | 1040 +- platform/ui/jest.config.js | 13 + platform/ui/package.json | 28 +- platform/ui/src/assets/icons/chevron-left.svg | 4 +- platform/ui/src/assets/icons/icon-add.svg | 15 + platform/ui/src/assets/icons/icon-delete.svg | 17 + .../assets/icons/icon-disclosure-close.svg | 12 + .../src/assets/icons/icon-disclosure-open.svg | 12 + .../assets/icons/icon-display-settings.svg | 21 + .../ui/src/assets/icons/icon-more-menu.svg | 17 + platform/ui/src/assets/icons/icon-rename.svg | 15 + .../icons/icon-toggle-all-visibility.svg | 22 + .../ui/src/assets/icons/icon-tool-brush.svg | 13 + .../ui/src/assets/icons/icon-tool-eraser.svg | 14 + .../ui/src/assets/icons/icon-tool-scissor.svg | 17 + .../ui/src/assets/icons/icon-tool-shape.svg | 13 + .../src/assets/icons/icon-tool-threshold.svg | 17 + platform/ui/src/assets/icons/row-hidden.svg | 6 +- platform/ui/src/assets/icons/row-hide-all.svg | 11 - .../icons/{row-locked.svg => row-lock.svg} | 4 +- platform/ui/src/assets/icons/row-show-all.svg | 9 - .../icons/{row-hide.svg => row-shown.svg} | 4 +- platform/ui/src/assets/icons/row-unhide.svg | 11 - .../{row-unlocked.svg => row-unlock.svg} | 4 +- .../assets/icons/side-panel-close-left.svg | 14 + .../assets/icons/side-panel-close-right.svg | 14 + .../src/assets/icons/status-alert-warning.svg | 9 + .../ui/src/assets/icons/status-untracked.svg | 2 +- platform/ui/src/assets/icons/tab-studies.svg | 13 + .../src/assets/icons/toggle-dicom-overlay.svg | 8 + .../ui/src/assets/icons/ui-arrow-down.svg | 2 +- .../ui/src/assets/icons/ui-arrow-left.svg | 3 + .../ui/src/assets/icons/ui-arrow-right.svg | 3 + .../src/components/AboutModal/AboutModal.tsx | 80 +- .../__stories__/aboutModal.stories.mdx | 8 +- .../AdvancedToolbox/AdvancedToolbox.tsx | 64 + .../AdvancedToolbox/ToolSettings.tsx | 84 + .../__stories__/advancedToolbox.stories.mdx | 144 + .../src/components/AdvancedToolbox/index.js | 2 + platform/ui/src/components/Button/Button.tsx | 9 +- .../ui/src/components/Button/ButtonEnums.ts | 7 +- .../Button/__stories__/button.stories.mdx | 45 +- .../components/ButtonGroup/ButtonGroup.tsx | 177 +- .../__stories__/buttonGroup.stories.mdx | 97 +- .../ui/src/components/CheckBox/CheckBox.tsx | 19 +- .../CheckBox/__stories__/checkBox.stories.mdx | 16 +- .../src/components/CinePlayer/CinePlayer.tsx | 16 +- .../components/ContextMenu/ContextMenu.tsx | 11 +- .../__stories__/contextMenu.stories.mdx | 10 +- .../ui/src/components/DateRange/DateRange.css | 24 +- .../ui/src/components/DateRange/DateRange.tsx | 65 +- .../__stories__/dateRange.stories.mdx | 10 +- platform/ui/src/components/Dialog/Dialog.tsx | 18 +- platform/ui/src/components/Dialog/Footer.tsx | 7 +- platform/ui/src/components/Dialog/Header.tsx | 8 +- .../Dialog/__stories__/dialog.stories.mdx | 10 +- .../DisplaySetMessageListTooltip.tsx | 72 + .../DisplaySetMessageListTooltip/index.js | 2 + .../ui/src/components/Dropdown/Dropdown.tsx | 137 +- .../Dropdown/__stories__/dropdown.stories.mdx | 15 +- .../components/EmptyStudies/EmptyStudies.tsx | 16 +- .../ErrorBoundary/ErrorBoundary.tsx | 50 +- .../ExpandableToolbarButton.css | 4 +- .../ExpandableToolbarButton.tsx | 17 +- platform/ui/src/components/Header/Header.tsx | 25 +- .../Header/__stories__/Header.stories.mdx | 10 +- .../components/HotkeyField/HotkeyField.tsx | 9 +- .../HotkeysPreferences/HotkeysPreferences.tsx | 28 +- .../HotkeysPreferences/hotkeysValidators.js | 27 +- .../components/HotkeysPreferences/utils.js | 4 +- .../Icon/__stories__/icon.stories.mdx | 19 +- platform/ui/src/components/Icon/getIcon.js | 58 +- .../src/components/IconButton/IconButton.tsx | 15 +- .../ImageScrollbar/ImageScrollbar.tsx | 5 +- platform/ui/src/components/Input/Input.tsx | 7 +- .../Input/__stories__/input.stories.mdx | 10 +- .../InputDateRange/InputDateRange.tsx | 3 +- .../InputDoubleRange/InputDoubleRange.css | 4 + .../InputDoubleRange/InputDoubleRange.tsx | 218 + .../__stories__/inputDoubleRange.stories.mdx | 56 + .../src/components/InputDoubleRange/index.js | 3 + .../InputFilterText/InputFilterText.tsx | 84 + .../_stories_/inputFilterText.stories.mdx | 59 + .../src/components/InputFilterText/index.js | 2 + .../src/components/InputGroup/InputGroup.tsx | 23 +- .../InputLabelWrapper/InputLabelWrapper.tsx | 18 +- .../InputMultiSelect/InputMultiSelect.tsx | 3 +- .../components/InputNumber/InputNumber.css | 12 + .../components/InputNumber/InputNumber.tsx | 157 +- .../__stories__/inputNumber.stories.mdx | 19 +- .../src/components/InputRange/InputRange.css | 23 +- .../src/components/InputRange/InputRange.tsx | 122 +- .../__stories__/inputRange.stories.mdx | 18 +- .../ui/src/components/InputText/InputText.tsx | 10 +- platform/ui/src/components/Label/Label.tsx | 5 +- .../LayoutSelector/LayoutSelector.tsx | 3 +- .../__stories__/layoutSelector.stories.mdx | 5 +- .../components/LegacyButton/LegacyButton.tsx | 21 +- .../__stories__/legacyButton.stories.mdx | 41 +- .../LegacyButtonGroup/LegacyButtonGroup.tsx | 150 + .../__stories__/legacyButtonGroup.stories.mdx | 102 + .../src/components/LegacyButtonGroup/index.js | 2 + .../LegacyCinePlayer/LegacyCinePlayer.tsx | 18 +- .../__stories__/legacyCinePlayer.stories.mdx | 5 +- .../LegacyPatientInfo/LegacyPatientInfo.tsx | 47 +- .../LegacySidePanel/LegacySidePanel.tsx | 376 + .../__stories__/legacySidePanel.stories.mdx | 74 + .../src/components/LegacySidePanel/index.js | 2 + .../{SidePanel => LegacySidePanel}/style.css | 0 .../LegacyViewportActionBar.stories.tsx | 2 +- .../LegacyViewportActionBar.tsx | 74 +- .../ui/src/components/ListMenu/ListMenu.tsx | 2 +- .../LoadingIndicatorProgress.tsx | 7 +- .../LoadingIndicatorTotalPercent.tsx | 10 +- .../MeasurementTable/MeasurementItem.tsx | 65 +- .../MeasurementTable/MeasurementTable.tsx | 50 +- .../__stories__/measurementTable.stories.mdx | 11 +- platform/ui/src/components/Modal/Modal.tsx | 13 +- platform/ui/src/components/NavBar/NavBar.tsx | 2 +- .../components/Notification/Notification.tsx | 35 +- .../__stories__/notification.stories.mdx | 5 +- .../components/PanelSection/PanelSection.tsx | 61 + .../__stories__/PanelSection.stories.mdx | 71 + .../ui/src/components/PanelSection/index.js | 2 + .../components/PatientInfo/PatientInfo.tsx | 44 +- .../ProgressLoadingBar/ProgressLoadingBar.tsx | 4 +- .../SegmentationGroupTable/AddSegmentRow.tsx | 25 + .../NoSegmentationRow.tsx | 22 + .../SegmentationConfig.tsx | 110 +- .../SegmentationDropDownRow.tsx | 173 + .../SegmentationGroup.tsx | 272 - .../SegmentationGroupSegment.tsx | 271 +- .../SegmentationGroupTable.tsx | 291 +- .../segmentationGroupTable.stories.mdx | 165 +- .../SegmentationTable/SegmentationItem.tsx | 42 +- .../SegmentationTable/SegmentationTable.tsx | 14 +- .../__stories__/segmentationTable.stories.mdx | 7 +- platform/ui/src/components/Select/Select.css | 47 +- platform/ui/src/components/Select/Select.tsx | 24 +- .../Select/__stories__/select.stories.mdx | 56 + .../ui/src/components/SidePanel/SidePanel.tsx | 511 +- .../__stories__/sidePanel.stories.mdx | 13 +- .../ui/src/components/Snackbar/Snackbar.css | 8 +- .../components/Snackbar/SnackbarContainer.tsx | 8 +- .../src/components/Snackbar/SnackbarItem.tsx | 30 +- .../components/SplitButton/SplitButton.tsx | 254 +- .../__stories__/splitButton.stories.mdx | 7 +- .../components/StudyBrowser/StudyBrowser.tsx | 34 +- .../__stories__/studyBrowser.stories.mdx | 14 +- .../ui/src/components/StudyItem/StudyItem.tsx | 32 +- .../__stories__/studyItem.stories.mdx | 10 +- .../StudyListExpandedRow.tsx | 20 +- .../studyListExpandedRow.stories.mdx | 3 +- .../StudyListFilter/StudyListFilter.tsx | 50 +- .../StudyListPagination.tsx | 29 +- .../StudyListTable/StudyListTable.tsx | 14 +- .../StudyListTable/StudyListTableRow.tsx | 30 +- .../components/StudySummary/StudySummary.tsx | 4 +- platform/ui/src/components/Table/Table.tsx | 6 +- .../ui/src/components/TableBody/TableBody.tsx | 11 +- .../ui/src/components/TableCell/TableCell.tsx | 4 +- .../ui/src/components/TableHead/TableHead.tsx | 8 +- .../ui/src/components/TableRow/TableRow.tsx | 11 +- .../components/ThemeWrapper/ThemeWrapper.tsx | 4 +- .../ui/src/components/Thumbnail/Thumbnail.tsx | 32 +- .../ThumbnailList/ThumbnailList.tsx | 26 +- .../ThumbnailNoImage/ThumbnailNoImage.tsx | 44 +- .../ThumbnailTracked/ThumbnailTracked.tsx | 58 +- .../ToolbarButton/ToolbarButton.tsx | 18 +- .../__stories__/toolbarButton.stories.mdx | 7 +- .../src/components/Tooltip/PortalTooltip.tsx | 103 + .../components/Tooltip/PortalTooltipCard.tsx | 385 + .../ui/src/components/Tooltip/Tooltip.tsx | 28 +- .../__stories__/toolbarButton.stories.mdx | 32 +- .../ui/src/components/Tooltip/tooltip.css | 20 +- .../TooltipClipboard/TooltipClipboard.tsx | 16 +- .../src/components/Typography/Typography.tsx | 3 +- .../__stories__/typography.stories.mdx | 150 +- .../UserPreferences/UserPreferences.tsx | 18 +- .../ui/src/components/Viewport/Viewport.tsx | 73 +- .../Viewport/__stories__/Viewport.stories.mdx | 12 +- platform/ui/src/components/Viewport/index.js | 2 - platform/ui/src/components/Viewport/index.ts | 5 + .../ViewportActionBar/ViewportActionBar.tsx | 47 +- .../ViewportDownloadForm.tsx | 60 +- .../ViewportOverlay/ViewportOverlay.tsx | 8 +- .../components/ViewportPane/ViewportPane.tsx | 8 +- .../WindowLevelMenuItem.tsx | 14 +- platform/ui/src/components/index.js | 14 + .../ui/src/contextProviders/CineProvider.tsx | 21 +- .../src/contextProviders/DialogProvider.tsx | 62 +- .../contextProviders/DragAndDropProvider.tsx | 8 +- .../contextProviders/ImageViewerProvider.tsx | 13 +- .../ui/src/contextProviders/ModalProvider.tsx | 37 +- .../src/contextProviders/SnackbarProvider.tsx | 26 +- .../UserAuthenticationProvider.tsx | 24 +- .../ViewportDialogProvider.tsx | 20 +- .../contextProviders/ViewportGridProvider.tsx | 384 +- platform/ui/src/contextProviders/index.js | 36 +- platform/ui/src/hooks/index.ts | 3 +- platform/ui/src/hooks/useResizeObserver.ts | 5 +- .../ui/src/hooks/useSessionStorage.test.js | 102 + platform/ui/src/hooks/useSessionStorage.tsx | 71 + platform/ui/src/index.js | 10 + .../ui/src/pages/Colors/BackgroundColor.tsx | 2 +- platform/ui/src/storybook/color/color.tsx | 7 +- platform/ui/src/storybook/colors.stories.mdx | 79 +- .../storybook/components/footer/footer.tsx | 6 +- .../storybook/components/heading/heading.tsx | 6 +- .../ui/src/storybook/components/index.tsx | 9 +- .../link-component/link-component.tsx | 7 +- .../components/section-name/section-name.tsx | 15 +- .../ui/src/storybook/contribute.stories.mdx | 64 +- .../functions/create-component-story.tsx | 6 +- platform/ui/src/storybook/header/header.tsx | 5 +- platform/ui/src/storybook/welcome.stories.mdx | 47 +- platform/ui/src/tailwind.css | 53 +- platform/ui/src/types/ThumbnailType.ts | 6 +- platform/ui/src/types/index.ts | 1 - platform/ui/src/utils/getMaxDigits.ts | 19 + .../src/views/StudyList/components/Header.js | 11 +- platform/ui/src/views/Viewer/Viewer.js | 18 +- .../ui/src/views/Viewer/components/Header.js | 15 +- platform/ui/tailwind.config.js | 314 +- postcss.config.js | 2 +- publish-package.mjs | 89 +- publish-version.mjs | 41 +- testdata | 2 +- tsconfig.json | 6 +- version.json | 4 +- version.mjs | 61 +- version.txt | 2 +- yarn.lock | 10148 +++++++------- 1072 files changed, 69094 insertions(+), 30806 deletions(-) create mode 100644 .codespellrc delete mode 100644 .docker/Nginx-Orthanc/config/nginx.conf delete mode 100644 .docker/Nginx-Orthanc/config/orthanc.json delete mode 100644 .docker/Nginx-Orthanc/docker-compose.yml delete mode 100644 .docker/Nginx-Orthanc/volumes/orthanc-db/.gitignore create mode 100644 .github/workflows/codespell.yml create mode 100644 .scripts/dicom-json-generator.js create mode 100644 .webpack/rules/stylusToJavaScript.js create mode 100644 CHANGELOG.md delete mode 100644 extensions/_example/src/index.js create mode 100644 extensions/cornerstone-dicom-rt/CHANGELOG.md delete mode 100644 extensions/cornerstone-dicom-rt/src/utils/_hydrateRT.ts create mode 100644 extensions/cornerstone-dicom-seg/CHANGELOG.md create mode 100644 extensions/cornerstone-dicom-seg/src/commandsModule.ts create mode 100644 extensions/cornerstone-dicom-seg/src/getPanelModule.tsx create mode 100644 extensions/cornerstone-dicom-seg/src/init.ts create mode 100644 extensions/cornerstone-dicom-seg/src/panels/SegmentationToolbox.tsx create mode 100644 extensions/cornerstone-dicom-seg/src/panels/colorPickerDialog.css create mode 100644 extensions/cornerstone-dicom-seg/src/panels/colorPickerDialog.tsx delete mode 100644 extensions/cornerstone-dicom-seg/src/utils/_hydrateSEG.ts create mode 100644 extensions/cornerstone-dicom-seg/src/utils/hydrationUtils.ts create mode 100644 extensions/cornerstone-dicom-sr/CHANGELOG.md create mode 100644 extensions/cornerstone/src/hps/fourUp.ts create mode 100644 extensions/cornerstone/src/hps/main3D.ts create mode 100644 extensions/cornerstone/src/hps/mpr.ts create mode 100644 extensions/cornerstone/src/hps/mprAnd3DVolumeViewport.ts create mode 100644 extensions/cornerstone/src/hps/only3D.ts create mode 100644 extensions/cornerstone/src/hps/primary3D.ts create mode 100644 extensions/cornerstone/src/hps/primaryAxial.ts create mode 100644 extensions/cornerstone/src/tools/ImageOverlayViewerTool.tsx create mode 100644 extensions/cornerstone/src/tools/OverlayPlaneModuleProvider.ts rename extensions/cornerstone/src/utils/{stackSync => imageSliceSync}/calculateViewportRegistrations.ts (90%) create mode 100644 extensions/cornerstone/src/utils/imageSliceSync/toggleImageSliceSync.ts create mode 100644 extensions/cornerstone/src/utils/initViewTiming.ts create mode 100644 extensions/cornerstone/src/utils/measurementServiceMappings/utils/getDisplayUnit.js delete mode 100644 extensions/cornerstone/src/utils/stackSync/toggleStackImageSync.ts create mode 100644 extensions/default/CHANGELOG.md create mode 100644 extensions/default/babel.config.js create mode 100644 extensions/default/jest.config.js create mode 100644 extensions/default/src/Components/DataSourceConfigurationComponent.tsx create mode 100644 extensions/default/src/Components/DataSourceConfigurationModalComponent.tsx create mode 100644 extensions/default/src/Components/ItemListComponent.tsx create mode 100644 extensions/default/src/Components/SidePanelWithServices.tsx create mode 100644 extensions/default/src/DataSourceConfigurationAPI/GoogleCloudDataSourceConfigurationAPI.ts create mode 100644 extensions/default/src/DicomWebDataSource/utils/retrieveMetadataFiltered.js create mode 100644 extensions/default/src/MergeDataSource/index.test.js create mode 100644 extensions/default/src/MergeDataSource/index.ts create mode 100644 extensions/default/src/MergeDataSource/types.ts create mode 100644 extensions/default/src/Toolbar/ToolbarButtonWithServices.tsx delete mode 100644 extensions/default/src/Toolbar/ToolbarSplitButton.tsx create mode 100644 extensions/default/src/Toolbar/ToolbarSplitButtonWithServices.tsx create mode 100644 extensions/default/src/ViewerLayout/ViewerHeader.tsx create mode 100644 extensions/default/src/getDisplaySetMessages.ts create mode 100644 extensions/default/src/getDisplaySetsFromUnsupportedSeries.js create mode 100644 extensions/default/src/hpCompare.ts create mode 100644 extensions/default/src/utils/calculateScanAxisNormal.ts create mode 100644 extensions/default/src/utils/validations/areAllImageComponentsEqual.ts create mode 100644 extensions/default/src/utils/validations/areAllImageDimensionsEqual.ts create mode 100644 extensions/default/src/utils/validations/areAllImageOrientationsEqual.ts create mode 100644 extensions/default/src/utils/validations/areAllImagePositionsEqual.ts create mode 100644 extensions/default/src/utils/validations/areAllImageSpacingEqual.ts create mode 100644 extensions/default/src/utils/validations/checkMultiframe.ts create mode 100644 extensions/default/src/utils/validations/checkSingleFrames.ts create mode 100644 extensions/dicom-microscopy/CHANGELOG.md create mode 100644 extensions/dicom-pdf/CHANGELOG.md create mode 100644 extensions/dicom-video/CHANGELOG.md create mode 100644 extensions/measurement-tracking/CHANGELOG.md delete mode 100644 extensions/measurement-tracking/src/_shared/createReportAsync.tsx delete mode 100644 extensions/measurement-tracking/src/_shared/createReportDialogPrompt.tsx create mode 100644 extensions/test-extension/CHANGELOG.md create mode 100644 extensions/test-extension/src/hpTestSwitch.ts create mode 100644 extensions/tmtv/CHANGELOG.md delete mode 100644 extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/RTSSReport.js delete mode 100644 extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/measurements/AnnotationToPointData.js delete mode 100644 extensions/tmtv/src/utils/dicomRTAnnotationExport/RTStructureSet/measurements/RectangleROIStartEndThreshold.js create mode 100644 modes/basic-dev-mode/CHANGELOG.md create mode 100644 modes/basic-test-mode/CHANGELOG.md rename modes/basic-test-mode/src/{index.js => index.ts} (75%) rename modes/basic-test-mode/src/{initToolGroups.js => initToolGroups.ts} (69%) create mode 100644 modes/basic-test-mode/src/moreTools.ts create mode 100644 modes/basic-test-mode/src/moreToolsMpr.ts rename modes/basic-test-mode/src/{toolbarButtons.js => toolbarButtons.ts} (61%) create mode 100644 modes/longitudinal/CHANGELOG.md create mode 100644 modes/longitudinal/src/moreTools.ts create mode 100644 modes/longitudinal/src/moreToolsMpr.ts create mode 100644 modes/longitudinal/src/toolbarButtons.ts create mode 100644 modes/microscopy/CHANGELOG.md create mode 100644 modes/segmentation/.gitignore create mode 100644 modes/segmentation/.prettierrc create mode 100644 modes/segmentation/.webpack/webpack.prod.js create mode 100644 modes/segmentation/CHANGELOG.md create mode 100644 modes/segmentation/LICENSE create mode 100644 modes/segmentation/README.md create mode 100644 modes/segmentation/babel.config.js create mode 100644 modes/segmentation/package.json create mode 100644 modes/segmentation/src/id.js create mode 100644 modes/segmentation/src/index.tsx create mode 100644 modes/segmentation/src/initToolGroups.ts rename modes/{longitudinal/src/toolbarButtons.js => segmentation/src/toolbarButtons.ts} (53%) create mode 100644 modes/tmtv/CHANGELOG.md create mode 100644 platform/app/.gitignore rename platform/app/.recipes/{Nginx-Dcm4che => Nginx-Dcm4chee}/docker-compose-dcm4che.env (100%) rename platform/app/.recipes/{Nginx-Dcm4che => Nginx-Dcm4chee}/docker-compose.yml (100%) rename platform/app/.recipes/{Nginx-Dcm4che => Nginx-Dcm4chee}/etc/localtime (100%) rename platform/app/.recipes/{Nginx-Dcm4che => Nginx-Dcm4chee}/etc/timezone (100%) rename platform/app/.recipes/{Nginx-Dcm4che => Nginx-Dcm4chee}/nginx-proxy/conf/nginx.conf (100%) delete mode 100644 platform/app/.recipes/OpenResty-Orthanc-Keycloak/.dockerignore delete mode 100644 platform/app/.recipes/OpenResty-Orthanc/.dockerignore create mode 100644 platform/app/cypress/integration/MultiStudy.spec.js create mode 100644 platform/app/public/config/default_16bit.js create mode 100644 platform/app/public/serve.json create mode 100644 platform/app/src/utils/isSeriesFilterUsed.ts create mode 100644 platform/cli/CHANGELOG.md rename platform/core/src/classes/{MetadataProvider.js => MetadataProvider.ts} (77%) create mode 100644 platform/core/src/enums/TimingEnum.ts create mode 100644 platform/core/src/enums/index.ts create mode 100644 platform/core/src/services/DisplaySetService/DisplaySetMessage.ts rename platform/core/src/services/_shared/{pubSubServiceInterface.js => pubSubServiceInterface.ts} (90%) create mode 100644 platform/core/src/types/DataSourceConfigurationAPI.ts create mode 100644 platform/core/src/types/DisplaySet.ts create mode 100644 platform/core/src/utils/addAccessors.js create mode 100644 platform/docs/CHANGELOG.md create mode 100644 platform/docs/docs/assets/img/data-source-configuration-ui.png create mode 100644 platform/docs/docs/assets/img/filtering-worklist.png create mode 100644 platform/docs/docs/assets/img/google-create-credentials.png create mode 100644 platform/docs/docs/assets/img/google-enable-apis.png create mode 100644 platform/docs/docs/assets/img/google-healthcare-service-agent-warning.png create mode 100644 platform/docs/docs/assets/img/google-manually-add-scopes.png create mode 100644 platform/docs/docs/assets/img/google-oauth-consent-steps.png create mode 100644 platform/docs/docs/assets/img/google-projects-drop-down.png create mode 100644 platform/docs/docs/assets/img/google-provided-accounts-checkbox.png create mode 100644 platform/docs/docs/assets/img/large-pt-ct.png create mode 100644 platform/docs/docs/assets/img/memory-profiling-regular.png create mode 100644 platform/docs/docs/assets/img/preferSizeOverAccuracy.png create mode 100644 platform/docs/docs/assets/img/webgl-int16.png create mode 100644 platform/docs/docs/assets/img/webgl-report-norm16.png create mode 100644 platform/docs/docs/configuration/dataSources/configuration-ui.md create mode 100644 platform/docs/docs/deployment/custom-url-access.md create mode 100644 platform/docs/docs/deployment/google-cloud-healthcare.md create mode 100644 platform/docs/docs/deployment/user-account-control.md rename platform/docs/docs/development/{continous-integration.md => continuous-integration.md} (99%) create mode 100644 platform/docs/docs/development/video-tutorials.md rename platform/docs/versioned_docs/version-2.0-deprecated/development/{continous-integration.md => continuous-integration.md} (97%) create mode 100644 platform/i18n/src/locales/de/AboutModal.json create mode 100644 platform/i18n/src/locales/de/Buttons.json create mode 100644 platform/i18n/src/locales/de/CineDialog.json create mode 100644 platform/i18n/src/locales/de/Common.json create mode 100644 platform/i18n/src/locales/de/DatePicker.json create mode 100644 platform/i18n/src/locales/de/Header.json create mode 100644 platform/i18n/src/locales/de/MeasurementTable.json create mode 100644 platform/i18n/src/locales/de/StudyList.json create mode 100644 platform/i18n/src/locales/de/UserPreferencesModal.json create mode 100644 platform/i18n/src/locales/de/ViewportDownloadForm.json create mode 100644 platform/i18n/src/locales/de/index.js create mode 100644 platform/i18n/src/locales/en-US/DataSourceConfiguration.json create mode 100644 platform/i18n/src/locales/en-US/ErrorBoundary.json create mode 100644 platform/i18n/src/locales/en-US/HotkeysValidators.json create mode 100644 platform/i18n/src/locales/en-US/Messages.json create mode 100644 platform/i18n/src/locales/en-US/Modes.json create mode 100644 platform/i18n/src/locales/en-US/SegmentationTable.json create mode 100644 platform/i18n/src/locales/en-US/StudyItem.json create mode 100644 platform/i18n/src/locales/en-US/ThumbnailTracked.json create mode 100644 platform/i18n/src/locales/en-US/TooltipClipboard.json create mode 100644 platform/i18n/src/locales/en-US/TrackedCornerstoneViewport.json create mode 100644 platform/i18n/src/locales/pt-BR/Messages.json create mode 100644 platform/i18n/src/locales/test-LNG/ErrorBoundary.json create mode 100644 platform/i18n/src/locales/test-LNG/HotkeysValidators.json create mode 100644 platform/i18n/src/locales/test-LNG/Messages.json create mode 100644 platform/i18n/src/locales/test-LNG/SegmentationTable.json create mode 100644 platform/i18n/src/locales/test-LNG/StudyItem.json create mode 100644 platform/i18n/src/locales/test-LNG/ThumbnailTracked.json create mode 100644 platform/i18n/src/locales/test-LNG/TooltipClipboard.json create mode 100644 platform/i18n/src/locales/test-LNG/TrackedCornerstoneViewport.json create mode 100644 platform/i18n/src/locales/tr-TR/AboutModal.json create mode 100644 platform/i18n/src/locales/tr-TR/Buttons.json create mode 100644 platform/i18n/src/locales/tr-TR/CineDialog.json create mode 100644 platform/i18n/src/locales/tr-TR/Common.json create mode 100644 platform/i18n/src/locales/tr-TR/DatePicker.json create mode 100644 platform/i18n/src/locales/tr-TR/Header.json create mode 100644 platform/i18n/src/locales/tr-TR/MeasurementTable.json create mode 100644 platform/i18n/src/locales/tr-TR/StudyList.json create mode 100644 platform/i18n/src/locales/tr-TR/UserPreferencesModal.json create mode 100644 platform/i18n/src/locales/tr-TR/ViewportDownloadForm.json create mode 100644 platform/i18n/src/locales/tr-TR/index.js create mode 100644 platform/i18n/src/locales/zh/AboutModal.json create mode 100644 platform/i18n/src/locales/zh/ContextMenu.json create mode 100644 platform/i18n/src/locales/zh/DatePicker.json create mode 100644 platform/i18n/src/locales/zh/Dialog.json create mode 100644 platform/i18n/src/locales/zh/ErrorBoundary.json create mode 100644 platform/i18n/src/locales/zh/Local.json create mode 100644 platform/i18n/src/locales/zh/Modals.json create mode 100644 platform/i18n/src/locales/zh/Modes.json create mode 100644 platform/i18n/src/locales/zh/Notification.json create mode 100644 platform/i18n/src/locales/zh/PatientInfo.json create mode 100644 platform/i18n/src/locales/zh/SidePanel.json create mode 100644 platform/i18n/src/locales/zh/StudyBrowser.json create mode 100644 platform/i18n/src/locales/zh/ViewportDownloadForm.json create mode 100644 platform/ui/17dd54813d5acc10bf8f.wasm create mode 100644 platform/ui/jest.config.js create mode 100644 platform/ui/src/assets/icons/icon-add.svg create mode 100644 platform/ui/src/assets/icons/icon-delete.svg create mode 100644 platform/ui/src/assets/icons/icon-disclosure-close.svg create mode 100644 platform/ui/src/assets/icons/icon-disclosure-open.svg create mode 100644 platform/ui/src/assets/icons/icon-display-settings.svg create mode 100644 platform/ui/src/assets/icons/icon-more-menu.svg create mode 100644 platform/ui/src/assets/icons/icon-rename.svg create mode 100644 platform/ui/src/assets/icons/icon-toggle-all-visibility.svg create mode 100644 platform/ui/src/assets/icons/icon-tool-brush.svg create mode 100644 platform/ui/src/assets/icons/icon-tool-eraser.svg create mode 100644 platform/ui/src/assets/icons/icon-tool-scissor.svg create mode 100644 platform/ui/src/assets/icons/icon-tool-shape.svg create mode 100644 platform/ui/src/assets/icons/icon-tool-threshold.svg delete mode 100644 platform/ui/src/assets/icons/row-hide-all.svg rename platform/ui/src/assets/icons/{row-locked.svg => row-lock.svg} (73%) delete mode 100644 platform/ui/src/assets/icons/row-show-all.svg rename platform/ui/src/assets/icons/{row-hide.svg => row-shown.svg} (73%) delete mode 100644 platform/ui/src/assets/icons/row-unhide.svg rename platform/ui/src/assets/icons/{row-unlocked.svg => row-unlock.svg} (73%) create mode 100644 platform/ui/src/assets/icons/side-panel-close-left.svg create mode 100644 platform/ui/src/assets/icons/side-panel-close-right.svg create mode 100644 platform/ui/src/assets/icons/status-alert-warning.svg create mode 100644 platform/ui/src/assets/icons/tab-studies.svg create mode 100644 platform/ui/src/assets/icons/toggle-dicom-overlay.svg create mode 100644 platform/ui/src/assets/icons/ui-arrow-left.svg create mode 100644 platform/ui/src/assets/icons/ui-arrow-right.svg create mode 100644 platform/ui/src/components/AdvancedToolbox/AdvancedToolbox.tsx create mode 100644 platform/ui/src/components/AdvancedToolbox/ToolSettings.tsx create mode 100644 platform/ui/src/components/AdvancedToolbox/__stories__/advancedToolbox.stories.mdx create mode 100644 platform/ui/src/components/AdvancedToolbox/index.js create mode 100644 platform/ui/src/components/DisplaySetMessageListTooltip/DisplaySetMessageListTooltip.tsx create mode 100644 platform/ui/src/components/DisplaySetMessageListTooltip/index.js create mode 100644 platform/ui/src/components/InputDoubleRange/InputDoubleRange.css create mode 100644 platform/ui/src/components/InputDoubleRange/InputDoubleRange.tsx create mode 100644 platform/ui/src/components/InputDoubleRange/__stories__/inputDoubleRange.stories.mdx create mode 100644 platform/ui/src/components/InputDoubleRange/index.js create mode 100644 platform/ui/src/components/InputFilterText/InputFilterText.tsx create mode 100644 platform/ui/src/components/InputFilterText/_stories_/inputFilterText.stories.mdx create mode 100644 platform/ui/src/components/InputFilterText/index.js create mode 100644 platform/ui/src/components/LegacyButtonGroup/LegacyButtonGroup.tsx create mode 100644 platform/ui/src/components/LegacyButtonGroup/__stories__/legacyButtonGroup.stories.mdx create mode 100644 platform/ui/src/components/LegacyButtonGroup/index.js create mode 100644 platform/ui/src/components/LegacySidePanel/LegacySidePanel.tsx create mode 100644 platform/ui/src/components/LegacySidePanel/__stories__/legacySidePanel.stories.mdx create mode 100644 platform/ui/src/components/LegacySidePanel/index.js rename platform/ui/src/components/{SidePanel => LegacySidePanel}/style.css (100%) create mode 100644 platform/ui/src/components/PanelSection/PanelSection.tsx create mode 100644 platform/ui/src/components/PanelSection/__stories__/PanelSection.stories.mdx create mode 100644 platform/ui/src/components/PanelSection/index.js create mode 100644 platform/ui/src/components/SegmentationGroupTable/AddSegmentRow.tsx create mode 100644 platform/ui/src/components/SegmentationGroupTable/NoSegmentationRow.tsx create mode 100644 platform/ui/src/components/SegmentationGroupTable/SegmentationDropDownRow.tsx delete mode 100644 platform/ui/src/components/SegmentationGroupTable/SegmentationGroup.tsx create mode 100644 platform/ui/src/components/Select/__stories__/select.stories.mdx create mode 100644 platform/ui/src/components/Tooltip/PortalTooltip.tsx create mode 100644 platform/ui/src/components/Tooltip/PortalTooltipCard.tsx delete mode 100644 platform/ui/src/components/Viewport/index.js create mode 100644 platform/ui/src/components/Viewport/index.ts create mode 100644 platform/ui/src/hooks/useSessionStorage.test.js create mode 100644 platform/ui/src/hooks/useSessionStorage.tsx create mode 100644 platform/ui/src/utils/getMaxDigits.ts diff --git a/.circleci/config.yml b/.circleci/config.yml index 9aa3bc95c6a..30c35af46ae 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -13,22 +13,24 @@ version: 2.1 ## orbs: codecov: codecov/codecov@1.0.5 - cypress: cypress-io/cypress@1.26.0 + cypress: cypress-io/cypress@3.2.1 + executors: - # Custom executor to override Cypress config - deploy-to-prod-executor: - docker: - - image: cimg/node:16.14 - environment: - CYPRESS_BASE_URL: https://ohif-staging.netlify.com/ - chrome-and-pacs: + cypress-custom: + description: | + Single Docker container used to run Cypress Tests docker: - # Primary container image where all steps run. - - image: 'cypress/browsers:node16.14.2-slim-chrome103-ff102' + - image: cimg/node:<< parameters.node-version >>-browsers + parameters: + node-version: + default: '18.16.1' + description: | + The version of Node to run your tests with. + type: string defaults: &defaults docker: - - image: cimg/node:16.14-browsers + - image: cimg/node:18.18 environment: TERM: xterm # Enable colors in term QUICK_BUILD: true @@ -43,6 +45,7 @@ jobs: steps: # Update yarn - run: yarn -v + - run: node --version # Checkout code and ALL Git Tags - checkout - restore_cache: @@ -90,43 +93,6 @@ jobs: file: '/home/circleci/repo/platform/core/coverage/reports' flags: 'core' - ### - # Workflow: PR_OPTIONAL_DOCKER_PUBLISH - ### - DOCKER_PR_PUBLISH: - <<: *defaults - steps: - # Enable yarn workspaces - - run: yarn config set workspaces-experimental true - - # Checkout code and ALL Git Tags - - checkout - - restore_cache: - name: Restore Yarn and Cypress Package Cache - keys: - # when lock file changes, use increasingly general patterns to restore cache - - yarn-packages-{{ checksum "yarn.lock" }} - - yarn-packages- - - - run: - name: Install Dependencies - command: yarn install --frozen-lockfile - - - setup_remote_docker: - docker_layer_caching: false - - - run: - name: Build and push Docker image - command: | - # Remove npm config - rm -f ./.npmrc - # Set our version number using vars - echo $CIRCLE_BUILD_NUM - # Build our image, auth, and push - docker build --tag ohif/app:PR_BUILD-$CIRCLE_BUILD_NUM . - echo $DOCKER_PWD | docker login -u $DOCKER_LOGIN --password-stdin - docker push ohif/app:PR_BUILD-$CIRCLE_BUILD_NUM - ### # Workflow: DEPLOY ### @@ -177,62 +143,52 @@ jobs: - commit.txt - version.json - # DEPLOY_TO_DEV: - # docker: - # - image: circleci/node:16.14.0 - # environment: - # TERM: xterm - # NETLIFY_SITE_ID: 32708787-c9b0-4634-b50f-7ca41952da77 - # working_directory: ~/repo - # steps: - # - attach_workspace: - # at: ~/repo - # - run: cd .netlify && npm install - # - run: cp .netlify/deploy-workflow/_redirects platform/app/dist/_redirects - # - run: cd .netlify && npm run deploy - - # DEPLOY_TO_STAGING: - # docker: - # - image: circleci/node:16.14.0 - # environment: - # TERM: xterm - # NETLIFY_SITE_ID: c7502ae3-b150-493c-8422-05701e44a969 - # working_directory: ~/repo - # steps: - # - attach_workspace: - # at: ~/repo - # - run: cd .netlify && npm install - # - run: cp .netlify/deploy-workflow/_redirects platform/app/dist/_redirects - # - run: cd .netlify && npm run deploy - - # DEPLOY_TO_PRODUCTION: - # docker: - # - image: circleci/node:16.14.0 - # environment: - # TERM: xterm - # NETLIFY_SITE_ID: 79c4a5da-5c95-4dc9-84f7-45fd9dfe21b0 - # working_directory: ~/repo - # steps: - # - attach_workspace: - # at: ~/repo - # - run: cd .netlify && npm install - # - run: cp .netlify/deploy-workflow/_redirects platform/app/dist/_redirects - # - run: cd .netlify && npm run deploy - - # DEPLOY_TO_RELEASE_DEV: - # docker: - # - image: circleci/node:16.14.0 - # environment: - # TERM: xterm - # NETLIFY_SITE_ID: 3270878-22 - # working_directory: ~/repo - # steps: - # - attach_workspace: - # at: ~/repo - # - run: cd .netlify && npm install - # - run: - # cp .netlify/deploy-workflow/_redirects platform/app/dist/_redirects - # - run: cd .netlify && npm run deploy + # just to make sure later on we can publish them + BUILD_PACKAGES_QUICK: + <<: *defaults + steps: + - run: yarn -v + # Checkout code and ALL Git Tags + - checkout + - attach_workspace: + at: ~/repo + # Use increasingly general patterns to restore cache + - restore_cache: + name: Restore Yarn and Cypress Package Cache + keys: + - yarn-packages-{{ checksum "yarn.lock" }} + - yarn-packages- + - run: + name: Install Dependencies + command: yarn install --frozen-lockfile + - save_cache: + name: Save Yarn Package Cache + paths: + - ~/.cache/yarn + key: yarn-packages-{{ checksum "yarn.lock" }} + - run: + name: Avoid hosts unknown for github + command: | + rm -rf ~/.ssh + mkdir ~/.ssh/ + echo -e "Host github.com\n\tStrictHostKeyChecking no\n" > ~/.ssh/config + git config --global user.email "danny.ri.brown+ohif-bot@gmail.com" + git config --global user.name "ohif-bot" + - run: + name: Authenticate with NPM registry + command: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/repo/.npmrc + - run: + name: Increase the event emitter limit + command: | + node ./increaseEventEmitterLimit.mjs + - run: + name: build half of the packages (to avoid out of memory in circleci) + command: | + yarn run build:package-all + - run: + name: build the other half of the packages + command: | + yarn run build:package-all-1 ### # Workflow: RELEASE @@ -269,8 +225,7 @@ jobs: git config --global user.name "ohif-bot" - run: name: Authenticate with NPM registry - command: - echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/repo/.npmrc + command: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/repo/.npmrc - run: name: Increase the event emitter limit command: | @@ -283,14 +238,21 @@ jobs: name: build the other half of the packages command: | yarn run build:package-all-1 + - run: + name: increase min time out + command: | + npm config set fetch-retry-mintimeout 20000 + - run: + name: increase max time out + command: | + npm config set fetch-retry-maxtimeout 120000 - run: name: publish package versions command: | node ./publish-version.mjs - run: name: Again set the NPM registry (was deleted in the version script) - command: - echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/repo/.npmrc + command: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/repo/.npmrc - run: name: publish package dist command: | @@ -337,8 +299,7 @@ jobs: - setup_remote_docker: docker_layer_caching: false - run: - name: - Build and push Docker image from the master branch (beta releases) + name: Build and push Docker image from the master branch (beta releases) command: | echo $(ls -l) @@ -365,71 +326,152 @@ jobs: docker push ohif/app:$IMAGE_VERSION_FULL fi -workflows: - version: 2 + # This is copied from the Cypress orb since the default for cypress/run is node 16 and + # we migrated to 18 + CYPRESS_CUSTOM_RUN: + description: | + A single, complete job to run Cypress end-to-end tests in your application. + executor: cypress-custom + parallelism: << parameters.parallelism >> + parameters: + cypress-cache-key: + default: cypress-cache-{{ arch }}-{{ checksum "package.json" }} + description: Cache key used to cache the Cypress binary. + type: string + cypress-cache-path: + default: ~/.cache/Cypress + description: | + By default, this will cache the '~/.cache/Cypress' directory so that the Cypress binary is cached. You can override this by providing your own cache path. + type: string + cypress-command: + default: npx cypress run + description: Command used to run your Cypress tests + type: string + include-branch-in-node-cache-key: + default: false + description: | + If true, this cache will only apply to runs within the same branch. (Adds -{{ .Branch }}- to the node cache key) + type: boolean + install-browsers: + default: false + description: | + Cypress runs by default in the Electron browser. Use this flag to install additional browsers to run your tests in. + This is only needed if you are passing the `--browser` flag in your `cypress-command`. + This parameter leverages the `circleci/browser-tools` orb and includes Chrome and FireFox. + If you need additional browser support you can set this to false and use an executor with a docker image + that includes the browsers of your choosing. See https://hub.docker.com/r/cypress/browsers/tags + type: boolean + install-command: + default: '' + description: Overrides the default NPM command (npm ci) + type: string + node-cache-version: + default: v1 + description: + Change the default node cache version if you need to clear the cache for any reason. + type: string + package-manager: + default: npm + description: Select the default node package manager to use. NPM v5+ Required. + enum: + - npm + - yarn + - yarn-berry + type: enum + parallelism: + default: 1 + description: | + Number of Circle machines to use for load balancing, min 1 + (requires `parallel` and `record` flags in your `cypress-command`) + type: integer + post-install: + default: '' + description: | + Additional commands to run after running install but before verifying Cypress and saving cache. + type: string + start-command: + default: '' + description: Command used to start your local dev server for Cypress to tests against + type: string + working-directory: + default: '' + description: Directory containing package.json + type: string + steps: + - cypress/install: + cypress-cache-key: << parameters.cypress-cache-key >> + cypress-cache-path: << parameters.cypress-cache-path >> + include-branch-in-node-cache-key: << parameters.include-branch-in-node-cache-key >> + install-browsers: << parameters.install-browsers >> + install-command: << parameters.install-command >> + node-cache-version: << parameters.node-cache-version >> + package-manager: << parameters.package-manager >> + post-install: << parameters.post-install >> + working-directory: << parameters.working-directory >> + - cypress/run-tests: + cypress-command: << parameters.cypress-command >> + start-command: << parameters.start-command >> + working-directory: << parameters.working-directory >> +workflows: PR_CHECKS: jobs: - - UNIT_TESTS - - # E2E: PWA - - cypress/run: - name: 'E2E: PWA' - executor: chrome-and-pacs - browser: chrome - pre-steps: - - run: | - # Clear yarn cache; use yarn from image (update image to update yarn) - rm -rf ~/.yarn - yarn -v - yarn: true - record: true - store_artifacts: true - working_directory: platform/app - build: yarn test:data - start: yarn run test:e2e:serve - spec: 'cypress/integration/**/*' - wait-on: 'http://localhost:3000' - cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}' - no-workspace: true # Don't persist workspace - post-steps: - - store_artifacts: - path: platform/app/cypress/screenshots - - store_artifacts: - path: platform/app/cypress/videos - - store_test_results: - path: platform/app/cypress/results + - BUILD_PACKAGES_QUICK: + filters: + branches: + ignore: master + - UNIT_TESTS: requires: - - UNIT_TESTS - - PR_OPTIONAL_VISUAL_TESTS: - jobs: - - AWAIT_APPROVAL: - type: approval - # Update hub.docker.org - - cypress/run: - name: 'Generate Percy Snapshots' - executor: cypress/browsers-chrome76 - browser: chrome - pre-steps: - - run: 'rm -rf ~/.yarn && yarn -v && yarn global add wait-on' - yarn: true - store_artifacts: false - working_directory: platform/app - build: - yarn test:data && npx cross-env QUICK_BUILD=true - APP_CONFIG=config/dicomweb-server.js yarn run build - # start server --> verify running --> percy + chrome + cypress - command: yarn run test:e2e:dist - cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}' - no-workspace: true # Don't persist workspace - post-steps: - - store_artifacts: - path: platform/app/cypress/screenshots - - store_artifacts: - path: platform/app/cypress/videos + - BUILD_PACKAGES_QUICK + - CYPRESS_CUSTOM_RUN: + name: 'Cypress Tests' + context: cypress + matrix: + parameters: + start-command: + - yarn run test:data && yarn run test:e2e:serve + install-browsers: + - true + cypress-command: + - 'npx wait-on@latest http://localhost:3000 && cd platform/app && npx cypress run + --record --browser chrome --parallel' + package-manager: + - 'yarn' + cypress-cache-key: + - 'yarn-packages-{{ checksum "yarn.lock" }}' + cypress-cache-path: + - '~/.cache/Cypress' requires: - - AWAIT_APPROVAL + - BUILD_PACKAGES_QUICK + + # PR_OPTIONAL_VISUAL_TESTS: + # jobs: + # - AWAIT_APPROVAL: + # type: approval + # # Update hub.docker.org + # - cypress/run: + # name: 'Generate Percy Snapshots' + # executor: cypress/browsers-chrome76 + # browser: chrome + # pre-steps: + # - run: 'rm -rf ~/.yarn && yarn -v && yarn global add wait-on' + # yarn: true + # store_artifacts: false + # working_directory: platform/app + # build: + # yarn test:data && npx cross-env QUICK_BUILD=true APP_CONFIG=config/dicomweb-server.js + # yarn run build + # # start server --> verify running --> percy + chrome + cypress + # command: yarn run test:e2e:dist + # cache-key: 'yarn-packages-{{ checksum "yarn.lock" }}' + # no-workspace: true # Don't persist workspace + # post-steps: + # - store_artifacts: + # path: platform/app/cypress/screenshots + # - store_artifacts: + # path: platform/app/cypress/videos + # requires: + # - AWAIT_APPROVAL # Our master branch deploys to viewer-dev.ohif.org, the viewer.ohif.org is # deployed from the release branch which is more stable and less frequently updated. diff --git a/.codespellrc b/.codespellrc new file mode 100644 index 00000000000..ab57ad157a0 --- /dev/null +++ b/.codespellrc @@ -0,0 +1,6 @@ +[codespell] +skip = .git,*.pdf,*.svg,yarn.lock,*.min.js,locales +# ignore words ending with … and some camelcased variables and names +ignore-regex = \b\S+…\S*|\b(doubleClick|afterAll|PostgresSQL)\b|\bWee, L\.|.*te.*Telugu.* +# some odd variables +ignore-words-list = datea,ser,childrens diff --git a/.docker/Nginx-Orthanc/config/nginx.conf b/.docker/Nginx-Orthanc/config/nginx.conf deleted file mode 100644 index c38ee5813df..00000000000 --- a/.docker/Nginx-Orthanc/config/nginx.conf +++ /dev/null @@ -1,48 +0,0 @@ -worker_processes 1; - -events { worker_connections 1024; } - -http { - - upstream orthanc-server { - server orthanc:8042; - } - - server { - listen [::]:80 default_server; - listen 80; - - # CORS Magic - add_header 'Access-Control-Allow-Origin' '*'; - add_header 'Access-Control-Allow_Credentials' 'true'; - add_header 'Access-Control-Allow-Headers' 'Authorization,Accept,Origin,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range'; - add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH'; - - location / { - - if ($request_method = 'OPTIONS') { - add_header 'Access-Control-Allow-Origin' '*'; - add_header 'Access-Control-Allow_Credentials' 'true'; - add_header 'Access-Control-Allow-Headers' 'Authorization,Accept,Origin,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range'; - add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH'; - add_header 'Access-Control-Max-Age' 1728000; - add_header 'Content-Type' 'text/plain charset=UTF-8'; - add_header 'Content-Length' 0; - return 204; - } - - proxy_pass http://orthanc:8042; - proxy_redirect off; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Host $server_name; - - # CORS Magic - add_header 'Access-Control-Allow-Origin' '*'; - add_header 'Access-Control-Allow_Credentials' 'true'; - add_header 'Access-Control-Allow-Headers' 'Authorization,Accept,Origin,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range'; - add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS,PUT,DELETE,PATCH'; - } - } -} diff --git a/.docker/Nginx-Orthanc/config/orthanc.json b/.docker/Nginx-Orthanc/config/orthanc.json deleted file mode 100644 index 2e10723c049..00000000000 --- a/.docker/Nginx-Orthanc/config/orthanc.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - "Name": "Orthanc inside Docker", - "StorageDirectory": "/var/lib/orthanc/db", - "IndexDirectory": "/var/lib/orthanc/db", - "StorageCompression": false, - "MaximumStorageSize": 0, - "MaximumPatientCount": 0, - "LuaScripts": [], - "Plugins": ["/usr/share/orthanc/plugins", "/usr/local/share/orthanc/plugins"], - "ConcurrentJobs": 2, - "HttpServerEnabled": true, - "HttpPort": 8042, - "HttpDescribeErrors": true, - "HttpCompressionEnabled": true, - "DicomServerEnabled": true, - "DicomAet": "ORTHANC", - "DicomCheckCalledAet": false, - "DicomPort": 4242, - "DefaultEncoding": "Latin1", - "DeflatedTransferSyntaxAccepted": true, - "JpegTransferSyntaxAccepted": true, - "Jpeg2000TransferSyntaxAccepted": true, - "JpegLosslessTransferSyntaxAccepted": true, - "JpipTransferSyntaxAccepted": true, - "Mpeg2TransferSyntaxAccepted": true, - "RleTransferSyntaxAccepted": true, - "UnknownSopClassAccepted": false, - "DicomScpTimeout": 30, - - "RemoteAccessAllowed": true, - "SslEnabled": false, - "SslCertificate": "certificate.pem", - "AuthenticationEnabled": false, - "RegisteredUsers": { - "test": "test" - }, - "DicomModalities": {}, - "DicomModalitiesInDatabase": false, - "DicomAlwaysAllowEcho": true, - "DicomAlwaysAllowStore": true, - "DicomCheckModalityHost": false, - "DicomScuTimeout": 10, - "OrthancPeers": {}, - "OrthancPeersInDatabase": false, - "HttpProxy": "", - - "HttpVerbose": true, - - "HttpTimeout": 10, - "HttpsVerifyPeers": true, - "HttpsCACertificates": "", - "UserMetadata": {}, - "UserContentType": {}, - "StableAge": 60, - "StrictAetComparison": false, - "StoreMD5ForAttachments": true, - "LimitFindResults": 0, - "LimitFindInstances": 0, - "LimitJobs": 10, - "LogExportedResources": false, - "KeepAlive": true, - "TcpNoDelay": true, - "HttpThreadsCount": 50, - "StoreDicom": true, - "DicomAssociationCloseDelay": 5, - "QueryRetrieveSize": 10, - "CaseSensitivePN": false, - "LoadPrivateDictionary": true, - "Dictionary": {}, - "SynchronousCMove": true, - "JobsHistorySize": 10, - "SaveJobs": true, - "OverwriteInstances": false, - "MediaArchiveSize": 1, - "StorageAccessOnFind": "Always", - "MetricsEnabled": true, - - "DicomWeb": { - "Enable": true, - "Root": "/dicom-web/", - "EnableWado": true, - "WadoRoot": "/wado", - "Host": "127.0.0.1", - "Ssl": false, - "StowMaxInstances": 10, - "StowMaxSize": 10, - "QidoCaseSensitive": false - } -} diff --git a/.docker/Nginx-Orthanc/docker-compose.yml b/.docker/Nginx-Orthanc/docker-compose.yml deleted file mode 100644 index eba7911a316..00000000000 --- a/.docker/Nginx-Orthanc/docker-compose.yml +++ /dev/null @@ -1,23 +0,0 @@ -version: '3.5' - -services: - orthanc: - image: jodogne/orthanc-plugins:1.11.0 - hostname: orthanc - volumes: - # Config - - ./config/orthanc.json:/etc/orthanc/orthanc.json:ro - # Persist data - - ./volumes/orthanc-db/:/var/lib/orthanc/db/ - ports: - - '4242:4242' # DICOM - - '8042:8042' # Web - restart: unless-stopped - nginx: - image: nginx:latest - volumes: - - ./config/nginx.conf:/etc/nginx/nginx.conf - ports: - - '80:80' #ngnix proxy - depends_on: - - orthanc diff --git a/.docker/Nginx-Orthanc/volumes/orthanc-db/.gitignore b/.docker/Nginx-Orthanc/volumes/orthanc-db/.gitignore deleted file mode 100644 index d6b7ef32c84..00000000000 --- a/.docker/Nginx-Orthanc/volumes/orthanc-db/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/.dockerignore b/.dockerignore index d4a539c50d9..ca74c539884 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,12 @@ # Reduces size of context and hides # files from Docker (can't COPY or ADD these) +# Note that typically the Docker context for various OHIF containers is the +# directory of this file (i.e. the root of the source). As such, this is +# the .dockerignore file for ALL Docker containers that are built. For example, +# the Docker containers built from the recipes in ./platform/app/.recipes will +# have this file as their .dockerignore. + # Output dist/ build/ @@ -28,3 +34,4 @@ dockerfile .vscode/ coverage/ docs/ +testdata/ diff --git a/.eslintrc.json b/.eslintrc.json index eecd3d05706..bc37a7dcf4d 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,10 +1,5 @@ { - "plugins": [ - "@typescript-eslint", - "import", - "eslint-plugin-tsdoc", - "prettier" - ], + "plugins": ["@typescript-eslint", "import", "eslint-plugin-tsdoc", "prettier"], "extends": [ "react-app", "eslint:recommended", diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 5a2907b18fd..83577dd7c13 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -39,8 +39,7 @@ body: attributes: label: The current behavior description: - 'A clear and concise description of what happens instead of the expected - behavior.' + 'A clear and concise description of what happens instead of the expected behavior.' validations: required: true @@ -48,8 +47,7 @@ body: id: expected_behavior attributes: label: The expected behavior - description: - 'A clear and concise description of what you expected to happen.' + description: 'A clear and concise description of what you expected to happen.' validations: required: true @@ -66,7 +64,7 @@ body: attributes: label: 'Node version' description: 'Your Node.js version.' - placeholder: 'e.g., 16.14.0' + placeholder: 'e.g., 18.16.1' validations: required: true - type: input @@ -81,6 +79,6 @@ body: - type: markdown attributes: value: > - > :warning: Reports we cannot reproduce are at risk of being marked - stale and > closed. The more information you can provide, the more - likely we are to look > into and address your issue. + > :warning: Reports we cannot reproduce are at risk of being marked stale and > closed. The + more information you can provide, the more likely we are to look > into and address your + issue. diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml index e03e8d9afe1..0331ba9bfa5 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.yml +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -20,17 +20,15 @@ body: attributes: label: 'What feature or change would you like to see made?' description: - 'Please include as much detail as possible including possibly mock up - screen shots, workflow or logic flow diagrams etc.' + 'Please include as much detail as possible including possibly mock up screen shots, workflow + or logic flow diagrams etc.' placeholder: '...' validations: required: true - type: textarea attributes: label: 'Why should we prioritize this feature?' - description: - 'Discuss if and how the requested feature interacts with existing - features.' + description: 'Discuss if and how the requested feature interacts with existing features.' placeholder: '...' validations: required: true diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index cc3752267f1..6a1753d87d8 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -83,7 +83,7 @@ after the commits are squashed. #### Tested Environment - [] OS: -- [] Node version: +- [] Node version: - [] Browser: diff --git a/.github/stale.yml b/.github/stale.yml index 4d6cdd4e356..28535cca620 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -19,8 +19,7 @@ exemptLabels: staleLabel: 'Stale :baguette_bread:' # Comment to post when marking an issue as stale. Set to `false` to disable markComment: > - This issue has been automatically marked as stale because it has not had - recent activity. It will be closed if no further activity occurs. Thank you - for your contributions. + This issue has been automatically marked as stale because it has not had recent activity. It will + be closed if no further activity occurs. Thank you for your contributions. # Comment to post when closing a stale issue. Set to `false` to disable closeComment: false diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml new file mode 100644 index 00000000000..7373affc383 --- /dev/null +++ b/.github/workflows/codespell.yml @@ -0,0 +1,22 @@ +--- +name: Codespell + +on: + push: + branches: [master] + pull_request: + branches: [master] + +permissions: + contents: read + +jobs: + codespell: + name: Check for spelling errors + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Codespell + uses: codespell-project/actions-codespell@v2 diff --git a/.gitignore b/.gitignore index 2911f13930f..ed3400749a7 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,6 @@ platform/app/src/pluginImports.js /Viewers.iml platform/app/.recipes/Nginx-Dcm4Che/dcm4che/dcm4che-arc/* platform/app/.recipes/OpenResty-Orthanc/logs/* +.vercel + +.vs diff --git a/.gitmodules b/.gitmodules index 413cf77c17f..f787a67a4b0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,4 @@ [submodule "testdata"] path = testdata url = https://github.com/OHIF/viewer-testdata-dicomweb.git + branch = main diff --git a/.netlify/www/index.html b/.netlify/www/index.html index 3d889be8291..c817960c50d 100644 --- a/.netlify/www/index.html +++ b/.netlify/www/index.html @@ -1,23 +1,21 @@ + + OHIF Viewer: Deploy Preview + - - OHIF Viewer: Deploy Preview - - - -

Index of Previews

- - - + +

Index of Previews

+ + diff --git a/.node-version b/.node-version index 832d3850644..3876fd49864 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -16.14.0 +18.16.1 diff --git a/.prettierrc b/.prettierrc index 6a64b74e2d9..c7eeb08defb 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,9 +1,12 @@ { + "plugins": ["prettier-plugin-tailwindcss"], "trailingComma": "es5", - "printWidth": 80, + "printWidth": 100, "proseWrap": "always", "tabWidth": 2, "semi": true, "singleQuote": true, - "arrowParens": "avoid" + "arrowParens": "avoid", + "singleAttributePerLine": true, + "endOfLine": "auto" } diff --git a/.scripts/dicom-json-generator.js b/.scripts/dicom-json-generator.js new file mode 100644 index 00000000000..5b9f45a16a6 --- /dev/null +++ b/.scripts/dicom-json-generator.js @@ -0,0 +1,265 @@ +/* + * This script uses nodejs to generate a JSON file from a DICOM study folder. + * You need to have dcmjs installed in your project. + * The JSON file can be used to load the study into the OHIF Viewer. You can get more detail + * in the DICOM JSON Data source on docs.ohif.org + * + * Usage: node dicomStudyToJSONLaunch.js + * + * params: + * - studyFolder: path to the study folder + * - urlPrefix: prefix to the url that will be used to load the study into the viewer. For instance + * we use https://ohif-assets.s3.us-east-2.amazonaws.com/dicom-json/data as the urlPrefix for the + * example since the data is hosted on S3 and each study is in a folder. So the url in the generated + * json file for the first instance of the first series of the first study will be + * dicomweb:https://ohif-assets.s3.us-east-2.amazonaws.com/dicom-json/data/Series1/Instance1 + * - outputJSONPath: path to the output JSON file + */ +const dcmjs = require('dcmjs'); +const path = require('path'); +const fs = require('fs').promises; + +const args = process.argv.slice(2); +const [studyDirectory, urlPrefix, outputPath] = args; + +if (args.length !== 3) { + console.error('Usage: node dicomStudyToJSONLaunch.js '); + process.exit(1); +} + +const model = { + studies: [], +}; + +async function convertDICOMToJSON(studyDirectory, urlPrefix, outputPath) { + try { + const files = await recursiveReadDir(studyDirectory); + console.debug('Processing...'); + + for (const file of files) { + if (!file.includes('.DS_Store') && !file.includes('.xml')) { + const arrayBuffer = await fs.readFile(file); + const dicomDict = dcmjs.data.DicomMessage.readFile(arrayBuffer.buffer); + const instance = dcmjs.data.DicomMetaDictionary.naturalizeDataset(dicomDict.dict); + + instance.fileLocation = createImageId(file, urlPrefix, studyDirectory); + processInstance(instance); + } + } + + console.log('Successfully loaded data'); + + model.studies.forEach(study => { + study.NumInstances = findInstancesNumber(study); + study.Modalities = findModalities(study).join('/'); + }); + + await fs.writeFile(outputPath, JSON.stringify(model, null, 2)); + console.log('JSON saved'); + } catch (error) { + console.error(error); + } +} + +async function recursiveReadDir(dir) { + let results = []; + const list = await fs.readdir(dir); + for (const file of list) { + const filePath = path.resolve(dir, file); + const stat = await fs.stat(filePath); + if (stat.isDirectory()) { + const res = await recursiveReadDir(filePath); + results = results.concat(res); + } else { + results.push(filePath); + } + } + return results; +} + +function createImageId(fileLocation, urlPrefix, studyDirectory) { + const relativePath = path.relative(studyDirectory, fileLocation); + const normalizedPath = path.normalize(relativePath).replace(/\\/g, '/'); + return `dicomweb:${urlPrefix}${normalizedPath}`; +} + +function processInstance(instance) { + const { StudyInstanceUID, SeriesInstanceUID } = instance; + let study = getStudy(StudyInstanceUID); + + if (!study) { + study = createStudyMetadata(StudyInstanceUID, instance); + model.studies.push(study); + } + + let series = getSeries(StudyInstanceUID, SeriesInstanceUID); + + if (!series) { + series = createSeriesMetadata(instance); + study.series.push(series); + } + + const instanceMetaData = + instance.NumberOfFrames > 1 + ? createInstanceMetaDataMultiFrame(instance) + : createInstanceMetaData(instance); + + series.instances.push(...[].concat(instanceMetaData)); +} + +function getStudy(StudyInstanceUID) { + return model.studies.find(study => study.StudyInstanceUID === StudyInstanceUID); +} + +function getSeries(StudyInstanceUID, SeriesInstanceUID) { + const study = getStudy(StudyInstanceUID); + return study + ? study.series.find(series => series.SeriesInstanceUID === SeriesInstanceUID) + : undefined; +} + +const findInstancesNumber = study => { + let numInstances = 0; + study.series.forEach(aSeries => { + numInstances = numInstances + aSeries.instances.length; + }); + return numInstances; +}; + +const findModalities = study => { + let modalities = new Set(); + study.series.forEach(aSeries => { + modalities.add(aSeries.Modality); + }); + return Array.from(modalities); +}; + +function createStudyMetadata(StudyInstanceUID, instance) { + return { + StudyInstanceUID, + StudyDescription: instance.StudyDescription, + StudyDate: instance.StudyDate, + StudyTime: instance.StudyTime, + PatientName: instance.PatientName, + PatientID: instance.PatientID || '1234', // this is critical to have + AccessionNumber: instance.AccessionNumber, + PatientAge: instance.PatientAge, + PatientSex: instance.PatientSex, + PatientWeight: instance.PatientWeight, + series: [], + }; +} +function createSeriesMetadata(instance) { + return { + SeriesInstanceUID: instance.SeriesInstanceUID, + SeriesDescription: instance.SeriesDescription, + SeriesNumber: instance.SeriesNumber, + SeriesTime: instance.SeriesTime, + Modality: instance.Modality, + SliceThickness: instance.SliceThickness, + instances: [], + }; +} +function commonMetaData(instance) { + return { + Columns: instance.Columns, + Rows: instance.Rows, + InstanceNumber: instance.InstanceNumber, + SOPClassUID: instance.SOPClassUID, + AcquisitionNumber: instance.AcquisitionNumber, + PhotometricInterpretation: instance.PhotometricInterpretation, + BitsAllocated: instance.BitsAllocated, + BitsStored: instance.BitsStored, + PixelRepresentation: instance.PixelRepresentation, + SamplesPerPixel: instance.SamplesPerPixel, + PixelSpacing: instance.PixelSpacing, + HighBit: instance.HighBit, + ImageOrientationPatient: instance.ImageOrientationPatient, + ImagePositionPatient: instance.ImagePositionPatient, + FrameOfReferenceUID: instance.FrameOfReferenceUID, + ImageType: instance.ImageType, + Modality: instance.Modality, + SOPInstanceUID: instance.SOPInstanceUID, + SeriesInstanceUID: instance.SeriesInstanceUID, + StudyInstanceUID: instance.StudyInstanceUID, + WindowCenter: instance.WindowCenter, + WindowWidth: instance.WindowWidth, + RescaleIntercept: instance.RescaleIntercept, + RescaleSlope: instance.RescaleSlope, + }; +} + +function conditionalMetaData(instance) { + return { + ...(instance.ConceptNameCodeSequence && { + ConceptNameCodeSequence: instance.ConceptNameCodeSequence, + }), + ...(instance.SeriesDate && { SeriesDate: instance.SeriesDate }), + ...(instance.ReferencedSeriesSequence && { + ReferencedSeriesSequence: instance.ReferencedSeriesSequence, + }), + ...(instance.SharedFunctionalGroupsSequence && { + SharedFunctionalGroupsSequence: instance.SharedFunctionalGroupsSequence, + }), + ...(instance.PerFrameFunctionalGroupsSequence && { + PerFrameFunctionalGroupsSequence: instance.PerFrameFunctionalGroupsSequence, + }), + ...(instance.ContentSequence && { ContentSequence: instance.ContentSequence }), + ...(instance.ContentTemplateSequence && { + ContentTemplateSequence: instance.ContentTemplateSequence, + }), + ...(instance.CurrentRequestedProcedureEvidenceSequence && { + CurrentRequestedProcedureEvidenceSequence: instance.CurrentRequestedProcedureEvidenceSequence, + }), + ...(instance.CodingSchemeIdentificationSequence && { + CodingSchemeIdentificationSequence: instance.CodingSchemeIdentificationSequence, + }), + ...(instance.RadiopharmaceuticalInformationSequence && { + RadiopharmaceuticalInformationSequence: instance.RadiopharmaceuticalInformationSequence, + }), + ...(instance.ROIContourSequence && { + ROIContourSequence: instance.ROIContourSequence, + }), + ...(instance.StructureSetROISequence && { + StructureSetROISequence: instance.StructureSetROISequence, + }), + ...(instance.ReferencedFrameOfReferenceSequence && { + ReferencedFrameOfReferenceSequence: instance.ReferencedFrameOfReferenceSequence, + }), + ...(instance.CorrectedImage && { CorrectedImage: instance.CorrectedImage }), + ...(instance.Units && { Units: instance.Units }), + ...(instance.DecayCorrection && { DecayCorrection: instance.DecayCorrection }), + ...(instance.AcquisitionDate && { AcquisitionDate: instance.AcquisitionDate }), + ...(instance.AcquisitionTime && { AcquisitionTime: instance.AcquisitionTime }), + ...(instance.PatientWeight && { PatientWeight: instance.PatientWeight }), + ...(instance.NumberOfFrames && { NumberOfFrames: instance.NumberOfFrames }), + ...(instance.FrameTime && { FrameTime: instance.FrameTime }), + ...(instance.EncapsulatedDocument && { EncapsulatedDocument: instance.EncapsulatedDocument }), + ...(instance.SequenceOfUltrasoundRegions && { + SequenceOfUltrasoundRegions: instance.SequenceOfUltrasoundRegions, + }), + }; +} + +function createInstanceMetaData(instance) { + const metadata = { + ...commonMetaData(instance), + ...conditionalMetaData(instance), + }; + return { metadata, url: instance.fileLocation }; +} + +function createInstanceMetaDataMultiFrame(instance) { + const instances = []; + const commonData = commonMetaData(instance); + const conditionalData = conditionalMetaData(instance); + + for (let i = 1; i <= instance.NumberOfFrames; i++) { + const metadata = { ...commonData, ...conditionalData }; + const result = { metadata, url: instance.fileLocation + `?frame=${i}` }; + instances.push(result); + } + return instances; +} + +convertDICOMToJSON(studyDirectory, urlPrefix, outputPath); diff --git a/.vscode/settings.json b/.vscode/settings.json index db08c79b1ab..84cc76bc295 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -31,6 +31,6 @@ "prettier.endOfLine": "lf", "workbench.colorCustomizations": {}, "editor.codeActionsOnSave": { - "source.fixAll.eslint": true + "source.fixAll.eslint": "explicit" } } diff --git a/.webpack/helpers/excludeNodeModulesExcept.js b/.webpack/helpers/excludeNodeModulesExcept.js index f7234de1a24..ce4d3c3e29e 100644 --- a/.webpack/helpers/excludeNodeModulesExcept.js +++ b/.webpack/helpers/excludeNodeModulesExcept.js @@ -5,11 +5,11 @@ function excludeNodeModulesExcept(modules) { if (pathSep == '\\') // must be quoted for use in a regexp: pathSep = '\\\\'; - var moduleRegExps = modules.map(function(modName) { + var moduleRegExps = modules.map(function (modName) { return new RegExp('node_modules' + pathSep + modName); }); - return function(modulePath) { + return function (modulePath) { if (/node_modules/.test(modulePath)) { for (var i = 0; i < moduleRegExps.length; i++) if (moduleRegExps[i].test(modulePath)) return false; diff --git a/.webpack/rules/cssToJavaScript.js b/.webpack/rules/cssToJavaScript.js index 05365d31bcb..0cd60e909ba 100644 --- a/.webpack/rules/cssToJavaScript.js +++ b/.webpack/rules/cssToJavaScript.js @@ -1,9 +1,7 @@ const autoprefixer = require('autoprefixer'); const path = require('path'); const tailwindcss = require('tailwindcss'); -const tailwindConfigPath = path.resolve( - '../../platform/app/tailwind.config.js' -); +const tailwindConfigPath = path.resolve('../../platform/app/tailwind.config.js'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const devMode = process.env.NODE_ENV !== 'production'; diff --git a/.webpack/rules/stylusToJavaScript.js b/.webpack/rules/stylusToJavaScript.js new file mode 100644 index 00000000000..1d83f95281e --- /dev/null +++ b/.webpack/rules/stylusToJavaScript.js @@ -0,0 +1,10 @@ +const stylusToJavaScript = { + test: /\.styl$/, + use: [ + { loader: 'style-loader' }, // 3. Style nodes from JS Strings + { loader: 'css-loader' }, // 2. CSS to CommonJS + { loader: 'stylus-loader' }, // 1. Stylus to CSS + ], +}; + +module.exports = stylusToJavaScript; diff --git a/.webpack/webpack.base.js b/.webpack/webpack.base.js index 347c02de226..6efedd0d2cd 100644 --- a/.webpack/webpack.base.js +++ b/.webpack/webpack.base.js @@ -7,8 +7,7 @@ const fs = require('fs'); const webpack = require('webpack'); // ~~ PLUGINS -const BundleAnalyzerPlugin = require('webpack-bundle-analyzer') - .BundleAnalyzerPlugin; +const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; const TerserJSPlugin = require('terser-webpack-plugin'); // ~~ PackageJSON @@ -19,6 +18,7 @@ const loadShadersRule = require('./rules/loadShaders.js'); const loadWebWorkersRule = require('./rules/loadWebWorkers.js'); const transpileJavaScriptRule = require('./rules/transpileJavaScript.js'); const cssToJavaScript = require('./rules/cssToJavaScript.js'); +const stylusToJavaScript = require('./rules/stylusToJavaScript.js'); // ~~ ENV VARS const NODE_ENV = process.env.NODE_ENV; @@ -26,15 +26,34 @@ const QUICK_BUILD = process.env.QUICK_BUILD; const BUILD_NUM = process.env.CIRCLE_BUILD_NUM || '0'; // read from ../version.txt -const VERSION_NUMBER = - fs.readFileSync(path.join(__dirname, '../version.txt'), 'utf8') || ''; +const VERSION_NUMBER = fs.readFileSync(path.join(__dirname, '../version.txt'), 'utf8') || ''; -const COMMIT_HASH = - fs.readFileSync(path.join(__dirname, '../commit.txt'), 'utf8') || ''; +const COMMIT_HASH = fs.readFileSync(path.join(__dirname, '../commit.txt'), 'utf8') || ''; // dotenv.config(); +const defineValues = { + /* Application */ + 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), + 'process.env.NODE_DEBUG': JSON.stringify(process.env.NODE_DEBUG), + 'process.env.DEBUG': JSON.stringify(process.env.DEBUG), + 'process.env.PUBLIC_URL': JSON.stringify(process.env.PUBLIC_URL || '/'), + 'process.env.BUILD_NUM': JSON.stringify(BUILD_NUM), + 'process.env.VERSION_NUMBER': JSON.stringify(VERSION_NUMBER), + 'process.env.COMMIT_HASH': JSON.stringify(COMMIT_HASH), + /* i18n */ + 'process.env.USE_LOCIZE': JSON.stringify(process.env.USE_LOCIZE || ''), + '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 || ''), +}; + +// Only redefine updated values. This avoids warning messages in the logs +if (!process.env.APP_CONFIG) { + defineValues['process.env.APP_CONFIG'] = ''; +} + module.exports = (env, argv, { SRC_DIR, ENTRY }) => { if (!process.env.NODE_ENV) { throw new Error('process.env.NODE_ENV not set'); @@ -94,6 +113,9 @@ module.exports = (env, argv, { SRC_DIR, ENTRY }) => { }, }, cssToJavaScript, + // Note: Only uncomment the following if you are using the old style of stylus in v2 + // Also you need to uncomment this platform/app/.webpack/rules/extractStyleChunks.js + // stylusToJavaScript, { test: /\.wasm/, type: 'asset/resource', @@ -105,10 +127,7 @@ module.exports = (env, argv, { SRC_DIR, ENTRY }) => { alias: { // Viewer project '@': path.resolve(__dirname, '../platform/app/src'), - '@components': path.resolve( - __dirname, - '../platform/app/src/components' - ), + '@components': path.resolve(__dirname, '../platform/app/src/components'), '@hooks': path.resolve(__dirname, '../platform/app/src/hooks'), '@routes': path.resolve(__dirname, '../platform/app/src/routes'), '@state': path.resolve(__dirname, '../platform/app/src/state'), @@ -131,29 +150,17 @@ module.exports = (env, argv, { SRC_DIR, ENTRY }) => { extensions: ['.js', '.jsx', '.json', '.ts', '.tsx', '*'], // symlinked resources are resolved to their real path, not their symlinked location symlinks: true, - fallback: { fs: false, path: false, zlib: false }, + fallback: { + fs: false, + path: false, + zlib: false, + buffer: require.resolve('buffer'), + }, }, plugins: [ - new webpack.DefinePlugin({ - /* Application */ - 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), - 'process.env.DEBUG': JSON.stringify(process.env.DEBUG), - 'process.env.APP_CONFIG': JSON.stringify(process.env.APP_CONFIG || ''), - 'process.env.PUBLIC_URL': JSON.stringify(process.env.PUBLIC_URL || '/'), - 'process.env.BUILD_NUM': JSON.stringify(BUILD_NUM), - 'process.env.VERSION_NUMBER': JSON.stringify(VERSION_NUMBER), - 'process.env.COMMIT_HASH': JSON.stringify(COMMIT_HASH), - /* i18n */ - 'process.env.USE_LOCIZE': JSON.stringify(process.env.USE_LOCIZE || ''), - '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 || '' - ), + new webpack.DefinePlugin(defineValues), + new webpack.ProvidePlugin({ + Buffer: ['buffer', 'Buffer'], }), // Uncomment to generate bundle analyzer // new BundleAnalyzerPlugin(), diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000000..063b2ba5512 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,1217 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [3.8.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.59...v3.8.0-beta.60) (2024-03-15) + + +### Features + +* **delete measurement:** icon for measurement table ([#3775](https://github.com/OHIF/Viewers/issues/3775)) ([f7fe91c](https://github.com/OHIF/Viewers/commit/f7fe91c5f6c4f05f3f3f5f640d3a119bd40a5870)) + + + + + +# [3.8.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.58...v3.8.0-beta.59) (2024-03-08) + + +### Bug Fixes + +* **cli:** mode creation template ([#3876](https://github.com/OHIF/Viewers/issues/3876)) ([#3981](https://github.com/OHIF/Viewers/issues/3981)) ([e485d68](https://github.com/OHIF/Viewers/commit/e485d68fd4619ce7187113cbe59e47f9523dbcc8)) + + + + + +# [3.8.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.57...v3.8.0-beta.58) (2024-03-05) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.8.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.56...v3.8.0-beta.57) (2024-02-28) + + +### Bug Fixes + +* **docs:** Minor typos in hpModule.md ([#3962](https://github.com/OHIF/Viewers/issues/3962)) ([4cdfdae](https://github.com/OHIF/Viewers/commit/4cdfdae8149166cf9dc91a55c0d7f2a224e55d8f)) + + + + + +# [3.8.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.55...v3.8.0-beta.56) (2024-02-22) + + +### Bug Fixes + +* **demo:** Deploy issue ([#3951](https://github.com/OHIF/Viewers/issues/3951)) ([21e8a2b](https://github.com/OHIF/Viewers/commit/21e8a2bd0b7cc72f90a31e472d285d761be15d30)) + + + + + +# [3.8.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.54...v3.8.0-beta.55) (2024-02-21) + + +### Features + +* **resize:** Optimize resizing process and maintain zoom level ([#3889](https://github.com/OHIF/Viewers/issues/3889)) ([b3a0faf](https://github.com/OHIF/Viewers/commit/b3a0faf5f5f0a1993b2b017eb4cc1216164ea2c6)) + + + + + +# [3.8.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.53...v3.8.0-beta.54) (2024-02-14) + + +### Features + +* **errorboundary:** format stack trace properly ([#3931](https://github.com/OHIF/Viewers/issues/3931)) ([0eac386](https://github.com/OHIF/Viewers/commit/0eac386a31a5d6965536360aa65a44769c1a5740)) + + + + + +# [3.8.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.52...v3.8.0-beta.53) (2024-02-05) + + +### Bug Fixes + +* 🐛 Sort merge results based on default data source (input) ([#3903](https://github.com/OHIF/Viewers/issues/3903)) ([5bba98e](https://github.com/OHIF/Viewers/commit/5bba98ed848bdf46b5ba4fc4708527cced3308b5)) + + + + + +# [3.8.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.51...v3.8.0-beta.52) (2024-01-22) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.8.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.50...v3.8.0-beta.51) (2024-01-22) + + +### Bug Fixes + +* catch errors in getPTImageIdInstanceMetadata ([#3897](https://github.com/OHIF/Viewers/issues/3897)) ([a47aeb8](https://github.com/OHIF/Viewers/commit/a47aeb8bd729dcb8d2cfc13b27a31b0dd88f11ad)) + + + + + +# [3.8.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.49...v3.8.0-beta.50) (2024-01-22) + + +### Bug Fixes + +* **viewport-sync:** remember synced viewports bw stack and volume and RENAME StackImageSync to ImageSliceSync ([#3849](https://github.com/OHIF/Viewers/issues/3849)) ([e4a116b](https://github.com/OHIF/Viewers/commit/e4a116b074fcb85c8cbcc9db44fdec565f3386db)) + + + + + +# [3.8.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.48...v3.8.0-beta.49) (2024-01-19) + + +### Bug Fixes + +* is same orientaiton ([#3905](https://github.com/OHIF/Viewers/issues/3905)) ([31b837f](https://github.com/OHIF/Viewers/commit/31b837fa90f631d4984482c6e952373fbb8bdbfc)) + + + + + +# [3.8.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.47...v3.8.0-beta.48) (2024-01-17) + + +### Bug Fixes + +* 🐛 Check merge key for merge data source ([#3901](https://github.com/OHIF/Viewers/issues/3901)) ([911d672](https://github.com/OHIF/Viewers/commit/911d67283536b2fe7930948f2819ea0ad66e2a32)) + + + + + +# [3.8.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.46...v3.8.0-beta.47) (2024-01-12) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.8.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.45...v3.8.0-beta.46) (2024-01-12) + + +### Bug Fixes + +* Update CS3D to fix second render ([#3892](https://github.com/OHIF/Viewers/issues/3892)) ([d00a86b](https://github.com/OHIF/Viewers/commit/d00a86b022742ea089d246d06cfd691f43b64412)) + + + + + +# [3.8.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.44...v3.8.0-beta.45) (2024-01-09) + + +### Features + +* **hp:** enable OHIF to run with partial metadata for large studies at the cost of less effective hanging protocol ([#3804](https://github.com/OHIF/Viewers/issues/3804)) ([0049f4c](https://github.com/OHIF/Viewers/commit/0049f4c0303f0b6ea995972326fc8784259f5a47)) + + + + + +# [3.8.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.43...v3.8.0-beta.44) (2024-01-09) + + +### Features + +* **transferSyntax:** prefer server transcoded transfer syntax for all images ([#3883](https://github.com/OHIF/Viewers/issues/3883)) ([1456a49](https://github.com/OHIF/Viewers/commit/1456a493d66c90c787b022256c9f2846afb115fc)) + + + + + +# [3.8.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.42...v3.8.0-beta.43) (2024-01-09) + + +### Bug Fixes + +* **segmentation:** upgrade cs3d to fix various segmentation bugs ([#3885](https://github.com/OHIF/Viewers/issues/3885)) ([b1efe40](https://github.com/OHIF/Viewers/commit/b1efe40aa146e4052cc47b3f774cabbb47a8d1a6)) + + + + + +# [3.8.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.41...v3.8.0-beta.42) (2024-01-08) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.8.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.40...v3.8.0-beta.41) (2024-01-08) + + +### Features + +* Add on mode init hook ([#3882](https://github.com/OHIF/Viewers/issues/3882)) ([f58725c](https://github.com/OHIF/Viewers/commit/f58725ce40685f7297181ef98d81bc28420c8291)) + + + + + +# [3.8.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.39...v3.8.0-beta.40) (2024-01-08) + + +### Features + +* **ui:** sidePanel expandedWidth ([#3728](https://github.com/OHIF/Viewers/issues/3728)) ([61bf22c](https://github.com/OHIF/Viewers/commit/61bf22c6f80e764bdf5c3b56bb0124a95aa0f793)) + + + + + +# [3.8.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.38...v3.8.0-beta.39) (2024-01-08) + + +### Features + +* improve disableEditing flag ([#3875](https://github.com/OHIF/Viewers/issues/3875)) ([2049c09](https://github.com/OHIF/Viewers/commit/2049c0936c86f819604c243d3dc7b3fe971b5b2c)) + + + + + +# [3.8.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.37...v3.8.0-beta.38) (2024-01-08) + + +### Bug Fixes + +* convert radian to degree value for mip rotation ([#3881](https://github.com/OHIF/Viewers/issues/3881)) ([bf846c9](https://github.com/OHIF/Viewers/commit/bf846c94c378f04b9f44dcd71be3f056dbcfe0b5)) +* PDF display request in v3 ([#3878](https://github.com/OHIF/Viewers/issues/3878)) ([9865030](https://github.com/OHIF/Viewers/commit/98650302c7575f0aea386e32cfc4112c378035e6)) + + + + + +# [3.8.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.36...v3.8.0-beta.37) (2024-01-08) + + +### Bug Fixes + +* colormap for stack viewports via HangingProtocol ([#3866](https://github.com/OHIF/Viewers/issues/3866)) ([e8858f3](https://github.com/OHIF/Viewers/commit/e8858f3eb55552f695af4a55980f9ae2e9af7291)) + + + + + +# [3.8.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.35...v3.8.0-beta.36) (2023-12-15) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.8.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.34...v3.8.0-beta.35) (2023-12-14) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.8.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.33...v3.8.0-beta.34) (2023-12-13) + + +### Bug Fixes + +* **icon-style:** Ensure consistent icon dimensions ([#3727](https://github.com/OHIF/Viewers/issues/3727)) ([6ca13c0](https://github.com/OHIF/Viewers/commit/6ca13c0a4cb5a95bbb52b0db902b5dbf72f8aa6e)) + + +### Features + +* **overlay:** add inline binary overlays ([#3852](https://github.com/OHIF/Viewers/issues/3852)) ([0177b62](https://github.com/OHIF/Viewers/commit/0177b625ba86760168bc4db58c8a109aa9ee83cb)) + + + + + +# [3.8.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.32...v3.8.0-beta.33) (2023-12-13) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.8.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.31...v3.8.0-beta.32) (2023-12-13) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.8.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.30...v3.8.0-beta.31) (2023-12-13) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.8.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.29...v3.8.0-beta.30) (2023-12-13) + + +### Features + +* **customizationService:** Enable saving and loading of private tags in SRs ([#3842](https://github.com/OHIF/Viewers/issues/3842)) ([e1f55e6](https://github.com/OHIF/Viewers/commit/e1f55e65f2d2a34136ad5d0b1ada77d337a0ea23)) + + + + + +# [3.8.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.28...v3.8.0-beta.29) (2023-12-13) + + +### Bug Fixes + +* address and improve system vulnerabilities ([#3851](https://github.com/OHIF/Viewers/issues/3851)) ([805c532](https://github.com/OHIF/Viewers/commit/805c53270f243ec61f142a3ffa0af500021cd5ec)) + + +### Features + +* **config:** Add activateViewportBeforeInteraction parameter for viewport interaction customization ([#3847](https://github.com/OHIF/Viewers/issues/3847)) ([f707b4e](https://github.com/OHIF/Viewers/commit/f707b4ebc996f379cd30337badc06b07e6e35ac5)) +* **i18n:** enhanced i18n support ([#3761](https://github.com/OHIF/Viewers/issues/3761)) ([d14a8f0](https://github.com/OHIF/Viewers/commit/d14a8f0199db95cd9e85866a011b64d6bf830d57)) + + + + + +# [3.8.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.27...v3.8.0-beta.28) (2023-12-08) + + +### Features + +* **HP:** Added new 3D hanging protocols to be used in the new layout selector ([#3844](https://github.com/OHIF/Viewers/issues/3844)) ([59576d6](https://github.com/OHIF/Viewers/commit/59576d695d4d26601d35c43f73d602f0b12d72bf)) + + + + + +# [3.8.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.26...v3.8.0-beta.27) (2023-12-06) + + +### Bug Fixes + +* **auth:** fix the issue with oauth at a non root path ([#3840](https://github.com/OHIF/Viewers/issues/3840)) ([6651008](https://github.com/OHIF/Viewers/commit/6651008fbb35dabd5991c7f61128e6ef324012df)) + + + + + +# [3.8.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.25...v3.8.0-beta.26) (2023-11-28) + + +### Bug Fixes + +* **SM:** drag and drop is now fixed for SM ([#3813](https://github.com/OHIF/Viewers/issues/3813)) ([f1a6764](https://github.com/OHIF/Viewers/commit/f1a67647aed635437b188cea7cf5d5a8fb974bbe)) + + + + + +# [3.8.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.24...v3.8.0-beta.25) (2023-11-27) + + +### Bug Fixes + +* **cine:** Set cine disabled on mode exit. ([#3812](https://github.com/OHIF/Viewers/issues/3812)) ([924affa](https://github.com/OHIF/Viewers/commit/924affa7b5d420c2f91522a075cecbb3c78e8f52)) + + + + + +# [3.8.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.23...v3.8.0-beta.24) (2023-11-24) + + +### Bug Fixes + +* Update the CS3D packages to add the most recent HTJ2K TSUIDS ([#3806](https://github.com/OHIF/Viewers/issues/3806)) ([9d1884d](https://github.com/OHIF/Viewers/commit/9d1884d7d8b6b2a1cdc26965a96995838aa72682)) + + + + + +# [3.8.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.22...v3.8.0-beta.23) (2023-11-24) + + +### Features + +* Merge Data Source ([#3788](https://github.com/OHIF/Viewers/issues/3788)) ([c4ff2c2](https://github.com/OHIF/Viewers/commit/c4ff2c2f09546ce8b72eab9c5e7beed611e3cab0)) + + + + + +# [3.8.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.21...v3.8.0-beta.22) (2023-11-21) + + +### Features + +* **events:** broadcast series summary metadata ([#3798](https://github.com/OHIF/Viewers/issues/3798)) ([404b0a5](https://github.com/OHIF/Viewers/commit/404b0a5d535182d1ae44e33f7232db500a7b2c16)) + + + + + +# [3.8.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.20...v3.8.0-beta.21) (2023-11-21) + + +### Bug Fixes + +* **DICOM Overlay:** The overlay data wasn't being refreshed on change ([#3793](https://github.com/OHIF/Viewers/issues/3793)) ([00e7519](https://github.com/OHIF/Viewers/commit/00e751933ac6d611a34773fa69594243f1b99082)) + + + + + +# [3.8.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.19...v3.8.0-beta.20) (2023-11-21) + + +### Bug Fixes + +* **metadata:** to handle cornerstone3D update for htj2k ([#3783](https://github.com/OHIF/Viewers/issues/3783)) ([8c8924a](https://github.com/OHIF/Viewers/commit/8c8924af373d906773f5db20defe38628cacd4a0)) + + + + + +# [3.8.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.18...v3.8.0-beta.19) (2023-11-18) + + +### Features + +* **docs:** Added various training videos to support the OHIF CLI tools ([#3794](https://github.com/OHIF/Viewers/issues/3794)) ([d83beb7](https://github.com/OHIF/Viewers/commit/d83beb7c62c1d5be19c54e08d23883f112147fe1)) + + + + + +# [3.8.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.17...v3.8.0-beta.18) (2023-11-15) + + +### Features + +* **url:** Add SeriesInstanceUIDs wado query param ([#3746](https://github.com/OHIF/Viewers/issues/3746)) ([b694228](https://github.com/OHIF/Viewers/commit/b694228dd535e4b97cb86a1dc085b6e8716bdaf3)) + + + + + +# [3.8.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.16...v3.8.0-beta.17) (2023-11-13) + + +### Bug Fixes + +* 🐛 Run error handler for failed image requests ([#3773](https://github.com/OHIF/Viewers/issues/3773)) ([3234014](https://github.com/OHIF/Viewers/commit/323401418e7ccab74655ba02f990bbe0ed4e523b)) + + + + + +# [3.8.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.15...v3.8.0-beta.16) (2023-11-13) + + +### Bug Fixes + +* **overlay:** Overlays aren't shown on undefined origin ([#3781](https://github.com/OHIF/Viewers/issues/3781)) ([fd1251f](https://github.com/OHIF/Viewers/commit/fd1251f751d8147b8a78c7f4d81c67ba69769afa)) + + + + + +# [3.8.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.14...v3.8.0-beta.15) (2023-11-10) + + +### Features + +* **dicomJSON:** Add Loading Other Display Sets and JSON Metadata Generation script ([#3777](https://github.com/OHIF/Viewers/issues/3777)) ([43b1c17](https://github.com/OHIF/Viewers/commit/43b1c17209502e4876ad59bae09ed9442eda8024)) + + + + + +# [3.8.0-beta.14](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.13...v3.8.0-beta.14) (2023-11-10) + + +### Bug Fixes + +* **path:** upgrade docusaurus for security ([#3780](https://github.com/OHIF/Viewers/issues/3780)) ([8bbcd0e](https://github.com/OHIF/Viewers/commit/8bbcd0e692e25917c1b6dd94a39fac834c812fca)) + + + + + +# [3.8.0-beta.13](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.12...v3.8.0-beta.13) (2023-11-09) + + +### Bug Fixes + +* **arrow:** ArrowAnnotate text key cause validation error ([#3771](https://github.com/OHIF/Viewers/issues/3771)) ([8af1046](https://github.com/OHIF/Viewers/commit/8af10468035f1f59e0a21e579d50ad63c8cbf7ad)) + + + + + +# [3.8.0-beta.12](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.11...v3.8.0-beta.12) (2023-11-08) + + +### Features + +* add VolumeViewport rotation ([#3776](https://github.com/OHIF/Viewers/issues/3776)) ([442f99d](https://github.com/OHIF/Viewers/commit/442f99d5eb2ceece7def20e14da59af1dd7d8442)) + + + + + +# [3.8.0-beta.11](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.10...v3.8.0-beta.11) (2023-11-08) + + +### Features + +* **hp callback:** Add viewport ready callback ([#3772](https://github.com/OHIF/Viewers/issues/3772)) ([bf252bc](https://github.com/OHIF/Viewers/commit/bf252bcec2aae3a00479fdcb732110b344bcf2c0)) + + + + + +# [3.8.0-beta.10](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.9...v3.8.0-beta.10) (2023-11-03) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.8.0-beta.9](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.8...v3.8.0-beta.9) (2023-11-02) + + +### Bug Fixes + +* **thumbnail:** Avoid multiple promise creations for thumbnails ([#3756](https://github.com/OHIF/Viewers/issues/3756)) ([b23eeff](https://github.com/OHIF/Viewers/commit/b23eeff93745769e67e60c33d75293d6242c5ec9)) + + + + + +# [3.8.0-beta.8](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.7...v3.8.0-beta.8) (2023-10-31) + + +### Features + +* **i18n:** enhanced i18n support ([#3730](https://github.com/OHIF/Viewers/issues/3730)) ([330e11c](https://github.com/OHIF/Viewers/commit/330e11c7ff0151e1096e19b8ffdae7d64cae280e)) + + + + + +# [3.8.0-beta.7](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.6...v3.8.0-beta.7) (2023-10-30) + + +### Bug Fixes + +* **measurement service:** Implemented correct check of schema keys in _isValidMeasurment. ([#3750](https://github.com/OHIF/Viewers/issues/3750)) ([db39585](https://github.com/OHIF/Viewers/commit/db395852b6fc6cd5c265a9282e5eee5bd6f951b7)) + + +### Features + +* **filters:** save worklist query filters to session storage so that they persist between navigation to the viewer and back ([#3749](https://github.com/OHIF/Viewers/issues/3749)) ([2a15ef0](https://github.com/OHIF/Viewers/commit/2a15ef0e44b7b4d8bbf5cb9363db6e523201c681)) + + + + + +# [3.8.0-beta.6](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.5...v3.8.0-beta.6) (2023-10-25) + + +### Bug Fixes + +* **toolbar:** allow customizable toolbar for active viewport and allow active tool to be deactivated via a click ([#3608](https://github.com/OHIF/Viewers/issues/3608)) ([dd6d976](https://github.com/OHIF/Viewers/commit/dd6d9768bbca1d3cc472e8c1e6d85822500b96ef)) + + + + + +# [3.8.0-beta.5](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.4...v3.8.0-beta.5) (2023-10-24) + + +### Bug Fixes + +* **sr:** dcm4chee requires the patient name for an SR to match what is in the original study ([#3739](https://github.com/OHIF/Viewers/issues/3739)) ([d98439f](https://github.com/OHIF/Viewers/commit/d98439fe7f3825076dbc87b664a1d1480ff414d3)) + + + + + +# [3.8.0-beta.4](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.3...v3.8.0-beta.4) (2023-10-23) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.8.0-beta.3](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.2...v3.8.0-beta.3) (2023-10-23) + + +### Bug Fixes + +* **recipes:** package.json script orthanc:up docker-compose path ([#3741](https://github.com/OHIF/Viewers/issues/3741)) ([49514ae](https://github.com/OHIF/Viewers/commit/49514aedfe0498b5bd505193106a9745a6a5b5e6)) + + + + + +# [3.8.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.1...v3.8.0-beta.2) (2023-10-19) + + +### Bug Fixes + +* **cine:** Use the frame rate specified in DICOM and optionally auto play cine ([#3735](https://github.com/OHIF/Viewers/issues/3735)) ([d9258ec](https://github.com/OHIF/Viewers/commit/d9258eca70587cf4dc18be4e56c79b16bae73d6d)) + + + + + +# [3.8.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.0...v3.8.0-beta.1) (2023-10-19) + + +### Bug Fixes + +* **calibration:** No calibration popup caused by perhaps an unused code optimization for production builds ([#3736](https://github.com/OHIF/Viewers/issues/3736)) ([93d798d](https://github.com/OHIF/Viewers/commit/93d798db99c0dee53ef73c376f8a74ac3049cf3f)) + + + + + +# [3.8.0-beta.0](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.110...v3.8.0-beta.0) (2023-10-12) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.7.0-beta.110](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.109...v3.7.0-beta.110) (2023-10-11) + + +### Bug Fixes + +* **display messages:** broken after timings ([#3719](https://github.com/OHIF/Viewers/issues/3719)) ([157b88c](https://github.com/OHIF/Viewers/commit/157b88c909d3289cb89ace731c1f9a19d40797ac)) + + + + + +# [3.7.0-beta.109](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.108...v3.7.0-beta.109) (2023-10-11) + + +### Bug Fixes + +* **export:** wrong export for the tmtv RT function ([#3715](https://github.com/OHIF/Viewers/issues/3715)) ([a3f2a1a](https://github.com/OHIF/Viewers/commit/a3f2a1a7b0d16bfcc0ecddc2ab731e54c5e377c8)) + + + + + +# [3.7.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.107...v3.7.0-beta.108) (2023-10-10) + + +### Bug Fixes + +* **i18n:** display set(s) are two words for English messages ([#3711](https://github.com/OHIF/Viewers/issues/3711)) ([c3a5847](https://github.com/OHIF/Viewers/commit/c3a5847dcd3dce4f1c8d8b11af95f79e3f93f70d)) + + + + + +# [3.7.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.106...v3.7.0-beta.107) (2023-10-10) + + +### Bug Fixes + +* **modules:** add stylus loader as an option to be uncommented ([#3710](https://github.com/OHIF/Viewers/issues/3710)) ([7c57f67](https://github.com/OHIF/Viewers/commit/7c57f67844b790fc6e47ac3f9708bf9d576389c8)) + + + + + +# [3.7.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.105...v3.7.0-beta.106) (2023-10-10) + + +### Bug Fixes + +* **segmentation:** Various fixes for segmentation mode and other ([#3709](https://github.com/OHIF/Viewers/issues/3709)) ([a9a6ad5](https://github.com/OHIF/Viewers/commit/a9a6ad50eae67b43b8b34efc07182d788cacdcfe)) + + + + + +# [3.7.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.104...v3.7.0-beta.105) (2023-10-10) + + +### Bug Fixes + +* **voi:** should publish voi change event on reset ([#3707](https://github.com/OHIF/Viewers/issues/3707)) ([52f34c6](https://github.com/OHIF/Viewers/commit/52f34c64d014f433ec1661a39b47e7fb27f15332)) + + + + + +# [3.7.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.103...v3.7.0-beta.104) (2023-10-09) + + +### Bug Fixes + +* **modality unit:** fix the modality unit per target via upgrade of cs3d ([#3706](https://github.com/OHIF/Viewers/issues/3706)) ([0a42d57](https://github.com/OHIF/Viewers/commit/0a42d573bbca7f2551a831a46d3aa6b56674a580)) + + + + + +# [3.7.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.102...v3.7.0-beta.103) (2023-10-09) + + +### Bug Fixes + +* **segmentation:** do not use SAB if not specified ([#3705](https://github.com/OHIF/Viewers/issues/3705)) ([4911e47](https://github.com/OHIF/Viewers/commit/4911e4796cef5e22cb7cc0ca73dc5c956bc75339)) + + + + + +# [3.7.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.101...v3.7.0-beta.102) (2023-10-06) + + +### Features + +* **Segmentation:** download RTSS from Labelmap([#3692](https://github.com/OHIF/Viewers/issues/3692)) ([40673f6](https://github.com/OHIF/Viewers/commit/40673f64b36b1150149c55632aa1825178a39e65)) + + + + + +# [3.7.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.100...v3.7.0-beta.101) (2023-10-06) + + +### Bug Fixes + +* **bugs:** fixing lots of bugs regarding release candidate ([#3700](https://github.com/OHIF/Viewers/issues/3700)) ([8bc12a3](https://github.com/OHIF/Viewers/commit/8bc12a37d0353160ae5ea4624dc0b244b7d59c07)) + + + + + +# [3.7.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.99...v3.7.0-beta.100) (2023-10-06) + + +### Bug Fixes + +* **segmentation scroll:** and hydration bugs ([#3701](https://github.com/OHIF/Viewers/issues/3701)) ([1fd98d9](https://github.com/OHIF/Viewers/commit/1fd98d922094d10fe0c6e9df726314ec9fce49e8)) + + + + + +# [3.7.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.98...v3.7.0-beta.99) (2023-10-04) + + +### Bug Fixes + +* **measurement and microscopy:** various small fixes for measurement and microscopy side panel ([#3696](https://github.com/OHIF/Viewers/issues/3696)) ([c1d5ee7](https://github.com/OHIF/Viewers/commit/c1d5ee7e3f7f4c0c6bed9ae81eba5519741c5155)) + + + + + +# [3.7.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.97...v3.7.0-beta.98) (2023-10-04) + + +### Features + +* **locale:** add German translations - community PR ([#3697](https://github.com/OHIF/Viewers/issues/3697)) ([ebe8f71](https://github.com/OHIF/Viewers/commit/ebe8f71da22c1d24b58f889c5d803951e19817b6)) + + + + + +# [3.7.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.96...v3.7.0-beta.97) (2023-10-04) + + +### Features + +* **locale:** Added Turkish language support (tr-TR) - Community PR ([#3695](https://github.com/OHIF/Viewers/issues/3695)) ([745050a](https://github.com/OHIF/Viewers/commit/745050a28ec7c2ef2e9a4d4e590040050b2177b2)) + + + + + +# [3.7.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.95...v3.7.0-beta.96) (2023-10-04) + + +### Bug Fixes + +* **translation:** Side panel translate fix ([#3156](https://github.com/OHIF/Viewers/issues/3156)) ([29748d4](https://github.com/OHIF/Viewers/commit/29748d46a14d23817dbe196e0f64363fc61a8aed)) + + + + + +# [3.7.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.94...v3.7.0-beta.95) (2023-10-04) + + +### Bug Fixes + +* **cli:** Add npm packaged mode not working ([#3689](https://github.com/OHIF/Viewers/issues/3689)) ([28cec04](https://github.com/OHIF/Viewers/commit/28cec04ff43b81e218c3e9addef4665b3833a6fe)) + + + + + +# [3.7.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.93...v3.7.0-beta.94) (2023-10-03) + + +### Features + +* **debug:** Add timing information about time to first image/all images, and query time ([#3681](https://github.com/OHIF/Viewers/issues/3681)) ([108383b](https://github.com/OHIF/Viewers/commit/108383b9ef51e4bef82d9c932b9bc7aa5354e799)) + + + + + +# [3.7.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.92...v3.7.0-beta.93) (2023-10-03) + + +### Features + +* **displayArea:** add display area to hanging protocol ([#3691](https://github.com/OHIF/Viewers/issues/3691)) ([5e7fe91](https://github.com/OHIF/Viewers/commit/5e7fe91617d7399f85702d82e7bfa028b8010a89)) + + + + + +# [3.7.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.91...v3.7.0-beta.92) (2023-10-03) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.7.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.90...v3.7.0-beta.91) (2023-10-03) + + +### Bug Fixes + +* **editing:** regression bug in disable editing ([#3687](https://github.com/OHIF/Viewers/issues/3687)) ([4dc2acd](https://github.com/OHIF/Viewers/commit/4dc2acdefa872dd1d8df47f465e9e9656f95f67f)) + + + + + +# [3.7.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.89...v3.7.0-beta.90) (2023-10-03) + + +### Bug Fixes + +* **typescript error:** Change pubSubServiceInterface file type to typescript ([#3546](https://github.com/OHIF/Viewers/issues/3546)) ([eb22328](https://github.com/OHIF/Viewers/commit/eb22328fc05d06fc4411805e7a30f826659d796a)) + + + + + +# [3.7.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.88...v3.7.0-beta.89) (2023-10-03) + + +### Bug Fixes + +* **dicom overlay:** Handle special cases of ArrayBuffer for various DICOM overlay attributes. ([#3684](https://github.com/OHIF/Viewers/issues/3684)) ([e36a604](https://github.com/OHIF/Viewers/commit/e36a6043315e900eeb6ce183772c7f852f478e96)) +* **StackSync:** Miscellaneous fixes for stack image sync ([#3663](https://github.com/OHIF/Viewers/issues/3663)) ([8a335bd](https://github.com/OHIF/Viewers/commit/8a335bd03d14ba87d65d7468d93f74040aa828d9)) + + + + + +# [3.7.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.87...v3.7.0-beta.88) (2023-10-03) + + +### Bug Fixes + +* **config:** support more values for the useSharedArrayBuffer ([#3688](https://github.com/OHIF/Viewers/issues/3688)) ([1129c15](https://github.com/OHIF/Viewers/commit/1129c155d2c7d46c98a5df7c09879aa3d459fa7e)) + + + + + +# [3.7.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.86...v3.7.0-beta.87) (2023-09-29) + + +### Bug Fixes + +* **no sab:** should work when shared array buffer is not required ([#3686](https://github.com/OHIF/Viewers/issues/3686)) ([a67d72d](https://github.com/OHIF/Viewers/commit/a67d72de85238b369a18c010bf6d147daefc6df5)) + + + + + +# [3.7.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.85...v3.7.0-beta.86) (2023-09-29) + + +### Bug Fixes + +* **cli:** various fixes for adding custom modes and extensions ([#3683](https://github.com/OHIF/Viewers/issues/3683)) ([dc73b18](https://github.com/OHIF/Viewers/commit/dc73b187484da029a2664bb1302f30137c973b8c)) + + + + + +# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26) + + +### Bug Fixes + +* **toggleOneUp:** fixed one up for main tmtv layout ([#3677](https://github.com/OHIF/Viewers/issues/3677)) ([86f54d0](https://github.com/OHIF/Viewers/commit/86f54d0d07042750a863ae876aa8dd5fb16029a5)) + + + + + +# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22) + + +### Bug Fixes + +* **react-select:** update react select package ([#3622](https://github.com/OHIF/Viewers/issues/3622)) ([04ca10d](https://github.com/OHIF/Viewers/commit/04ca10d8779dd15454920002f3d48afa8830de8a)) + + +### Features + +* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1)) +* **SidePanel:** new side panel tab look-and-feel ([#3657](https://github.com/OHIF/Viewers/issues/3657)) ([85c899b](https://github.com/OHIF/Viewers/commit/85c899b399e2521480724be145538993721b9378)) + + + + + +# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22) + + +### Performance Improvements + +* **memory:** add 16 bit texture via configuration - reduces memory by half ([#3662](https://github.com/OHIF/Viewers/issues/3662)) ([2bd3b26](https://github.com/OHIF/Viewers/commit/2bd3b26a6aa54b211ef988f3ad64ef1fe5648bab)) + + + + + +# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21) + + +### Bug Fixes + +* **mpr:** Return the original/raw hanging protocol when fetching and preserving the current active protocol. ([#3670](https://github.com/OHIF/Viewers/issues/3670)) ([221dedd](https://github.com/OHIF/Viewers/commit/221dedde5dd4df086276406a9fa2da1cc23b4eb1)) + + + + + +# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19) + + +### Bug Fixes + +* **keyCloak:** fix openresty keycloak deployment recipe ([#3655](https://github.com/OHIF/Viewers/issues/3655)) ([2d7721c](https://github.com/OHIF/Viewers/commit/2d7721cb581f55dc49e3baeca2411b18dd78ad74)) + + + + + +# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18) + + +### Bug Fixes + +* **DicomJson:** retrieve.series.metadata method should be async ([#3659](https://github.com/OHIF/Viewers/issues/3659)) ([2737903](https://github.com/OHIF/Viewers/commit/2737903386cf97399473e0fa64fe53ad14da155a)) + + + + + +# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15) + + +### Bug Fixes + +* **measurements:** Update the calibration tool to match changes in CS3D ([#3505](https://github.com/OHIF/Viewers/issues/3505)) ([38af311](https://github.com/OHIF/Viewers/commit/38af3112ec1f94f36c0ef64ff1cf9d21c0981c81)) + + + + + +# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12) + + +### Bug Fixes + +* **health imaging:** studies not loading from healthimaging if imagepositionpatient is missing ([#3646](https://github.com/OHIF/Viewers/issues/3646)) ([74e62a1](https://github.com/OHIF/Viewers/commit/74e62a176374f720080d4e777972f70e7f2d8b2b)) + + + + + +# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12) + + +### Bug Fixes + +* **suv:** import calculate-suv library version that prevents SUV calculation for a zero PatientWeight ([#3638](https://github.com/OHIF/Viewers/issues/3638)) ([0d10f46](https://github.com/OHIF/Viewers/commit/0d10f46b885fe54ec3dae1848134da658eb6280a)) + + + + + +# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06) + + +### Bug Fixes + +* **hotkeys:** preserve hotkeys if changed, and reduce re-rendering ([#3635](https://github.com/OHIF/Viewers/issues/3635)) ([94f7cfb](https://github.com/OHIF/Viewers/commit/94f7cfb08e3490488394efc42ef089ebe55e86be)) + + + + + +# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06) + + +### Features + +* **ImageOverlayViewerTool:** add ImageOverlayViewer tool that can render image overlay (pixel overlay) of the DICOM images ([#3163](https://github.com/OHIF/Viewers/issues/3163)) ([69115da](https://github.com/OHIF/Viewers/commit/69115da06d2d437b57e66608b435bb0bc919a90f)) + + + + + +# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05) + + +### Bug Fixes + +* **nginx archive recipe:** Fixes to various configuration files. ([#3624](https://github.com/OHIF/Viewers/issues/3624)) ([3ce7225](https://github.com/OHIF/Viewers/commit/3ce72254b390f32c9aa207a0589e688805e2659d)) + + + + + +# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01) + + +### Features + +* **grid:** remove viewportIndex and only rely on viewportId ([#3591](https://github.com/OHIF/Viewers/issues/3591)) ([4c6ff87](https://github.com/OHIF/Viewers/commit/4c6ff873e887cc30ffc09223f5cb99e5f94c9cdd)) + + + + + +# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30) + + +### Features + +* **data source UI config:** Popup the configuration dialogue whenever a data source is not fully configured ([#3620](https://github.com/OHIF/Viewers/issues/3620)) ([adedc8c](https://github.com/OHIF/Viewers/commit/adedc8c382e18a2e86a569e3d023cc55a157363f)) + + + + + +# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29) + + +### Bug Fixes + +* **OpenIdConnectRoutes:** fix handleUnauthenticated ([#3617](https://github.com/OHIF/Viewers/issues/3617)) ([35fc30c](https://github.com/OHIF/Viewers/commit/35fc30c5359d8199cc38ffa670c08687d2672f11)) + + + + + +# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29) + + +### Bug Fixes + +* **PT Metadata:** Allow for PatientWeight to be missing from the metadata ([#3621](https://github.com/OHIF/Viewers/issues/3621)) ([44f101d](https://github.com/OHIF/Viewers/commit/44f101d3f2b3204b67e31f4e4939062e65a246ee)) + + + + + +# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29) + +**Note:** Version bump only for package ohif-monorepo-root + + + + + +# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25) + + +### Features + +* **cloud data source config:** GUI and API for configuring a cloud data source with Google cloud healthcare implementation ([#3589](https://github.com/OHIF/Viewers/issues/3589)) ([a336992](https://github.com/OHIF/Viewers/commit/a336992971c07552c9dbb6e1de43169d37762ef1)) + + + + + +# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23) + + +### Bug Fixes + +* **memory leak:** array buffer was sticking around in volume viewports ([#3611](https://github.com/OHIF/Viewers/issues/3611)) ([65b49ae](https://github.com/OHIF/Viewers/commit/65b49aeb1b5f38224e4892bdf32453500ee351f8)) diff --git a/Dockerfile b/Dockerfile index 0f458dced61..e6310c3ba34 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ # Stage 1: Build the application # docker build -t ohif/viewer:latest . -FROM node:16.15.0-slim as json-copier +FROM node:18.16.1-slim as json-copier RUN mkdir /usr/src/app WORKDIR /usr/src/app @@ -37,7 +37,8 @@ COPY platform /usr/src/app/platform #RUN find platform \! -name "package.json" -mindepth 2 -maxdepth 2 -print | xargs rm -rf # Copy Files -FROM node:16.15.0-slim as builder +FROM node:18.16.1-slim as builder +RUN apt-get update && apt-get install -y build-essential python3 RUN mkdir /usr/src/app WORKDIR /usr/src/app diff --git a/README.md b/README.md index 109d19acb33..5befea75c47 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,10 @@ provided by the Open Health Imaging Foundation (OHIF +
+ 📰 Join OHIF Newsletter 📰 +
+
@@ -249,7 +253,7 @@ or, for v1, please cite: > [10.1158/0008-5472.CAN-17-0334](https://www.doi.org/10.1158/0008-5472.CAN-17-0334) **Note:** If you use or find this repository helpful, please take the time to -star this repository on Github. This is an easy way for us to assess adoption +star this repository on GitHub. This is an easy way for us to assess adoption and it can help us obtain future funding for the project. This work is supported primarily by the National Institutes of Health, National @@ -257,7 +261,11 @@ Cancer Institute, Informatics Technology for Cancer Research (ITCR) program, under a [grant to Dr. Gordon Harris at Massachusetts General Hospital (U24 CA199460)](https://projectreporter.nih.gov/project_info_description.cfm?aid=8971104). -This project is tested with BrowserStack. Thank you for supporting open source +[NCI Imaging Data Commons (IDC) project](https://imaging.datacommons.cancer.gov/) supported the development of new features and bug fixes marked with ["IDC:priority"](https://github.com/OHIF/Viewers/issues?q=is%3Aissue+is%3Aopen+label%3AIDC%3Apriority), +["IDC:candidate"](https://github.com/OHIF/Viewers/issues?q=is%3Aissue+is%3Aopen+label%3AIDC%3Acandidate) or ["IDC:collaboration"](https://github.com/OHIF/Viewers/issues?q=is%3Aissue+is%3Aopen+label%3AIDC%3Acollaboration). NCI Imaging Data Commons is supported by contract number 19X037Q from +Leidos Biomedical Research under Task Order HHSN26100071 from NCI. [IDC Viewer](https://learn.canceridc.dev/portal/visualization) is a customized version of the OHIF Viewer. + +This project is tested with BrowserStack. Thank you for supporting open-source! ## License diff --git a/babel.config.js b/babel.config.js index 1b20bbf6b1a..9fbd804637a 100644 --- a/babel.config.js +++ b/babel.config.js @@ -3,11 +3,7 @@ const { extendDefaultPlugins } = require('svgo'); module.exports = { babelrcRoots: ['./platform/*', './extensions/*', './modes/*'], - presets: [ - '@babel/preset-env', - '@babel/preset-react', - '@babel/preset-typescript', - ], + presets: ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'], plugins: [ [ 'inline-react-svg', @@ -30,6 +26,7 @@ module.exports = { '@babel/plugin-transform-typescript', ['@babel/plugin-proposal-private-property-in-object', { loose: true }], ['@babel/plugin-proposal-private-methods', { loose: true }], + '@babel/plugin-transform-class-static-block', ], env: { test: { @@ -49,6 +46,7 @@ module.exports = { '@babel/plugin-proposal-object-rest-spread', '@babel/plugin-syntax-dynamic-import', '@babel/plugin-transform-regenerator', + '@babel/transform-destructuring', '@babel/plugin-transform-runtime', '@babel/plugin-transform-typescript', ], diff --git a/commit.txt b/commit.txt index 5fc676cc174..91ed542df2f 100644 --- a/commit.txt +++ b/commit.txt @@ -1 +1 @@ -b55518e08ec9c9ab23e74aa4987927d8d75ae909 \ No newline at end of file +f7fe91c5f6c4f05f3f3f5f640d3a119bd40a5870 \ No newline at end of file diff --git a/extensions/_example/src/index.js b/extensions/_example/src/index.js deleted file mode 100644 index c4fdb9eb1cd..00000000000 --- a/extensions/_example/src/index.js +++ /dev/null @@ -1,138 +0,0 @@ -import ImageSet from '@ohif/core/src/classes/ImageSet'; -import { IWebApiDataSource } from '@ohif/core'; - -/** - * - */ -export default { - id: '@ohif/extension-*', - - /** - * LIFECYCLE HOOKS - */ - preRegistration() {}, - beforeExtInit() {}, - beforeExtDestroy() {}, - - /** - * MODULES - */ - getCommandsModule, - getContextModule, - getDataSourcesModule, - getLayoutTemplateModule, - getPanelModule, - getSopClassHandlerModule, - getToolbarModule() {}, - getViewportModule, -}; - -// appConfig, -// extensionConfig, -// dataSources, -// servicesManager, -// extensionManager, -// commandsManager, - -/** - * - */ -const getCommandsModule = () => ({ - definitions: { - exampleActionDef: { - commandFn: ({ param1 }) => { - console.log(`param1's value is: ${param1}`); - }, - // storeContexts: ['viewports'], - options: { param1: 'hello world' }, - context: 'VIEWER', // optional - }, - }, - defaultContext: 'ACTIVE_VIEWPORT::DICOMSR', -}); - -const ExampleContext = React.createContext(); - -function ExampleContextProvider({ children }) { - return ( - - {children} - - ); -} - -const getContextModule = () => [ - { - name: 'ExampleContext', - context: ExampleContext, - provider: ExampleContextProvider, - }, -]; - -const getDataSourcesModule = () => [ - { - name: 'exampleDataSource', - type: 'webApi', // 'webApi' | 'local' | 'other' - createDataSource: dataSourceConfig => { - return IWebApiDataSource.create(/* */); - }, - }, -]; - -const getLayoutTemplateModule = (/* ... */) => [ - { - id: 'exampleLayout', - name: 'exampleLayout', - component: ExampleLayoutComponent, - }, -]; - -const getPanelModule = () => { - return [ - { - name: 'exampleSidePanel', - iconName: 'info-circle-o', - iconLabel: 'Example', - label: 'Hello World', - isDisabled: studies => {}, // optional - component: ExamplePanelContentComponent, - }, - ]; -}; - -const getSopClassHandlerModule = (/* ... */) => { - const BASIC_TEXT_SR = '1.2.840.10008.5.1.4.1.1.88.11'; - - return [ - { - name: 'ExampleSopClassHandle', - sopClassUids: [BASIC_TEXT_SR], - getDisplaySetsFromSeries: instances => { - const imageSet = new ImageSet(instances); - - imageSet.setAttributes(/** */); - imageSet.sortBy((a, b) => 0); - - return imageSet; - }, - }, - ]; -}; - -const getToolbarModule = () => {}; - -// displaySet, viewportIndex, dataSource -const getViewportModule = () => { - const wrappedViewport = props => { - return ( - { - commandsManager.runCommand('commandName', data); - }} - /> - ); - }; - - return [{ name: 'example', component: wrappedViewport }]; -}; diff --git a/extensions/cornerstone-dicom-rt/.webpack/webpack.prod.js b/extensions/cornerstone-dicom-rt/.webpack/webpack.prod.js index 5182c4a6a98..4ee5cc9fc1c 100644 --- a/extensions/cornerstone-dicom-rt/.webpack/webpack.prod.js +++ b/extensions/cornerstone-dicom-rt/.webpack/webpack.prod.js @@ -3,8 +3,7 @@ const { merge } = require('webpack-merge'); const path = require('path'); const webpackCommon = require('./../../../.webpack/webpack.base.js'); const pkg = require('./../package.json'); -const BundleAnalyzerPlugin = require('webpack-bundle-analyzer') - .BundleAnalyzerPlugin; +const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; const ROOT_DIR = path.join(__dirname, './..'); const SRC_DIR = path.join(__dirname, '../src'); @@ -38,13 +37,7 @@ module.exports = (env, argv) => { libraryTarget: 'umd', filename: pkg.main, }, - externals: [ - /\b(vtk.js)/, - /\b(dcmjs)/, - /\b(gl-matrix)/, - /^@ohif/, - /^@cornerstonejs/, - ], + externals: [/\b(vtk.js)/, /\b(dcmjs)/, /\b(gl-matrix)/, /^@ohif/, /^@cornerstonejs/], plugins: [ new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1, diff --git a/extensions/cornerstone-dicom-rt/CHANGELOG.md b/extensions/cornerstone-dicom-rt/CHANGELOG.md new file mode 100644 index 00000000000..41cad75f179 --- /dev/null +++ b/extensions/cornerstone-dicom-rt/CHANGELOG.md @@ -0,0 +1,938 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [3.8.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.59...v3.8.0-beta.60) (2024-03-15) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.58...v3.8.0-beta.59) (2024-03-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.57...v3.8.0-beta.58) (2024-03-05) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.56...v3.8.0-beta.57) (2024-02-28) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.55...v3.8.0-beta.56) (2024-02-22) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.54...v3.8.0-beta.55) (2024-02-21) + + +### Features + +* **resize:** Optimize resizing process and maintain zoom level ([#3889](https://github.com/OHIF/Viewers/issues/3889)) ([b3a0faf](https://github.com/OHIF/Viewers/commit/b3a0faf5f5f0a1993b2b017eb4cc1216164ea2c6)) + + + + + +# [3.8.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.53...v3.8.0-beta.54) (2024-02-14) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.52...v3.8.0-beta.53) (2024-02-05) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.51...v3.8.0-beta.52) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.50...v3.8.0-beta.51) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.49...v3.8.0-beta.50) (2024-01-22) + + +### Bug Fixes + +* **viewport-sync:** remember synced viewports bw stack and volume and RENAME StackImageSync to ImageSliceSync ([#3849](https://github.com/OHIF/Viewers/issues/3849)) ([e4a116b](https://github.com/OHIF/Viewers/commit/e4a116b074fcb85c8cbcc9db44fdec565f3386db)) + + + + + +# [3.8.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.48...v3.8.0-beta.49) (2024-01-19) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.47...v3.8.0-beta.48) (2024-01-17) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.46...v3.8.0-beta.47) (2024-01-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.45...v3.8.0-beta.46) (2024-01-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.44...v3.8.0-beta.45) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.43...v3.8.0-beta.44) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.42...v3.8.0-beta.43) (2024-01-09) + + +### Bug Fixes + +* **segmentation:** upgrade cs3d to fix various segmentation bugs ([#3885](https://github.com/OHIF/Viewers/issues/3885)) ([b1efe40](https://github.com/OHIF/Viewers/commit/b1efe40aa146e4052cc47b3f774cabbb47a8d1a6)) + + + + + +# [3.8.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.41...v3.8.0-beta.42) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.40...v3.8.0-beta.41) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.39...v3.8.0-beta.40) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.38...v3.8.0-beta.39) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.37...v3.8.0-beta.38) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.36...v3.8.0-beta.37) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.35...v3.8.0-beta.36) (2023-12-15) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.34...v3.8.0-beta.35) (2023-12-14) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.33...v3.8.0-beta.34) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.32...v3.8.0-beta.33) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.31...v3.8.0-beta.32) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.30...v3.8.0-beta.31) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.29...v3.8.0-beta.30) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.28...v3.8.0-beta.29) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.27...v3.8.0-beta.28) (2023-12-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.26...v3.8.0-beta.27) (2023-12-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.25...v3.8.0-beta.26) (2023-11-28) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.24...v3.8.0-beta.25) (2023-11-27) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.23...v3.8.0-beta.24) (2023-11-24) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.22...v3.8.0-beta.23) (2023-11-24) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.21...v3.8.0-beta.22) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.20...v3.8.0-beta.21) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.19...v3.8.0-beta.20) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.18...v3.8.0-beta.19) (2023-11-18) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.17...v3.8.0-beta.18) (2023-11-15) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.16...v3.8.0-beta.17) (2023-11-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.15...v3.8.0-beta.16) (2023-11-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.14...v3.8.0-beta.15) (2023-11-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.14](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.13...v3.8.0-beta.14) (2023-11-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.13](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.12...v3.8.0-beta.13) (2023-11-09) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.12](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.11...v3.8.0-beta.12) (2023-11-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.11](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.10...v3.8.0-beta.11) (2023-11-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.10](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.9...v3.8.0-beta.10) (2023-11-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.9](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.8...v3.8.0-beta.9) (2023-11-02) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.8](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.7...v3.8.0-beta.8) (2023-10-31) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.7](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.6...v3.8.0-beta.7) (2023-10-30) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.6](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.5...v3.8.0-beta.6) (2023-10-25) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.5](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.4...v3.8.0-beta.5) (2023-10-24) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.4](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.3...v3.8.0-beta.4) (2023-10-23) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.3](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.2...v3.8.0-beta.3) (2023-10-23) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.1...v3.8.0-beta.2) (2023-10-19) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.0...v3.8.0-beta.1) (2023-10-19) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.8.0-beta.0](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.110...v3.8.0-beta.0) (2023-10-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.110](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.109...v3.7.0-beta.110) (2023-10-11) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.109](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.108...v3.7.0-beta.109) (2023-10-11) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.107...v3.7.0-beta.108) (2023-10-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.106...v3.7.0-beta.107) (2023-10-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.105...v3.7.0-beta.106) (2023-10-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.104...v3.7.0-beta.105) (2023-10-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.103...v3.7.0-beta.104) (2023-10-09) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.102...v3.7.0-beta.103) (2023-10-09) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.101...v3.7.0-beta.102) (2023-10-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.100...v3.7.0-beta.101) (2023-10-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.99...v3.7.0-beta.100) (2023-10-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.98...v3.7.0-beta.99) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.97...v3.7.0-beta.98) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.96...v3.7.0-beta.97) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.95...v3.7.0-beta.96) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.94...v3.7.0-beta.95) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.93...v3.7.0-beta.94) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.92...v3.7.0-beta.93) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.91...v3.7.0-beta.92) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.90...v3.7.0-beta.91) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.89...v3.7.0-beta.90) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.88...v3.7.0-beta.89) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.87...v3.7.0-beta.88) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.86...v3.7.0-beta.87) (2023-09-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.85...v3.7.0-beta.86) (2023-09-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22) + + +### Features + +* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1)) + + + + + +# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01) + + +### Features + +* **grid:** remove viewportIndex and only rely on viewportId ([#3591](https://github.com/OHIF/Viewers/issues/3591)) ([4c6ff87](https://github.com/OHIF/Viewers/commit/4c6ff873e887cc30ffc09223f5cb99e5f94c9cdd)) + + + + + +# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt + + + + + +# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25) + + +### Features + +* **cloud data source config:** GUI and API for configuring a cloud data source with Google cloud healthcare implementation ([#3589](https://github.com/OHIF/Viewers/issues/3589)) ([a336992](https://github.com/OHIF/Viewers/commit/a336992971c07552c9dbb6e1de43169d37762ef1)) + + + + + +# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-rt diff --git a/extensions/cornerstone-dicom-rt/babel.config.js b/extensions/cornerstone-dicom-rt/babel.config.js index 92fbbdeaf95..a38ddda2127 100644 --- a/extensions/cornerstone-dicom-rt/babel.config.js +++ b/extensions/cornerstone-dicom-rt/babel.config.js @@ -10,7 +10,7 @@ module.exports = { modules: 'commonjs', debug: false, }, - "@babel/preset-typescript", + '@babel/preset-typescript', ], '@babel/preset-react', ], @@ -26,7 +26,7 @@ module.exports = { // WebPack handles ES6 --> Target Syntax ['@babel/preset-env', { modules: false }], '@babel/preset-react', - "@babel/preset-typescript", + '@babel/preset-typescript', ], ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'], }, @@ -35,7 +35,7 @@ module.exports = { // WebPack handles ES6 --> Target Syntax ['@babel/preset-env', { modules: false }], '@babel/preset-react', - "@babel/preset-typescript", + '@babel/preset-typescript', ], plugins: ['react-hot-loader/babel'], ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'], diff --git a/extensions/cornerstone-dicom-rt/package.json b/extensions/cornerstone-dicom-rt/package.json index 767e49276bb..530f8ce67f4 100644 --- a/extensions/cornerstone-dicom-rt/package.json +++ b/extensions/cornerstone-dicom-rt/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-cornerstone-dicom-rt", - "version": "3.7.0-beta.46", + "version": "3.8.0-beta.60", "description": "DICOM RT read workflow", "author": "OHIF", "license": "MIT", @@ -24,6 +24,8 @@ "yarn": ">=1.18.0" }, "scripts": { + "clean": "shx rm -rf dist", + "clean:deep": "yarn run clean && shx rm -rf node_modules", "dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --output-pathinfo", "dev:dicom-seg": "yarn run dev", "build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js", @@ -31,10 +33,10 @@ "start": "yarn run dev" }, "peerDependencies": { - "@ohif/core": "3.7.0-beta.46", - "@ohif/extension-cornerstone": "3.7.0-beta.46", - "@ohif/extension-default": "3.7.0-beta.46", - "@ohif/i18n": "3.7.0-beta.46", + "@ohif/core": "3.8.0-beta.60", + "@ohif/extension-cornerstone": "3.8.0-beta.60", + "@ohif/extension-default": "3.8.0-beta.60", + "@ohif/i18n": "3.8.0-beta.60", "prop-types": "^15.6.2", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/extensions/cornerstone-dicom-rt/src/getSopClassHandlerModule.js b/extensions/cornerstone-dicom-rt/src/getSopClassHandlerModule.js index 81ba6aab99b..a7a232f1985 100644 --- a/extensions/cornerstone-dicom-rt/src/getSopClassHandlerModule.js +++ b/extensions/cornerstone-dicom-rt/src/getSopClassHandlerModule.js @@ -7,11 +7,7 @@ const sopClassUids = ['1.2.840.10008.5.1.4.1.1.481.3']; let loadPromises = {}; -function _getDisplaySetsFromSeries( - instances, - servicesManager, - extensionManager -) { +function _getDisplaySetsFromSeries(instances, servicesManager, extensionManager) { const instance = instances[0]; const { @@ -56,10 +52,7 @@ function _getDisplaySetsFromSeries( }; let referencedSeriesSequence = instance.ReferencedSeriesSequence; - if ( - instance.ReferencedFrameOfReferenceSequence && - !instance.ReferencedSeriesSequence - ) { + if (instance.ReferencedFrameOfReferenceSequence && !instance.ReferencedSeriesSequence) { instance.ReferencedSeriesSequence = _deriveReferencedSeriesSequenceFromFrameOfReferenceSequence( instance.ReferencedFrameOfReferenceSequence ); @@ -72,8 +65,7 @@ function _getDisplaySetsFromSeries( const referencedSeries = referencedSeriesSequence[0]; - displaySet.referencedImages = - instance.ReferencedSeriesSequence.ReferencedInstanceSequence; + displaySet.referencedImages = instance.ReferencedSeriesSequence.ReferencedInstanceSequence; displaySet.referencedSeriesInstanceUID = referencedSeries.SeriesInstanceUID; displaySet.getReferenceDisplaySet = () => { @@ -88,14 +80,12 @@ function _getDisplaySetsFromSeries( const referencedDisplaySet = referencedDisplaySets[0]; - displaySet.referencedDisplaySetInstanceUID = - referencedDisplaySet.displaySetInstanceUID; + displaySet.referencedDisplaySetInstanceUID = referencedDisplaySet.displaySetInstanceUID; return referencedDisplaySet; }; - displaySet.load = ({ headers }) => - _load(displaySet, servicesManager, extensionManager, headers); + displaySet.load = ({ headers }) => _load(displaySet, servicesManager, extensionManager, headers); return [displaySet]; } @@ -194,11 +184,7 @@ function getSopClassHandlerModule({ servicesManager, extensionManager }) { name: 'dicom-rt', sopClassUids, getDisplaySetsFromSeries: instances => { - return _getDisplaySetsFromSeries( - instances, - servicesManager, - extensionManager - ); + return _getDisplaySetsFromSeries(instances, servicesManager, extensionManager); }, }, ]; diff --git a/extensions/cornerstone-dicom-rt/src/index.tsx b/extensions/cornerstone-dicom-rt/src/index.tsx index e444b070892..953c9a7c7da 100644 --- a/extensions/cornerstone-dicom-rt/src/index.tsx +++ b/extensions/cornerstone-dicom-rt/src/index.tsx @@ -2,12 +2,9 @@ import { id } from './id'; import React from 'react'; import { Types } from '@ohif/core'; import getSopClassHandlerModule from './getSopClassHandlerModule'; -import hydrateRTDisplaySet from './utils/_hydrateRT'; const Component = React.lazy(() => { - return import( - /* webpackPrefetch: true */ './viewports/OHIFCornerstoneRTViewport' - ); + return import(/* webpackPrefetch: true */ './viewports/OHIFCornerstoneRTViewport'); }); const OHIFCornerstoneRTViewport = props => { @@ -62,4 +59,3 @@ const extension: Types.Extensions.Extension = { }; export default extension; -export { hydrateRTDisplaySet }; diff --git a/extensions/cornerstone-dicom-rt/src/loadRTStruct.js b/extensions/cornerstone-dicom-rt/src/loadRTStruct.js index 25378170081..cf58eb40a2e 100644 --- a/extensions/cornerstone-dicom-rt/src/loadRTStruct.js +++ b/extensions/cornerstone-dicom-rt/src/loadRTStruct.js @@ -25,23 +25,13 @@ async function checkAndLoadContourData(instance, datasource) { if (Array.isArray(contourData)) { promisesMap.has(referencedROINumber) - ? promisesMap - .get(referencedROINumber) - .push(Promise.resolve(contourData)) - : promisesMap.set(referencedROINumber, [ - Promise.resolve(contourData), - ]); + ? promisesMap.get(referencedROINumber).push(Promise.resolve(contourData)) + : promisesMap.set(referencedROINumber, [Promise.resolve(contourData)]); } else if (contourData && contourData.BulkDataURI) { const bulkDataURI = contourData.BulkDataURI; - if ( - !datasource || - !datasource.retrieve || - !datasource.retrieve.bulkDataURI - ) { - return Promise.reject( - 'Invalid datasource object or retrieve function' - ); + if (!datasource || !datasource.retrieve || !datasource.retrieve.bulkDataURI) { + return Promise.reject('Invalid datasource object or retrieve function'); } const bulkDataPromise = datasource.retrieve.bulkDataURI({ @@ -74,10 +64,7 @@ async function checkAndLoadContourData(instance, datasource) { ROIContour.ContourSequence.forEach((Contour, index) => { const promise = resolvedPromises[index]; if (promise.status === 'fulfilled') { - if ( - Array.isArray(promise.value) && - promise.value.every(Number.isFinite) - ) { + if (Array.isArray(promise.value) && promise.value.every(Number.isFinite)) { // If promise.value is already an array of numbers, use it directly Contour.ContourData = promise.value; } else { @@ -85,13 +72,8 @@ async function checkAndLoadContourData(instance, datasource) { const uint8Array = new Uint8Array(promise.value); const textDecoder = new TextDecoder(); const dataUint8Array = textDecoder.decode(uint8Array); - if ( - typeof dataUint8Array === 'string' && - dataUint8Array.includes('\\') - ) { - Contour.ContourData = dataUint8Array - .split('\\') - .map(parseFloat); + if (typeof dataUint8Array === 'string' && dataUint8Array.includes('\\')) { + Contour.ContourData = dataUint8Array.split('\\').map(parseFloat); } else { Contour.ContourData = []; } @@ -120,9 +102,8 @@ export default async function loadRTStruct( const { bulkDataURI } = dataSource.getConfig?.() || {}; const { dicomLoaderService } = utilityModule.exports; - const imageIdSopInstanceUidPairs = _getImageIdSopInstanceUidPairsForDisplaySet( - referencedDisplaySet - ); + const imageIdSopInstanceUidPairs = + _getImageIdSopInstanceUidPairsForDisplaySet(referencedDisplaySet); // Set here is loading is asynchronous. // If this function throws its set back to false. @@ -137,20 +118,14 @@ export default async function loadRTStruct( ); const dicomData = DicomMessage.readFile(segArrayBuffer); - const rtStructDataset = DicomMetaDictionary.naturalizeDataset( - dicomData.dict - ); + const rtStructDataset = DicomMetaDictionary.naturalizeDataset(dicomData.dict); rtStructDataset._meta = DicomMetaDictionary.namifyDataset(dicomData.meta); instance = rtStructDataset; } else { await checkAndLoadContourData(instance, dataSource); } - const { - StructureSetROISequence, - ROIContourSequence, - RTROIObservationsSequence, - } = instance; + const { StructureSetROISequence, ROIContourSequence, RTROIObservationsSequence } = instance; // Define our structure set entry and add it to the rtstruct module state. const structureSet = { @@ -174,12 +149,8 @@ export default async function loadRTStruct( const contourPoints = []; for (let c = 0; c < ContourSequenceArray.length; c++) { - const { - ContourImageSequence, - ContourData, - NumberOfContourPoints, - ContourGeometricType, - } = ContourSequenceArray[c]; + const { ContourImageSequence, ContourData, NumberOfContourPoints, ContourGeometricType } = + ContourSequenceArray[c]; let isSupported = false; @@ -229,9 +200,7 @@ const _getImageId = (imageIdSopInstanceUidPairs, sopInstanceUID) => { imageIdSopInstanceUidPairsEntry.sopInstanceUID === sopInstanceUID ); - return imageIdSopInstanceUidPairsEntry - ? imageIdSopInstanceUidPairsEntry.imageId - : null; + return imageIdSopInstanceUidPairsEntry ? imageIdSopInstanceUidPairsEntry.imageId : null; }; function _getImageIdSopInstanceUidPairsForDisplaySet(referencedDisplaySet) { @@ -252,8 +221,7 @@ function _setROIContourMetadata( isSupported ) { const StructureSetROI = StructureSetROISequence.find( - structureSetROI => - structureSetROI.ROINumber === ROIContour.ReferencedROINumber + structureSetROI => structureSetROI.ROINumber === ROIContour.ReferencedROINumber ); const ROIContourData = { @@ -293,23 +261,15 @@ function _setROIContourDataColor(ROIContour, ROIContourData) { } } -function _setROIContourRTROIObservations( - ROIContourData, - RTROIObservationsSequence, - ROINumber -) { +function _setROIContourRTROIObservations(ROIContourData, RTROIObservationsSequence, ROINumber) { const RTROIObservations = RTROIObservationsSequence.find( RTROIObservations => RTROIObservations.ReferencedROINumber === ROINumber ); if (RTROIObservations) { // Deep copy so we don't keep the reference to the dcmjs dataset entry. - const { - ObservationNumber, - ROIObservationDescription, - RTROIInterpretedType, - ROIInterpreter, - } = RTROIObservations; + const { ObservationNumber, ROIObservationDescription, RTROIInterpretedType, ROIInterpreter } = + RTROIObservations; ROIContourData.RTROIObservations = { ObservationNumber, diff --git a/extensions/cornerstone-dicom-rt/src/utils/_hydrateRT.ts b/extensions/cornerstone-dicom-rt/src/utils/_hydrateRT.ts deleted file mode 100644 index 0668c894b82..00000000000 --- a/extensions/cornerstone-dicom-rt/src/utils/_hydrateRT.ts +++ /dev/null @@ -1,70 +0,0 @@ -async function _hydrateRTDisplaySet({ - rtDisplaySet, - viewportIndex, - servicesManager, -}) { - const { - segmentationService, - hangingProtocolService, - viewportGridService, - } = servicesManager.services; - - const displaySetInstanceUID = rtDisplaySet.referencedDisplaySetInstanceUID; - - let segmentationId = null; - - // We need the hydration to notify panels about the new segmentation added - const suppressEvents = false; - - segmentationId = await segmentationService.createSegmentationForRTDisplaySet( - rtDisplaySet, - segmentationId, - suppressEvents - ); - - segmentationService.hydrateSegmentation(rtDisplaySet.displaySetInstanceUID); - - const { viewports } = viewportGridService.getState(); - - const updatedViewports = hangingProtocolService.getViewportsRequireUpdate( - viewportIndex, - displaySetInstanceUID - ); - - viewportGridService.setDisplaySetsForViewports(updatedViewports); - - // Todo: fix this after we have a better way for stack viewport segmentations - - // check every viewport in the viewports to see if the displaySetInstanceUID - // is being displayed, if so we need to update the viewport to use volume viewport - // (if already is not using it) since Cornerstone3D currently only supports - // volume viewport for segmentation - viewports.forEach((viewport, index) => { - if (index === viewportIndex) { - return; - } - - const shouldDisplaySeg = segmentationService.shouldRenderSegmentation( - viewport.displaySetInstanceUIDs, - rtDisplaySet.displaySetInstanceUID - ); - - if (shouldDisplaySeg) { - updatedViewports.push({ - viewportIndex: index, - displaySetInstanceUIDs: viewport.displaySetInstanceUIDs, - viewportOptions: { - initialImageOptions: { - preset: 'middle', - }, - }, - }); - } - }); - - // Do the entire update at once - viewportGridService.setDisplaySetsForViewports(updatedViewports); - return true; -} - -export default _hydrateRTDisplaySet; diff --git a/extensions/cornerstone-dicom-rt/src/utils/initRTToolGroup.ts b/extensions/cornerstone-dicom-rt/src/utils/initRTToolGroup.ts index 826e3b9a6f3..f47f0089d8a 100644 --- a/extensions/cornerstone-dicom-rt/src/utils/initRTToolGroup.ts +++ b/extensions/cornerstone-dicom-rt/src/utils/initRTToolGroup.ts @@ -1,12 +1,7 @@ -function createRTToolGroupAndAddTools( - ToolGroupService, - customizationService, - toolGroupId -) { - const { tools } = - customizationService.get('cornerstone.overlayViewportTools') ?? {}; +function createRTToolGroupAndAddTools(ToolGroupService, customizationService, toolGroupId) { + const { tools } = customizationService.get('cornerstone.overlayViewportTools') ?? {}; - return ToolGroupService.createToolGroupAndAddTools(toolGroupId, tools, {}); + return ToolGroupService.createToolGroupAndAddTools(toolGroupId, tools); } export default createRTToolGroupAndAddTools; diff --git a/extensions/cornerstone-dicom-rt/src/utils/promptHydrateRT.ts b/extensions/cornerstone-dicom-rt/src/utils/promptHydrateRT.ts index 55c01107468..91492cbfbed 100644 --- a/extensions/cornerstone-dicom-rt/src/utils/promptHydrateRT.ts +++ b/extensions/cornerstone-dicom-rt/src/utils/promptHydrateRT.ts @@ -1,5 +1,4 @@ import { ButtonEnums } from '@ohif/ui'; -import hydrateRTDisplaySet from './_hydrateRT'; const RESPONSE = { NO_NEVER: -1, @@ -10,17 +9,15 @@ const RESPONSE = { function promptHydrateRT({ servicesManager, rtDisplaySet, - viewportIndex, + viewportId, toolGroupId = 'default', preHydrateCallbacks, + hydrateRTDisplaySet, }) { const { uiViewportDialogService } = servicesManager.services; - return new Promise(async function(resolve, reject) { - const promptResult = await _askHydrate( - uiViewportDialogService, - viewportIndex - ); + return new Promise(async function (resolve, reject) { + const promptResult = await _askHydrate(uiViewportDialogService, viewportId); if (promptResult === RESPONSE.HYDRATE_SEG) { preHydrateCallbacks?.forEach(callback => { @@ -29,7 +26,7 @@ function promptHydrateRT({ const isHydrated = await hydrateRTDisplaySet({ rtDisplaySet, - viewportIndex, + viewportId, toolGroupId, servicesManager, }); @@ -39,8 +36,8 @@ function promptHydrateRT({ }); } -function _askHydrate(uiViewportDialogService, viewportIndex) { - return new Promise(function(resolve, reject) { +function _askHydrate(uiViewportDialogService, viewportId) { + return new Promise(function (resolve, reject) { const message = 'Do you want to open this Segmentation?'; const actions = [ { @@ -60,7 +57,7 @@ function _askHydrate(uiViewportDialogService, viewportIndex) { }; uiViewportDialogService.show({ - viewportIndex, + viewportId, type: 'info', message, actions, diff --git a/extensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsx b/extensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsx index d203c676923..5c6a844dd91 100644 --- a/extensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsx +++ b/extensions/cornerstone-dicom-rt/src/viewports/OHIFCornerstoneRTViewport.tsx @@ -1,17 +1,11 @@ import React, { useCallback, useEffect, useRef, useState } from 'react'; import PropTypes from 'prop-types'; import OHIF, { utils } from '@ohif/core'; -import { - ViewportActionBar, - useViewportGrid, - LoadingIndicatorTotalPercent, -} from '@ohif/ui'; +import { ViewportActionBar, useViewportGrid, LoadingIndicatorTotalPercent } from '@ohif/ui'; -import _hydrateRTdisplaySet from '../utils/_hydrateRT'; import promptHydrateRT from '../utils/promptHydrateRT'; import _getStatusComponent from './_getStatusComponent'; import createRTToolGroupAndAddTools from '../utils/initRTToolGroup'; -import _hydrateRTDisplaySet from '../utils/_hydrateRT'; const { formatDate } = utils; const RT_TOOLGROUP_BASE_NAME = 'RTToolGroup'; @@ -21,7 +15,6 @@ function OHIFCornerstoneRTViewport(props) { children, displaySets, viewportOptions, - viewportIndex, viewportLabel, servicesManager, extensionManager, @@ -36,7 +29,9 @@ function OHIFCornerstoneRTViewport(props) { customizationService, } = servicesManager.services; - const toolGroupId = `${RT_TOOLGROUP_BASE_NAME}-${viewportIndex}`; + const viewportId = viewportOptions.viewportId; + + const toolGroupId = `${RT_TOOLGROUP_BASE_NAME}-${viewportId}`; // RT viewport will always have a single display set if (displaySets.length > 1) { @@ -67,12 +62,10 @@ function OHIFCornerstoneRTViewport(props) { // refs const referencedDisplaySetRef = useRef(null); - const { viewports, activeViewportIndex } = viewportGrid; + const { viewports, activeViewportId } = viewportGrid; const referencedDisplaySet = rtDisplaySet.getReferenceDisplaySet(); - const referencedDisplaySetMetadata = _getReferencedDisplaySetMetadata( - referencedDisplaySet - ); + const referencedDisplaySetMetadata = _getReferencedDisplaySetMetadata(referencedDisplaySet); referencedDisplaySetRef.current = { displaySet: referencedDisplaySet, @@ -93,21 +86,26 @@ function OHIFCornerstoneRTViewport(props) { }; const storePresentationState = useCallback(() => { - viewportGrid?.viewports.forEach(({ viewportIndex }) => { + viewportGrid?.viewports.forEach(({ viewportId }) => { commandsManager.runCommand('storePresentation', { - viewportIndex, + viewportId, }); }); }, [viewportGrid]); + const hydrateRTDisplaySet = ({ rtDisplaySet, viewportId }) => { + commandsManager.runCommand('loadSegmentationDisplaySetsForViewport', { + displaySets: [rtDisplaySet], + viewportId, + }); + }; + const getCornerstoneViewport = useCallback(() => { const { component: Component } = extensionManager.getModuleEntry( '@ohif/extension-cornerstone.viewportModule.cornerstone' ); - const { - displaySet: referencedDisplaySet, - } = referencedDisplaySetRef.current; + const { displaySet: referencedDisplaySet } = referencedDisplaySetRef.current; // Todo: jump to the center of the first segment return ( @@ -124,7 +122,7 @@ function OHIFCornerstoneRTViewport(props) { onElementDisabled={onElementDisabled} > ); - }, [viewportIndex, rtDisplaySet, toolGroupId]); + }, [viewportId, rtDisplaySet, toolGroupId]); const onSegmentChange = useCallback( direction => { @@ -145,11 +143,7 @@ function OHIFCornerstoneRTViewport(props) { newSelectedSegmentIndex = numberOfSegments - 1; } - segmentationService.jumpToSegmentCenter( - segmentationId, - newSelectedSegmentIndex, - toolGroupId - ); + segmentationService.jumpToSegmentCenter(segmentationId, newSelectedSegmentIndex, toolGroupId); setSelectedSegment(newSelectedSegmentIndex); }, [selectedSegment] @@ -162,32 +156,31 @@ function OHIFCornerstoneRTViewport(props) { promptHydrateRT({ servicesManager, - viewportIndex, + viewportId, rtDisplaySet, preHydrateCallbacks: [storePresentationState], + hydrateRTDisplaySet, }).then(isHydrated => { if (isHydrated) { setIsHydrated(true); } }); - }, [servicesManager, viewportIndex, rtDisplaySet, rtIsLoading]); + }, [servicesManager, viewportId, rtDisplaySet, rtIsLoading]); useEffect(() => { + // I'm not sure what is this, since in RT we support Overlapping segments + // via contours const { unsubscribe } = segmentationService.subscribe( segmentationService.EVENTS.SEGMENTATION_LOADING_COMPLETE, evt => { - if ( - evt.rtDisplaySet.displaySetInstanceUID === - rtDisplaySet.displaySetInstanceUID - ) { + if (evt.rtDisplaySet.displaySetInstanceUID === rtDisplaySet.displaySetInstanceUID) { setRtIsLoading(false); } if (evt.overlappingSegments) { uiNotificationService.show({ title: 'Overlapping Segments', - message: - 'Overlapping segments detected which is not currently supported', + message: 'Overlapping segments detected which is not currently supported', type: 'warning', }); } @@ -222,12 +215,10 @@ function OHIFCornerstoneRTViewport(props) { const onDisplaySetsRemovedSubscription = displaySetService.subscribe( displaySetService.EVENTS.DISPLAY_SETS_REMOVED, ({ displaySetInstanceUIDs }) => { - const activeViewport = viewports[activeViewportIndex]; - if ( - displaySetInstanceUIDs.includes(activeViewport.displaySetInstanceUID) - ) { + const activeViewport = viewports.get(activeViewportId); + if (displaySetInstanceUIDs.includes(activeViewport.displaySetInstanceUID)) { viewportGridService.setDisplaySetsForViewport({ - viewportIndex: activeViewportIndex, + viewportId: activeViewportId, displaySetInstanceUIDs: [], }); } @@ -246,19 +237,13 @@ function OHIFCornerstoneRTViewport(props) { return; } - toolGroup = createRTToolGroupAndAddTools( - toolGroupService, - customizationService, - toolGroupId - ); + toolGroup = createRTToolGroupAndAddTools(toolGroupService, customizationService, toolGroupId); setToolGroupCreated(true); return () => { // remove the segmentation representations if seg displayset changed - segmentationService.removeSegmentationRepresentationFromToolGroup( - toolGroupId - ); + segmentationService.removeSegmentationRepresentationFromToolGroup(toolGroupId); toolGroupService.destroyToolGroup(toolGroupId); }; @@ -269,9 +254,7 @@ function OHIFCornerstoneRTViewport(props) { return () => { // remove the segmentation representations if seg displayset changed - segmentationService.removeSegmentationRepresentationFromToolGroup( - toolGroupId - ); + segmentationService.removeSegmentationRepresentationFromToolGroup(toolGroupId); referencedDisplaySetRef.current = null; }; }, [rtDisplaySet]); @@ -292,7 +275,7 @@ function OHIFCornerstoneRTViewport(props) { return ( child && React.cloneElement(child, { - viewportIndex, + viewportId, key: index, }) ); @@ -320,10 +303,9 @@ function OHIFCornerstoneRTViewport(props) { // presentation state (w/l and invert) and then opens the RT. If we don't store // the presentation state, the viewport will be reset to the default presentation storePresentationState(); - const isHydrated = await _hydrateRTDisplaySet({ + const isHydrated = await hydrateRTDisplaySet({ rtDisplaySet, - viewportIndex, - servicesManager, + viewportId, }); setIsHydrated(isHydrated); @@ -350,26 +332,22 @@ function OHIFCornerstoneRTViewport(props) { currentSeries: SeriesNumber, seriesDescription: `RT Viewport ${SeriesDescription}`, patientInformation: { - patientName: PatientName - ? OHIF.utils.formatPN(PatientName.Alphabetic) - : '', + patientName: PatientName ? OHIF.utils.formatPN(PatientName.Alphabetic) : '', patientSex: PatientSex || '', patientAge: PatientAge || '', MRN: PatientID || '', thickness: SliceThickness ? `${SliceThickness.toFixed(2)}mm` : '', spacing: - SpacingBetweenSlices !== undefined - ? `${SpacingBetweenSlices.toFixed(2)}mm` - : '', + SpacingBetweenSlices !== undefined ? `${SpacingBetweenSlices.toFixed(2)}mm` : '', scanner: ManufacturerModelName || '', }, }} /> -
+
{rtIsLoading && ( ; - ToolTipMessage = () => ( -
This Segmentation is loaded in the segmentation panel
- ); + ToolTipMessage = () =>
This Segmentation is loaded in the segmentation panel
; break; case false: - StatusIcon = () => ; + StatusIcon = () => ( + + ); ToolTipMessage = () =>
Click LOAD to load RTSTRUCT.
; } const StatusArea = () => ( -
-
+
+
RTSTRUCT
{!isHydrated && (
@@ -44,7 +47,10 @@ export default function _getStatusComponent({ isHydrated, onStatusClick }) { return ( <> {ToolTipMessage && ( - } position="bottom-left"> + } + position="bottom-left" + > )} diff --git a/extensions/cornerstone-dicom-seg/.webpack/webpack.prod.js b/extensions/cornerstone-dicom-seg/.webpack/webpack.prod.js index 017e4bbf014..3f6eb4b69ea 100644 --- a/extensions/cornerstone-dicom-seg/.webpack/webpack.prod.js +++ b/extensions/cornerstone-dicom-seg/.webpack/webpack.prod.js @@ -40,21 +40,15 @@ module.exports = (env, argv) => { libraryTarget: 'umd', filename: pkg.main, }, - externals: [ - /\b(vtk.js)/, - /\b(dcmjs)/, - /\b(gl-matrix)/, - /^@ohif/, - /^@cornerstonejs/, - ], + externals: [/\b(vtk.js)/, /\b(dcmjs)/, /\b(gl-matrix)/, /^@ohif/, /^@cornerstonejs/], plugins: [ new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1, }), - // new MiniCssExtractPlugin({ - // filename: `./dist/${outputName}.css`, - // chunkFilename: `./dist/${outputName}.css`, - // }), + new MiniCssExtractPlugin({ + filename: `./dist/${outputName}.css`, + chunkFilename: `./dist/${outputName}.css`, + }), ], }); }; diff --git a/extensions/cornerstone-dicom-seg/CHANGELOG.md b/extensions/cornerstone-dicom-seg/CHANGELOG.md new file mode 100644 index 00000000000..c1cb3e787e5 --- /dev/null +++ b/extensions/cornerstone-dicom-seg/CHANGELOG.md @@ -0,0 +1,966 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [3.8.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.59...v3.8.0-beta.60) (2024-03-15) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.58...v3.8.0-beta.59) (2024-03-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.57...v3.8.0-beta.58) (2024-03-05) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.56...v3.8.0-beta.57) (2024-02-28) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.55...v3.8.0-beta.56) (2024-02-22) + + +### Bug Fixes + +* **demo:** Deploy issue ([#3951](https://github.com/OHIF/Viewers/issues/3951)) ([21e8a2b](https://github.com/OHIF/Viewers/commit/21e8a2bd0b7cc72f90a31e472d285d761be15d30)) + + + + + +# [3.8.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.54...v3.8.0-beta.55) (2024-02-21) + + +### Features + +* **resize:** Optimize resizing process and maintain zoom level ([#3889](https://github.com/OHIF/Viewers/issues/3889)) ([b3a0faf](https://github.com/OHIF/Viewers/commit/b3a0faf5f5f0a1993b2b017eb4cc1216164ea2c6)) + + + + + +# [3.8.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.53...v3.8.0-beta.54) (2024-02-14) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.52...v3.8.0-beta.53) (2024-02-05) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.51...v3.8.0-beta.52) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.50...v3.8.0-beta.51) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.49...v3.8.0-beta.50) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.48...v3.8.0-beta.49) (2024-01-19) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.47...v3.8.0-beta.48) (2024-01-17) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.46...v3.8.0-beta.47) (2024-01-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.45...v3.8.0-beta.46) (2024-01-12) + + +### Bug Fixes + +* Update CS3D to fix second render ([#3892](https://github.com/OHIF/Viewers/issues/3892)) ([d00a86b](https://github.com/OHIF/Viewers/commit/d00a86b022742ea089d246d06cfd691f43b64412)) + + + + + +# [3.8.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.44...v3.8.0-beta.45) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.43...v3.8.0-beta.44) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.42...v3.8.0-beta.43) (2024-01-09) + + +### Bug Fixes + +* **segmentation:** upgrade cs3d to fix various segmentation bugs ([#3885](https://github.com/OHIF/Viewers/issues/3885)) ([b1efe40](https://github.com/OHIF/Viewers/commit/b1efe40aa146e4052cc47b3f774cabbb47a8d1a6)) + + + + + +# [3.8.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.41...v3.8.0-beta.42) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.40...v3.8.0-beta.41) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.39...v3.8.0-beta.40) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.38...v3.8.0-beta.39) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.37...v3.8.0-beta.38) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.36...v3.8.0-beta.37) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.35...v3.8.0-beta.36) (2023-12-15) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.34...v3.8.0-beta.35) (2023-12-14) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.33...v3.8.0-beta.34) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.32...v3.8.0-beta.33) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.31...v3.8.0-beta.32) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.30...v3.8.0-beta.31) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.29...v3.8.0-beta.30) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.28...v3.8.0-beta.29) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.27...v3.8.0-beta.28) (2023-12-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.26...v3.8.0-beta.27) (2023-12-06) + + +### Bug Fixes + +* **auth:** fix the issue with oauth at a non root path ([#3840](https://github.com/OHIF/Viewers/issues/3840)) ([6651008](https://github.com/OHIF/Viewers/commit/6651008fbb35dabd5991c7f61128e6ef324012df)) + + + + + +# [3.8.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.25...v3.8.0-beta.26) (2023-11-28) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.24...v3.8.0-beta.25) (2023-11-27) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.23...v3.8.0-beta.24) (2023-11-24) + + +### Bug Fixes + +* Update the CS3D packages to add the most recent HTJ2K TSUIDS ([#3806](https://github.com/OHIF/Viewers/issues/3806)) ([9d1884d](https://github.com/OHIF/Viewers/commit/9d1884d7d8b6b2a1cdc26965a96995838aa72682)) + + + + + +# [3.8.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.22...v3.8.0-beta.23) (2023-11-24) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.21...v3.8.0-beta.22) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.20...v3.8.0-beta.21) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.19...v3.8.0-beta.20) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.18...v3.8.0-beta.19) (2023-11-18) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.17...v3.8.0-beta.18) (2023-11-15) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.16...v3.8.0-beta.17) (2023-11-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.15...v3.8.0-beta.16) (2023-11-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.14...v3.8.0-beta.15) (2023-11-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.14](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.13...v3.8.0-beta.14) (2023-11-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.13](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.12...v3.8.0-beta.13) (2023-11-09) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.12](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.11...v3.8.0-beta.12) (2023-11-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.11](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.10...v3.8.0-beta.11) (2023-11-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.10](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.9...v3.8.0-beta.10) (2023-11-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.9](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.8...v3.8.0-beta.9) (2023-11-02) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.8](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.7...v3.8.0-beta.8) (2023-10-31) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.7](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.6...v3.8.0-beta.7) (2023-10-30) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.6](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.5...v3.8.0-beta.6) (2023-10-25) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.5](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.4...v3.8.0-beta.5) (2023-10-24) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.4](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.3...v3.8.0-beta.4) (2023-10-23) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.3](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.2...v3.8.0-beta.3) (2023-10-23) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.1...v3.8.0-beta.2) (2023-10-19) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.0...v3.8.0-beta.1) (2023-10-19) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.8.0-beta.0](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.110...v3.8.0-beta.0) (2023-10-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.110](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.109...v3.7.0-beta.110) (2023-10-11) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.109](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.108...v3.7.0-beta.109) (2023-10-11) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.107...v3.7.0-beta.108) (2023-10-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.106...v3.7.0-beta.107) (2023-10-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.105...v3.7.0-beta.106) (2023-10-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.104...v3.7.0-beta.105) (2023-10-10) + + +### Bug Fixes + +* **voi:** should publish voi change event on reset ([#3707](https://github.com/OHIF/Viewers/issues/3707)) ([52f34c6](https://github.com/OHIF/Viewers/commit/52f34c64d014f433ec1661a39b47e7fb27f15332)) + + + + + +# [3.7.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.103...v3.7.0-beta.104) (2023-10-09) + + +### Bug Fixes + +* **modality unit:** fix the modality unit per target via upgrade of cs3d ([#3706](https://github.com/OHIF/Viewers/issues/3706)) ([0a42d57](https://github.com/OHIF/Viewers/commit/0a42d573bbca7f2551a831a46d3aa6b56674a580)) + + + + + +# [3.7.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.102...v3.7.0-beta.103) (2023-10-09) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.101...v3.7.0-beta.102) (2023-10-06) + + +### Features + +* **Segmentation:** download RTSS from Labelmap([#3692](https://github.com/OHIF/Viewers/issues/3692)) ([40673f6](https://github.com/OHIF/Viewers/commit/40673f64b36b1150149c55632aa1825178a39e65)) + + + + + +# [3.7.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.100...v3.7.0-beta.101) (2023-10-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.99...v3.7.0-beta.100) (2023-10-06) + + +### Bug Fixes + +* **segmentation scroll:** and hydration bugs ([#3701](https://github.com/OHIF/Viewers/issues/3701)) ([1fd98d9](https://github.com/OHIF/Viewers/commit/1fd98d922094d10fe0c6e9df726314ec9fce49e8)) + + + + + +# [3.7.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.98...v3.7.0-beta.99) (2023-10-04) + + +### Bug Fixes + +* **measurement and microscopy:** various small fixes for measurement and microscopy side panel ([#3696](https://github.com/OHIF/Viewers/issues/3696)) ([c1d5ee7](https://github.com/OHIF/Viewers/commit/c1d5ee7e3f7f4c0c6bed9ae81eba5519741c5155)) + + + + + +# [3.7.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.97...v3.7.0-beta.98) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.96...v3.7.0-beta.97) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.95...v3.7.0-beta.96) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.94...v3.7.0-beta.95) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.93...v3.7.0-beta.94) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.92...v3.7.0-beta.93) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.91...v3.7.0-beta.92) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.90...v3.7.0-beta.91) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.89...v3.7.0-beta.90) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.88...v3.7.0-beta.89) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.87...v3.7.0-beta.88) (2023-10-03) + + +### Bug Fixes + +* **config:** support more values for the useSharedArrayBuffer ([#3688](https://github.com/OHIF/Viewers/issues/3688)) ([1129c15](https://github.com/OHIF/Viewers/commit/1129c155d2c7d46c98a5df7c09879aa3d459fa7e)) + + + + + +# [3.7.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.86...v3.7.0-beta.87) (2023-09-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.85...v3.7.0-beta.86) (2023-09-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22) + + +### Features + +* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1)) +* **SidePanel:** new side panel tab look-and-feel ([#3657](https://github.com/OHIF/Viewers/issues/3657)) ([85c899b](https://github.com/OHIF/Viewers/commit/85c899b399e2521480724be145538993721b9378)) + + + + + +# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01) + + +### Features + +* **grid:** remove viewportIndex and only rely on viewportId ([#3591](https://github.com/OHIF/Viewers/issues/3591)) ([4c6ff87](https://github.com/OHIF/Viewers/commit/4c6ff873e887cc30ffc09223f5cb99e5f94c9cdd)) + + + + + +# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg + + + + + +# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25) + + +### Features + +* **cloud data source config:** GUI and API for configuring a cloud data source with Google cloud healthcare implementation ([#3589](https://github.com/OHIF/Viewers/issues/3589)) ([a336992](https://github.com/OHIF/Viewers/commit/a336992971c07552c9dbb6e1de43169d37762ef1)) + + + + + +# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-seg diff --git a/extensions/cornerstone-dicom-seg/babel.config.js b/extensions/cornerstone-dicom-seg/babel.config.js index 92fbbdeaf95..a38ddda2127 100644 --- a/extensions/cornerstone-dicom-seg/babel.config.js +++ b/extensions/cornerstone-dicom-seg/babel.config.js @@ -10,7 +10,7 @@ module.exports = { modules: 'commonjs', debug: false, }, - "@babel/preset-typescript", + '@babel/preset-typescript', ], '@babel/preset-react', ], @@ -26,7 +26,7 @@ module.exports = { // WebPack handles ES6 --> Target Syntax ['@babel/preset-env', { modules: false }], '@babel/preset-react', - "@babel/preset-typescript", + '@babel/preset-typescript', ], ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'], }, @@ -35,7 +35,7 @@ module.exports = { // WebPack handles ES6 --> Target Syntax ['@babel/preset-env', { modules: false }], '@babel/preset-react', - "@babel/preset-typescript", + '@babel/preset-typescript', ], plugins: ['react-hot-loader/babel'], ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'], diff --git a/extensions/cornerstone-dicom-seg/package.json b/extensions/cornerstone-dicom-seg/package.json index 3ab6693091b..506511f415b 100644 --- a/extensions/cornerstone-dicom-seg/package.json +++ b/extensions/cornerstone-dicom-seg/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-cornerstone-dicom-seg", - "version": "3.7.0-beta.46", + "version": "3.8.0-beta.60", "description": "DICOM SEG read workflow", "author": "OHIF", "license": "MIT", @@ -24,6 +24,8 @@ "yarn": ">=1.18.0" }, "scripts": { + "clean": "shx rm -rf dist", + "clean:deep": "yarn run clean && shx rm -rf node_modules", "dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --output-pathinfo", "dev:dicom-seg": "yarn run dev", "build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js", @@ -31,10 +33,10 @@ "start": "yarn run dev" }, "peerDependencies": { - "@ohif/core": "3.7.0-beta.46", - "@ohif/extension-cornerstone": "3.7.0-beta.46", - "@ohif/extension-default": "3.7.0-beta.46", - "@ohif/i18n": "3.7.0-beta.46", + "@ohif/core": "3.8.0-beta.60", + "@ohif/extension-cornerstone": "3.8.0-beta.60", + "@ohif/extension-default": "3.8.0-beta.60", + "@ohif/i18n": "3.8.0-beta.60", "prop-types": "^15.6.2", "react": "^17.0.2", "react-dom": "^17.0.2", @@ -44,6 +46,9 @@ }, "dependencies": { "@babel/runtime": "^7.20.13", + "@cornerstonejs/adapters": "^1.63.4", + "@cornerstonejs/core": "^1.63.4", + "@kitware/vtk.js": "29.7.0", "react-color": "^2.19.3" } } diff --git a/extensions/cornerstone-dicom-seg/src/commandsModule.ts b/extensions/cornerstone-dicom-seg/src/commandsModule.ts new file mode 100644 index 00000000000..1926ee9f138 --- /dev/null +++ b/extensions/cornerstone-dicom-seg/src/commandsModule.ts @@ -0,0 +1,435 @@ +import dcmjs from 'dcmjs'; +import { createReportDialogPrompt } from '@ohif/extension-default'; +import { ServicesManager, Types } from '@ohif/core'; +import { cache, metaData } from '@cornerstonejs/core'; +import { + segmentation as cornerstoneToolsSegmentation, + Enums as cornerstoneToolsEnums, +} from '@cornerstonejs/tools'; +import { adaptersRT, helpers, adaptersSEG } from '@cornerstonejs/adapters'; +import { classes, 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'; + +import { + updateViewportsForSegmentationRendering, + getUpdatedViewportsForSegmentation, + getTargetViewport, +} from './utils/hydrationUtils'; + +const { datasetToBlob } = dcmjs.data; + +const { + Cornerstone3D: { + Segmentation: { generateLabelMaps2DFrom3D, generateSegmentation }, + }, +} = adaptersSEG; + +const { + Cornerstone3D: { + RTSS: { generateRTSSFromSegmentations }, + }, +} = adaptersRT; + +const { downloadDICOMData } = helpers; + +const commandsModule = ({ + servicesManager, + extensionManager, +}: Types.Extensions.ExtensionParams): Types.Extensions.CommandsModule => { + const { + uiNotificationService, + segmentationService, + uiDialogService, + displaySetService, + viewportGridService, + } = (servicesManager as ServicesManager).services; + + const actions = { + /** + * Retrieves a list of viewports that require updates in preparation for segmentation rendering. + * This function evaluates viewports based on their compatibility with the provided segmentation's + * frame of reference UID and appends them to the updated list if they should render the segmentation. + * + * @param {Object} params - Parameters for the function. + * @param params.viewportId - the ID of the viewport to be updated. + * @param params.servicesManager - The services manager + * @param params.referencedDisplaySetInstanceUID - Optional UID for the referenced display set instance. + * + * @returns {Array} Returns an array of viewports that require updates for segmentation rendering. + */ + getUpdatedViewportsForSegmentation, + /** + * Creates an empty segmentation for a specified viewport. + * It first checks if the display set associated with the viewport is reconstructable. + * If not, it raises a notification error. Otherwise, it creates a new segmentation + * for the display set after handling the necessary steps for making the viewport + * a volume viewport first + * + * @param {Object} params - Parameters for the function. + * @param params.viewportId - the target viewport ID. + * + */ + createEmptySegmentationForViewport: async ({ viewportId }) => { + const viewport = getTargetViewport({ viewportId, viewportGridService }); + // Todo: add support for multiple display sets + const displaySetInstanceUID = viewport.displaySetInstanceUIDs[0]; + + const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID); + + if (!displaySet.isReconstructable) { + uiNotificationService.show({ + title: 'Segmentation', + message: 'Segmentation is not supported for non-reconstructible displaysets yet', + type: 'error', + }); + return; + } + + updateViewportsForSegmentationRendering({ + viewportId, + servicesManager, + loadFn: async () => { + const currentSegmentations = segmentationService.getSegmentations(); + const segmentationId = await segmentationService.createSegmentationForDisplaySet( + displaySetInstanceUID, + { label: `Segmentation ${currentSegmentations.length + 1}` } + ); + + const toolGroupId = viewport.viewportOptions.toolGroupId; + + await segmentationService.addSegmentationRepresentationToToolGroup( + toolGroupId, + segmentationId + ); + + // Add only one segment for now + segmentationService.addSegment(segmentationId, { + toolGroupId, + segmentIndex: 1, + properties: { + label: 'Segment 1', + }, + }); + + return segmentationId; + }, + }); + }, + /** + * Loads segmentations for a specified viewport. + * The function prepares the viewport for rendering, then loads the segmentation details. + * Additionally, if the segmentation has scalar data, it is set for the corresponding label map volume. + * + * @param {Object} params - Parameters for the function. + * @param params.segmentations - Array of segmentations to be loaded. + * @param params.viewportId - the target viewport ID. + * + */ + loadSegmentationsForViewport: async ({ segmentations, viewportId }) => { + updateViewportsForSegmentationRendering({ + viewportId, + servicesManager, + loadFn: async () => { + // Todo: handle adding more than one segmentation + const viewport = getTargetViewport({ viewportId, viewportGridService }); + const displaySetInstanceUID = viewport.displaySetInstanceUIDs[0]; + + const segmentation = segmentations[0]; + const segmentationId = segmentation.id; + const label = segmentation.label; + const segments = segmentation.segments; + + delete segmentation.segments; + + await segmentationService.createSegmentationForDisplaySet(displaySetInstanceUID, { + segmentationId, + label, + }); + + if (segmentation.scalarData) { + const labelmapVolume = segmentationService.getLabelmapVolume(segmentationId); + labelmapVolume.scalarData.set(segmentation.scalarData); + } + + segmentationService.addOrUpdateSegmentation(segmentation); + + const toolGroupId = viewport.viewportOptions.toolGroupId; + await segmentationService.addSegmentationRepresentationToToolGroup( + toolGroupId, + segmentationId + ); + + segments.forEach(segment => { + if (segment === null) { + return; + } + segmentationService.addSegment(segmentationId, { + segmentIndex: segment.segmentIndex, + toolGroupId, + properties: { + color: segment.color, + label: segment.label, + opacity: segment.opacity, + isLocked: segment.isLocked, + visibility: segment.isVisible, + active: segmentation.activeSegmentIndex === segment.segmentIndex, + }, + }); + }); + + if (segmentation.centroidsIJK) { + segmentationService.setCentroids(segmentation.id, segmentation.centroidsIJK); + } + + return segmentationId; + }, + }); + }, + /** + * Loads segmentation display sets for a specified viewport. + * Depending on the modality of the display set (SEG or RTSTRUCT), + * it chooses the appropriate service function to create + * the segmentation for the display set. + * The function then prepares the viewport for rendering segmentation. + * + * @param {Object} params - Parameters for the function. + * @param params.viewportId - ID of the viewport where the segmentation display sets should be loaded. + * @param params.displaySets - Array of display sets to be loaded for segmentation. + * + */ + loadSegmentationDisplaySetsForViewport: async ({ viewportId, displaySets }) => { + // Todo: handle adding more than one segmentation + const displaySet = displaySets[0]; + + updateViewportsForSegmentationRendering({ + viewportId, + servicesManager, + referencedDisplaySetInstanceUID: displaySet.referencedDisplaySetInstanceUID, + loadFn: async () => { + const segDisplaySet = displaySet; + const suppressEvents = false; + const serviceFunction = + segDisplaySet.Modality === 'SEG' + ? 'createSegmentationForSEGDisplaySet' + : 'createSegmentationForRTDisplaySet'; + + const boundFn = segmentationService[serviceFunction].bind(segmentationService); + const segmentationId = await boundFn(segDisplaySet, null, suppressEvents); + + return segmentationId; + }, + }); + }, + /** + * Generates a segmentation from a given segmentation ID. + * This function retrieves the associated segmentation and + * its referenced volume, extracts label maps from the + * segmentation volume, and produces segmentation data + * alongside associated metadata. + * + * @param {Object} params - Parameters for the function. + * @param params.segmentationId - ID of the segmentation to be generated. + * @param params.options - Optional configuration for the generation process. + * + * @returns Returns the generated segmentation data. + */ + generateSegmentation: ({ segmentationId, options = {} }) => { + const segmentation = cornerstoneToolsSegmentation.state.getSegmentation(segmentationId); + + const { referencedVolumeId } = segmentation.representationData.LABELMAP; + + const segmentationVolume = cache.getVolume(segmentationId); + const referencedVolume = cache.getVolume(referencedVolumeId); + const referencedImages = referencedVolume.getCornerstoneImages(); + + const labelmapObj = generateLabelMaps2DFrom3D(segmentationVolume); + + // Generate fake metadata as an example + labelmapObj.metadata = []; + + const segmentationInOHIF = segmentationService.getSegmentation(segmentationId); + labelmapObj.segmentsOnLabelmap.forEach(segmentIndex => { + // segmentation service already has a color for each segment + const segment = segmentationInOHIF?.segments[segmentIndex]; + const { label, color } = segment; + + const RecommendedDisplayCIELabValue = dcmjs.data.Colors.rgb2DICOMLAB( + color.slice(0, 3).map(value => value / 255) + ).map(value => Math.round(value)); + + const segmentMetadata = { + SegmentNumber: segmentIndex.toString(), + SegmentLabel: label, + SegmentAlgorithmType: 'MANUAL', + SegmentAlgorithmName: 'OHIF Brush', + RecommendedDisplayCIELabValue, + SegmentedPropertyCategoryCodeSequence: { + CodeValue: 'T-D0050', + CodingSchemeDesignator: 'SRT', + CodeMeaning: 'Tissue', + }, + SegmentedPropertyTypeCodeSequence: { + CodeValue: 'T-D0050', + CodingSchemeDesignator: 'SRT', + CodeMeaning: 'Tissue', + }, + }; + labelmapObj.metadata[segmentIndex] = segmentMetadata; + }); + + const generatedSegmentation = generateSegmentation( + referencedImages, + labelmapObj, + metaData, + options + ); + + return generatedSegmentation; + }, + /** + * Downloads a segmentation based on the provided segmentation ID. + * This function retrieves the associated segmentation and + * uses it to generate the corresponding DICOM dataset, which + * is then downloaded with an appropriate filename. + * + * @param {Object} params - Parameters for the function. + * @param params.segmentationId - ID of the segmentation to be downloaded. + * + */ + downloadSegmentation: ({ segmentationId }) => { + const segmentationInOHIF = segmentationService.getSegmentation(segmentationId); + const generatedSegmentation = actions.generateSegmentation({ + segmentationId, + }); + + downloadDICOMData(generatedSegmentation.dataset, `${segmentationInOHIF.label}`); + }, + /** + * Stores a segmentation based on the provided segmentationId into a specified data source. + * The SeriesDescription is derived from user input or defaults to the segmentation label, + * and in its absence, defaults to 'Research Derived Series'. + * + * @param {Object} params - Parameters for the function. + * @param params.segmentationId - ID of the segmentation to be stored. + * @param params.dataSource - Data source where the generated segmentation will be stored. + * + * @returns {Object|void} Returns the naturalized report if successfully stored, + * 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) { + throw new Error('No segmentation found'); + } + + const { label } = segmentation; + const SeriesDescription = promptResult.value || label || 'Research Derived Series'; + + const generatedData = actions.generateSegmentation({ + segmentationId, + options: { + SeriesDescription, + }, + }); + + if (!generatedData || !generatedData.dataset) { + throw new Error('Error during segmentation generation'); + } + + 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. + * This sample function retrieves all segentations and passes to + * cornerstone tool adapter to convert to DICOM RTSS format. It then + * converts dataset to downloadable blob. + * + */ + downloadRTSS: ({ segmentationId }) => { + const segmentations = segmentationService.getSegmentation(segmentationId); + const vtkUtils = { + vtkImageMarchingSquares, + vtkDataArray, + vtkImageData, + }; + + const RTSS = generateRTSSFromSegmentations( + segmentations, + classes.MetadataProvider, + DicomMetadataStore, + cache, + cornerstoneToolsEnums, + vtkUtils + ); + + try { + const reportBlob = datasetToBlob(RTSS); + + //Create a URL for the binary. + const objectUrl = URL.createObjectURL(reportBlob); + window.location.assign(objectUrl); + } catch (e) { + console.warn(e); + } + }, + }; + + const definitions = { + getUpdatedViewportsForSegmentation: { + commandFn: actions.getUpdatedViewportsForSegmentation, + }, + loadSegmentationDisplaySetsForViewport: { + commandFn: actions.loadSegmentationDisplaySetsForViewport, + }, + loadSegmentationsForViewport: { + commandFn: actions.loadSegmentationsForViewport, + }, + createEmptySegmentationForViewport: { + commandFn: actions.createEmptySegmentationForViewport, + }, + generateSegmentation: { + commandFn: actions.generateSegmentation, + }, + downloadSegmentation: { + commandFn: actions.downloadSegmentation, + }, + storeSegmentation: { + commandFn: actions.storeSegmentation, + }, + downloadRTSS: { + commandFn: actions.downloadRTSS, + }, + }; + + return { + actions, + definitions, + }; +}; + +export default commandsModule; diff --git a/extensions/cornerstone-dicom-seg/src/getHangingProtocolModule.ts b/extensions/cornerstone-dicom-seg/src/getHangingProtocolModule.ts index 0a2f888e4f8..f61136245d1 100644 --- a/extensions/cornerstone-dicom-seg/src/getHangingProtocolModule.ts +++ b/extensions/cornerstone-dicom-seg/src/getHangingProtocolModule.ts @@ -5,7 +5,6 @@ const segProtocol: Types.HangingProtocol.Protocol = { // Don't store this hanging protocol as it applies to the currently active // display set by default // cacheId: null, - hasUpdatedPriorsInformation: false, name: 'Segmentations', // Just apply this one when specifically listed protocolMatchingRules: [], diff --git a/extensions/cornerstone-dicom-seg/src/getPanelModule.tsx b/extensions/cornerstone-dicom-seg/src/getPanelModule.tsx new file mode 100644 index 00000000000..e626c87d7bf --- /dev/null +++ b/extensions/cornerstone-dicom-seg/src/getPanelModule.tsx @@ -0,0 +1,70 @@ +import React from 'react'; + +import { useAppConfig } from '@state'; +import PanelSegmentation from './panels/PanelSegmentation'; +import SegmentationToolbox from './panels/SegmentationToolbox'; + +const getPanelModule = ({ commandsManager, servicesManager, extensionManager, configuration }) => { + const { customizationService } = servicesManager.services; + + const wrappedPanelSegmentation = configuration => { + const [appConfig] = useAppConfig(); + + const disableEditingForMode = customizationService.get('segmentation.disableEditing'); + + return ( + + ); + }; + + const wrappedPanelSegmentationWithTools = configuration => { + const [appConfig] = useAppConfig(); + return ( + <> + + + + ); + }; + + return [ + { + name: 'panelSegmentation', + iconName: 'tab-segmentation', + iconLabel: 'Segmentation', + label: 'Segmentation', + component: wrappedPanelSegmentation, + }, + { + name: 'panelSegmentationWithTools', + iconName: 'tab-segmentation', + iconLabel: 'Segmentation', + label: 'Segmentation', + component: wrappedPanelSegmentationWithTools, + }, + ]; +}; + +export default getPanelModule; diff --git a/extensions/cornerstone-dicom-seg/src/getSopClassHandlerModule.js b/extensions/cornerstone-dicom-seg/src/getSopClassHandlerModule.js index b46b56e3671..d150201e06c 100644 --- a/extensions/cornerstone-dicom-seg/src/getSopClassHandlerModule.js +++ b/extensions/cornerstone-dicom-seg/src/getSopClassHandlerModule.js @@ -1,10 +1,6 @@ import { utils } from '@ohif/core'; -import { - metaData, - cache, - triggerEvent, - eventTarget, -} from '@cornerstonejs/core'; +import { metaData, cache, triggerEvent, eventTarget } from '@cornerstonejs/core'; +import { CONSTANTS } from '@cornerstonejs/tools'; import { adaptersSEG, Enums } from '@cornerstonejs/adapters'; import { SOPClassHandlerId } from './id'; @@ -14,11 +10,7 @@ const sopClassUids = ['1.2.840.10008.5.1.4.1.1.66.4']; let loadPromises = {}; -function _getDisplaySetsFromSeries( - instances, - servicesManager, - extensionManager -) { +function _getDisplaySetsFromSeries(instances, servicesManager, extensionManager) { const instance = instances[0]; const { @@ -66,13 +58,13 @@ function _getDisplaySetsFromSeries( const referencedSeriesSequence = instance.ReferencedSeriesSequence; if (!referencedSeriesSequence) { - throw new Error('ReferencedSeriesSequence is missing for the SEG'); + console.error('ReferencedSeriesSequence is missing for the SEG'); + return; } - const referencedSeries = referencedSeriesSequence[0]; + const referencedSeries = referencedSeriesSequence[0] || referencedSeriesSequence; - displaySet.referencedImages = - instance.ReferencedSeriesSequence.ReferencedInstanceSequence; + displaySet.referencedImages = instance.ReferencedSeriesSequence.ReferencedInstanceSequence; displaySet.referencedSeriesInstanceUID = referencedSeries.SeriesInstanceUID; displaySet.getReferenceDisplaySet = () => { @@ -87,8 +79,7 @@ function _getDisplaySetsFromSeries( const referencedDisplaySet = referencedDisplaySets[0]; - displaySet.referencedDisplaySetInstanceUID = - referencedDisplaySet.displaySetInstanceUID; + displaySet.referencedDisplaySetInstanceUID = referencedDisplaySet.displaySetInstanceUID; // Todo: this needs to be able to work with other reference volumes (other than streaming) such as nifti, etc. displaySet.referencedVolumeURI = referencedDisplaySet.displaySetInstanceUID; @@ -121,10 +112,7 @@ function _load(segDisplaySet, servicesManager, extensionManager, headers) { // We don't want to fire multiple loads, so we'll wait for the first to finish // and also return the same promise to any other callers. loadPromises[SOPInstanceUID] = new Promise(async (resolve, reject) => { - if ( - !segDisplaySet.segments || - Object.keys(segDisplaySet.segments).length === 0 - ) { + if (!segDisplaySet.segments || Object.keys(segDisplaySet.segments).length === 0) { await _loadSegments({ extensionManager, servicesManager, @@ -149,28 +137,17 @@ function _load(segDisplaySet, servicesManager, extensionManager, headers) { return loadPromises[SOPInstanceUID]; } -async function _loadSegments({ - extensionManager, - servicesManager, - segDisplaySet, - headers, -}) { +async function _loadSegments({ extensionManager, servicesManager, segDisplaySet, headers }) { const utilityModule = extensionManager.getModuleEntry( '@ohif/extension-cornerstone.utilityModule.common' ); - const { segmentationService } = servicesManager.services; + const { segmentationService, uiNotificationService } = servicesManager.services; const { dicomLoaderService } = utilityModule.exports; - const arrayBuffer = await dicomLoaderService.findDicomDataPromise( - segDisplaySet, - null, - headers - ); + const arrayBuffer = await dicomLoaderService.findDicomDataPromise(segDisplaySet, null, headers); - const cachedReferencedVolume = cache.getVolume( - segDisplaySet.referencedVolumeId - ); + const cachedReferencedVolume = cache.getVolume(segDisplaySet.referencedVolumeId); if (!cachedReferencedVolume) { throw new Error( @@ -186,12 +163,9 @@ async function _loadSegments({ eventTarget.addEventListener(Enums.Events.SEGMENTATION_LOAD_PROGRESS, evt => { const { percentComplete } = evt.detail; - segmentationService._broadcastEvent( - segmentationService.EVENTS.SEGMENT_LOADING_COMPLETE, - { - percentComplete, - } - ); + segmentationService._broadcastEvent(segmentationService.EVENTS.SEGMENT_LOADING_COMPLETE, { + percentComplete, + }); }); const results = await adaptersSEG.Cornerstone3D.Segmentation.generateToolState( @@ -201,29 +175,51 @@ async function _loadSegments({ { skipOverlapping, tolerance, eventTarget, triggerEvent } ); + let usedRecommendedDisplayCIELabValue = true; results.segMetadata.data.forEach((data, i) => { if (i > 0) { - data.rgba = dicomlabToRGB(data.RecommendedDisplayCIELabValue); + data.rgba = data.RecommendedDisplayCIELabValue; + + if (data.rgba) { + data.rgba = dicomlabToRGB(data.rgba); + } else { + usedRecommendedDisplayCIELabValue = false; + data.rgba = CONSTANTS.COLOR_LUT[i % CONSTANTS.COLOR_LUT.length]; + } } }); + 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({ + title: 'DICOM SEG import', + message: + 'RecommendedDisplayCIELabValue not found for one or more segments. The default color was used instead.', + type: 'warning', + duration: 5000, + }); + } + Object.assign(segDisplaySet, results); } function _segmentationExists(segDisplaySet, segmentationService) { // This should be abstracted with the CornerstoneCacheService - return segmentationService.getSegmentation( - segDisplaySet.displaySetInstanceUID - ); + return segmentationService.getSegmentation(segDisplaySet.displaySetInstanceUID); } function getSopClassHandlerModule({ servicesManager, extensionManager }) { const getDisplaySetsFromSeries = instances => { - return _getDisplaySetsFromSeries( - instances, - servicesManager, - extensionManager - ); + return _getDisplaySetsFromSeries(instances, servicesManager, extensionManager); }; return [ diff --git a/extensions/cornerstone-dicom-seg/src/index.tsx b/extensions/cornerstone-dicom-seg/src/index.tsx index 79ec4559459..bb6a6d4b118 100644 --- a/extensions/cornerstone-dicom-seg/src/index.tsx +++ b/extensions/cornerstone-dicom-seg/src/index.tsx @@ -1,17 +1,14 @@ import { id } from './id'; import React from 'react'; -import { Types } from '@ohif/core'; - import getSopClassHandlerModule from './getSopClassHandlerModule'; -import PanelSegmentation from './panels/PanelSegmentation'; import getHangingProtocolModule from './getHangingProtocolModule'; -import hydrateSEGDisplaySet from './utils/_hydrateSEG'; +import getPanelModule from './getPanelModule'; +import getCommandsModule from './commandsModule'; +import preRegistration from './init'; const Component = React.lazy(() => { - return import( - /* webpackPrefetch: true */ './viewports/OHIFCornerstoneSEGViewport' - ); + return import(/* webpackPrefetch: true */ './viewports/OHIFCornerstoneSEGViewport'); }); const OHIFCornerstoneSEGViewport = props => { @@ -31,6 +28,7 @@ const extension = { * You ID can be anything you want, but it should be unique. */ id, + preRegistration, /** * PanelModule should provide a list of panels that will be available in OHIF @@ -38,31 +36,8 @@ const extension = { * iconName, iconLabel, label, component} object. Example of a panel module * is the StudyBrowserPanel that is provided by the default extension in OHIF. */ - getPanelModule: ({ - servicesManager, - commandsManager, - extensionManager, - }): Types.Panel[] => { - const wrappedPanelSegmentation = () => { - return ( - - ); - }; - - return [ - { - name: 'panelSegmentation', - iconName: 'tab-segmentation', - iconLabel: 'Segmentation', - label: 'Segmentation', - component: wrappedPanelSegmentation, - }, - ]; - }, + getPanelModule, + getCommandsModule, getViewportModule({ servicesManager, extensionManager }) { const ExtendedOHIFCornerstoneSEGViewport = props => { @@ -76,9 +51,7 @@ const extension = { ); }; - return [ - { name: 'dicom-seg', component: ExtendedOHIFCornerstoneSEGViewport }, - ]; + return [{ name: 'dicom-seg', component: ExtendedOHIFCornerstoneSEGViewport }]; }, /** * SopClassHandlerModule should provide a list of sop class handlers that will be @@ -91,4 +64,3 @@ const extension = { }; export default extension; -export { hydrateSEGDisplaySet }; \ No newline at end of file diff --git a/extensions/cornerstone-dicom-seg/src/init.ts b/extensions/cornerstone-dicom-seg/src/init.ts new file mode 100644 index 00000000000..9702aa570b6 --- /dev/null +++ b/extensions/cornerstone-dicom-seg/src/init.ts @@ -0,0 +1,5 @@ +import { addTool, BrushTool } from '@cornerstonejs/tools'; + +export default function init({ configuration = {} }): void { + addTool(BrushTool); +} diff --git a/extensions/cornerstone-dicom-seg/src/panels/PanelSegmentation.tsx b/extensions/cornerstone-dicom-seg/src/panels/PanelSegmentation.tsx index bc645652a7f..ea1ab1de6ea 100644 --- a/extensions/cornerstone-dicom-seg/src/panels/PanelSegmentation.tsx +++ b/extensions/cornerstone-dicom-seg/src/panels/PanelSegmentation.tsx @@ -1,48 +1,28 @@ +import { createReportAsync } from '@ohif/extension-default'; import React, { useEffect, useState, useCallback } from 'react'; import PropTypes from 'prop-types'; import { SegmentationGroupTable } from '@ohif/ui'; -import callInputDialog from './callInputDialog'; +import callInputDialog from './callInputDialog'; +import callColorPickerDialog from './colorPickerDialog'; import { useTranslation } from 'react-i18next'; export default function PanelSegmentation({ servicesManager, commandsManager, + extensionManager, + configuration, }) { - const { segmentationService, uiDialogService } = servicesManager.services; + const { segmentationService, viewportGridService, uiDialogService } = servicesManager.services; const { t } = useTranslation('PanelSegmentation'); + const [selectedSegmentationId, setSelectedSegmentationId] = useState(null); const [segmentationConfiguration, setSegmentationConfiguration] = useState( segmentationService.getConfiguration() ); - const [segmentations, setSegmentations] = useState(() => - segmentationService.getSegmentations() - ); - - const [isMinimized, setIsMinimized] = useState({}); - - const onToggleMinimizeSegmentation = useCallback( - id => { - setIsMinimized(prevState => ({ - ...prevState, - [id]: !prevState[id], - })); - }, - [setIsMinimized] - ); - - // Only expand the last segmentation added to the list and collapse the rest - useEffect(() => { - const lastSegmentationId = segmentations[segmentations.length - 1]?.id; - if (lastSegmentationId) { - setIsMinimized(prevState => ({ - ...prevState, - [lastSegmentationId]: false, - })); - } - }, [segmentations, setIsMinimized]); + const [segmentations, setSegmentations] = useState(() => segmentationService.getSegmentations()); useEffect(() => { // ~~ Subscription @@ -67,6 +47,16 @@ export default function PanelSegmentation({ }; }, []); + const getToolGroupIds = segmentationId => { + const toolGroupIds = segmentationService.getToolGroupIdsWithSegmentation(segmentationId); + + return toolGroupIds; + }; + + const onSegmentationAdd = async () => { + commandsManager.runCommand('createEmptySegmentationForViewport'); + }; + const onSegmentationClick = (segmentationId: string) => { segmentationService.setActiveSegmentationForToolGroup(segmentationId); }; @@ -75,33 +65,19 @@ export default function PanelSegmentation({ segmentationService.remove(segmentationId); }; - const getToolGroupIds = segmentationId => { - const toolGroupIds = segmentationService.getToolGroupIdsWithSegmentation( - segmentationId - ); - - return toolGroupIds; + const onSegmentAdd = segmentationId => { + segmentationService.addSegment(segmentationId); }; const onSegmentClick = (segmentationId, segmentIndex) => { - segmentationService.setActiveSegmentForSegmentation( - segmentationId, - segmentIndex - ); + segmentationService.setActiveSegment(segmentationId, segmentIndex); const toolGroupIds = getToolGroupIds(segmentationId); toolGroupIds.forEach(toolGroupId => { // const toolGroupId = - segmentationService.setActiveSegmentationForToolGroup( - segmentationId, - toolGroupId - ); - segmentationService.jumpToSegmentCenter( - segmentationId, - segmentIndex, - toolGroupId - ); + segmentationService.setActiveSegmentationForToolGroup(segmentationId, toolGroupId); + segmentationService.jumpToSegmentCenter(segmentationId, segmentIndex, toolGroupId); }); }; @@ -116,11 +92,7 @@ export default function PanelSegmentation({ return; } - segmentationService.setSegmentLabelForSegmentation( - segmentationId, - segmentIndex, - label - ); + segmentationService.setSegmentLabel(segmentationId, segmentIndex, label); }); }; @@ -145,16 +117,34 @@ export default function PanelSegmentation({ }; const onSegmentColorClick = (segmentationId, segmentIndex) => { - // Todo: Implement color picker later - return; + const segmentation = segmentationService.getSegmentation(segmentationId); + + const segment = segmentation.segments[segmentIndex]; + const { color, opacity } = segment; + + const rgbaColor = { + r: color[0], + g: color[1], + b: color[2], + a: opacity / 255.0, + }; + + callColorPickerDialog(uiDialogService, rgbaColor, (newRgbaColor, actionId) => { + if (actionId === 'cancel') { + return; + } + + segmentationService.setSegmentRGBAColor(segmentationId, segmentIndex, [ + newRgbaColor.r, + newRgbaColor.g, + newRgbaColor.b, + newRgbaColor.a * 255.0, + ]); + }); }; const onSegmentDelete = (segmentationId, segmentIndex) => { - // segmentationService.removeSegmentFromSegmentation( - // segmentationId, - // segmentIndex - // ); - console.warn('not implemented yet'); + segmentationService.removeSegment(segmentationId, segmentIndex); }; const onToggleSegmentVisibility = (segmentationId, segmentIndex) => { @@ -174,6 +164,10 @@ export default function PanelSegmentation({ }); }; + const onToggleSegmentLock = (segmentationId, segmentIndex) => { + segmentationService.toggleSegmentLocked(segmentationId, segmentIndex); + }; + const onToggleSegmentationVisibility = segmentationId => { segmentationService.toggleSegmentationVisibility(segmentationId); }; @@ -188,47 +182,78 @@ export default function PanelSegmentation({ [segmentationService] ); + const onSegmentationDownload = segmentationId => { + commandsManager.runCommand('downloadSegmentation', { + segmentationId, + }); + }; + + const storeSegmentation = async segmentationId => { + const datasources = extensionManager.getActiveDataSource(); + + const displaySetInstanceUIDs = await createReportAsync({ + servicesManager, + getReport: () => + commandsManager.runCommand('storeSegmentation', { + segmentationId, + dataSource: datasources[0], + }), + reportType: 'Segmentation', + }); + + // Show the exported report in the active viewport as read only (similar to SR) + if (displaySetInstanceUIDs) { + // clear the segmentation that we exported, similar to the storeMeasurement + // where we remove the measurements and prompt again the user if they would like + // to re-read the measurements in a SR read only viewport + segmentationService.remove(segmentationId); + + viewportGridService.setDisplaySetsForViewport({ + viewportId: viewportGridService.getActiveViewportId(), + displaySetInstanceUIDs, + }); + } + }; + + const onSegmentationDownloadRTSS = segmentationId => { + commandsManager.runCommand('downloadRTSS', { + segmentationId, + }); + }; + return ( -
- {/* show segmentation table */} - {segmentations?.length ? ( + <> +
- _setSegmentationConfiguration( - selectedSegmentationId, - 'renderOutline', - value - ) + _setSegmentationConfiguration(selectedSegmentationId, 'renderOutline', value) } setOutlineOpacityActive={value => - _setSegmentationConfiguration( - selectedSegmentationId, - 'outlineOpacity', - value - ) + _setSegmentationConfiguration(selectedSegmentationId, 'outlineOpacity', value) } setRenderFill={value => - _setSegmentationConfiguration( - selectedSegmentationId, - 'renderFill', - value - ) + _setSegmentationConfiguration(selectedSegmentationId, 'renderFill', value) } setRenderInactiveSegmentations={value => _setSegmentationConfiguration( @@ -238,29 +263,17 @@ export default function PanelSegmentation({ ) } setOutlineWidthActive={value => - _setSegmentationConfiguration( - selectedSegmentationId, - 'outlineWidthActive', - value - ) + _setSegmentationConfiguration(selectedSegmentationId, 'outlineWidthActive', value) } setFillAlpha={value => - _setSegmentationConfiguration( - selectedSegmentationId, - 'fillAlpha', - value - ) + _setSegmentationConfiguration(selectedSegmentationId, 'fillAlpha', value) } setFillAlphaInactive={value => - _setSegmentationConfiguration( - selectedSegmentationId, - 'fillAlphaInactive', - value - ) + _setSegmentationConfiguration(selectedSegmentationId, 'fillAlphaInactive', value) } /> - ) : null} -
+
+ ); } diff --git a/extensions/cornerstone-dicom-seg/src/panels/SegmentationToolbox.tsx b/extensions/cornerstone-dicom-seg/src/panels/SegmentationToolbox.tsx new file mode 100644 index 00000000000..868a5279f85 --- /dev/null +++ b/extensions/cornerstone-dicom-seg/src/panels/SegmentationToolbox.tsx @@ -0,0 +1,425 @@ +import React, { useCallback, useEffect, useState, useReducer } from 'react'; +import { AdvancedToolbox, InputDoubleRange, useViewportGrid } from '@ohif/ui'; +import { Types } from '@ohif/extension-cornerstone'; +import { utilities } from '@cornerstonejs/tools'; + +const { segmentation: segmentationUtils } = utilities; + +const TOOL_TYPES = { + CIRCULAR_BRUSH: 'CircularBrush', + SPHERE_BRUSH: 'SphereBrush', + CIRCULAR_ERASER: 'CircularEraser', + SPHERE_ERASER: 'SphereEraser', + CIRCLE_SHAPE: 'CircleScissor', + RECTANGLE_SHAPE: 'RectangleScissor', + SPHERE_SHAPE: 'SphereScissor', + THRESHOLD_CIRCULAR_BRUSH: 'ThresholdCircularBrush', + THRESHOLD_SPHERE_BRUSH: 'ThresholdSphereBrush', +}; + +const ACTIONS = { + SET_TOOL_CONFIG: 'SET_TOOL_CONFIG', + SET_ACTIVE_TOOL: 'SET_ACTIVE_TOOL', +}; + +const initialState = { + Brush: { + brushSize: 15, + mode: 'CircularBrush', // Can be 'CircularBrush' or 'SphereBrush' + }, + Eraser: { + brushSize: 15, + mode: 'CircularEraser', // Can be 'CircularEraser' or 'SphereEraser' + }, + Shapes: { + brushSize: 15, + mode: 'CircleScissor', // E.g., 'CircleScissor', 'RectangleScissor', or 'SphereScissor' + }, + ThresholdBrush: { + brushSize: 15, + thresholdRange: null, + }, + activeTool: null, +}; + +function toolboxReducer(state, action) { + switch (action.type) { + case ACTIONS.SET_TOOL_CONFIG: + const { tool, config } = action.payload; + return { + ...state, + [tool]: { + ...state[tool], + ...config, + }, + }; + case ACTIONS.SET_ACTIVE_TOOL: + return { ...state, activeTool: action.payload }; + default: + return state; + } +} + +function SegmentationToolbox({ servicesManager, extensionManager }) { + const { toolbarService, segmentationService, toolGroupService } = + servicesManager.services as Types.CornerstoneServices; + + const [viewportGrid] = useViewportGrid(); + const { viewports, activeViewportId } = viewportGrid; + + const [toolsEnabled, setToolsEnabled] = useState(false); + const [state, dispatch] = useReducer(toolboxReducer, initialState); + + const updateActiveTool = useCallback(() => { + if (!viewports?.size || activeViewportId === undefined) { + return; + } + const viewport = viewports.get(activeViewportId); + + if (!viewport) { + return; + } + + dispatch({ + type: ACTIONS.SET_ACTIVE_TOOL, + payload: toolGroupService.getActiveToolForViewport(viewport.viewportId), + }); + }, [activeViewportId, viewports, toolGroupService, dispatch]); + + const setToolActive = useCallback( + toolName => { + initializeThresholdValue(toolName); + + toolbarService.recordInteraction({ + interactionType: 'tool', + commands: [ + { + commandName: 'setToolActive', + commandOptions: { + toolName, + }, + }, + ], + }); + + dispatch({ type: ACTIONS.SET_ACTIVE_TOOL, payload: toolName }); + }, + [toolbarService, dispatch] + ); + + /** + * sets the tools enabled IF there are segmentations + */ + useEffect(() => { + const events = [ + segmentationService.EVENTS.SEGMENTATION_ADDED, + segmentationService.EVENTS.SEGMENTATION_UPDATED, + segmentationService.EVENTS.SEGMENTATION_REMOVED, + ]; + + const unsubscriptions = []; + + events.forEach(event => { + const { unsubscribe } = segmentationService.subscribe(event, () => { + const segmentations = segmentationService.getSegmentations(); + + const activeSegmentation = segmentations?.find(seg => seg.isActive); + + setToolsEnabled(activeSegmentation?.segmentCount > 0); + }); + + unsubscriptions.push(unsubscribe); + }); + + updateActiveTool(); + + return () => { + unsubscriptions.forEach(unsubscribe => unsubscribe()); + }; + }, [activeViewportId, viewports, segmentationService, updateActiveTool]); + + /** + * Update the active tool when the toolbar state changes + */ + useEffect(() => { + const { unsubscribe } = toolbarService.subscribe( + toolbarService.EVENTS.TOOL_BAR_STATE_MODIFIED, + () => { + updateActiveTool(); + } + ); + + return () => { + unsubscribe(); + }; + }, [toolbarService, updateActiveTool]); + + useEffect(() => { + // if the active tool is not a brush tool then do nothing + if (!Object.values(TOOL_TYPES).includes(state.activeTool)) { + return; + } + + // if the tool is Segmentation and it is enabled then do nothing + if (toolsEnabled) { + return; + } + + // if the tool is Segmentation and it is disabled, then switch + // back to the window level tool to not confuse the user when no + // segmentation is active or when there is no segment in the segmentation + setToolActive('WindowLevel'); + }, [toolsEnabled, state.activeTool, setToolActive]); + + const updateBrushSize = useCallback( + (toolName, brushSize) => { + toolGroupService.getToolGroupIds()?.forEach(toolGroupId => { + segmentationUtils.setBrushSizeForToolGroup(toolGroupId, brushSize, toolName); + }); + }, + [toolGroupService] + ); + + function initializeThresholdValue(toolName: any) { + if (state.ThresholdBrush.thresholdRange === null) { + // set the default threshold range from the tool configuration + const toolGroupIds = toolGroupService.getToolGroupIds(); + const toolGroupId = toolGroupIds[0]; + const toolGroup = toolGroupService.getToolGroup(toolGroupId); + const toolConfig = toolGroup.getToolConfiguration(toolName); + const defaultThresholdRange = toolConfig?.strategySpecificConfiguration?.THRESHOLD?.threshold; + dispatch({ + type: ACTIONS.SET_TOOL_CONFIG, + payload: { + tool: 'ThresholdBrush', + config: { thresholdRange: defaultThresholdRange }, + }, + }); + } + } + + const onBrushSizeChange = useCallback( + (valueAsStringOrNumber, toolCategory) => { + const value = Number(valueAsStringOrNumber); + + _getToolNamesFromCategory(toolCategory).forEach(toolName => { + updateBrushSize(toolName, value); + }); + + dispatch({ + type: ACTIONS.SET_TOOL_CONFIG, + payload: { + tool: toolCategory, + config: { brushSize: value }, + }, + }); + }, + [toolGroupService, dispatch] + ); + + const handleRangeChange = useCallback( + newRange => { + if ( + newRange[0] === state.ThresholdBrush.thresholdRange?.[0] && + newRange[1] === state.ThresholdBrush.thresholdRange?.[1] + ) { + return; + } + + const toolNames = _getToolNamesFromCategory('ThresholdBrush'); + + toolNames.forEach(toolName => { + toolGroupService.getToolGroupIds()?.forEach(toolGroupId => { + const toolGroup = toolGroupService.getToolGroup(toolGroupId); + toolGroup.setToolConfiguration(toolName, { + strategySpecificConfiguration: { + THRESHOLD: { + threshold: newRange, + }, + }, + }); + }); + }); + + dispatch({ + type: ACTIONS.SET_TOOL_CONFIG, + payload: { + tool: 'ThresholdBrush', + config: { thresholdRange: newRange }, + }, + }); + }, + [toolGroupService, dispatch, state.ThresholdBrush.thresholdRange] + ); + + return ( + setToolActive(TOOL_TYPES.CIRCULAR_BRUSH), + options: [ + { + name: 'Radius (mm)', + id: 'brush-radius', + type: 'range', + min: 0.5, + max: 99.5, + value: state.Brush.brushSize, + step: 0.5, + onChange: value => onBrushSizeChange(value, 'Brush'), + }, + { + name: 'Mode', + type: 'radio', + id: 'brush-mode', + value: state.Brush.mode, + values: [ + { value: TOOL_TYPES.CIRCULAR_BRUSH, label: 'Circle' }, + { value: TOOL_TYPES.SPHERE_BRUSH, label: 'Sphere' }, + ], + onChange: value => setToolActive(value), + }, + ], + }, + { + name: 'Eraser', + icon: 'icon-tool-eraser', + disabled: !toolsEnabled, + active: + state.activeTool === TOOL_TYPES.CIRCULAR_ERASER || + state.activeTool === TOOL_TYPES.SPHERE_ERASER, + onClick: () => setToolActive(TOOL_TYPES.CIRCULAR_ERASER), + options: [ + { + name: 'Radius (mm)', + type: 'range', + id: 'eraser-radius', + min: 0.5, + max: 99.5, + value: state.Eraser.brushSize, + step: 0.5, + onChange: value => onBrushSizeChange(value, 'Eraser'), + }, + { + name: 'Mode', + type: 'radio', + id: 'eraser-mode', + value: state.Eraser.mode, + values: [ + { value: TOOL_TYPES.CIRCULAR_ERASER, label: 'Circle' }, + { value: TOOL_TYPES.SPHERE_ERASER, label: 'Sphere' }, + ], + onChange: value => setToolActive(value), + }, + ], + }, + { + name: 'Shapes', + icon: 'icon-tool-shape', + disabled: !toolsEnabled, + active: + state.activeTool === TOOL_TYPES.CIRCLE_SHAPE || + state.activeTool === TOOL_TYPES.RECTANGLE_SHAPE || + state.activeTool === TOOL_TYPES.SPHERE_SHAPE, + onClick: () => setToolActive(TOOL_TYPES.CIRCLE_SHAPE), + options: [ + { + name: 'Mode', + type: 'radio', + value: state.Shapes.mode, + id: 'shape-mode', + values: [ + { value: TOOL_TYPES.CIRCLE_SHAPE, label: 'Circle' }, + { value: TOOL_TYPES.RECTANGLE_SHAPE, label: 'Rectangle' }, + { value: TOOL_TYPES.SPHERE_SHAPE, label: 'Sphere' }, + ], + onChange: value => setToolActive(value), + }, + ], + }, + { + name: 'Threshold Tool', + icon: 'icon-tool-threshold', + disabled: !toolsEnabled, + active: + state.activeTool === TOOL_TYPES.THRESHOLD_CIRCULAR_BRUSH || + state.activeTool === TOOL_TYPES.THRESHOLD_SPHERE_BRUSH, + onClick: () => setToolActive(TOOL_TYPES.THRESHOLD_CIRCULAR_BRUSH), + options: [ + { + name: 'Radius (mm)', + id: 'threshold-radius', + type: 'range', + min: 0.5, + max: 99.5, + value: state.ThresholdBrush.brushSize, + step: 0.5, + onChange: value => onBrushSizeChange(value, 'ThresholdBrush'), + }, + { + name: 'Mode', + type: 'radio', + id: 'threshold-mode', + value: state.activeTool, + values: [ + { value: TOOL_TYPES.THRESHOLD_CIRCULAR_BRUSH, label: 'Circle' }, + { value: TOOL_TYPES.THRESHOLD_SPHERE_BRUSH, label: 'Sphere' }, + ], + onChange: value => setToolActive(value), + }, + { + type: 'custom', + id: 'segmentation-threshold-range', + children: () => { + return ( +
+
+
Threshold
+ +
+ ); + }, + }, + ], + }, + ]} + /> + ); +} + +function _getToolNamesFromCategory(category) { + let toolNames = []; + switch (category) { + case 'Brush': + toolNames = ['CircularBrush', 'SphereBrush']; + break; + case 'Eraser': + toolNames = ['CircularEraser', 'SphereEraser']; + break; + case 'ThresholdBrush': + toolNames = ['ThresholdCircularBrush', 'ThresholdSphereBrush']; + break; + default: + break; + } + + return toolNames; +} + +export default SegmentationToolbox; diff --git a/extensions/cornerstone-dicom-seg/src/panels/callInputDialog.tsx b/extensions/cornerstone-dicom-seg/src/panels/callInputDialog.tsx index 6909d93fcde..6de2470d8bc 100644 --- a/extensions/cornerstone-dicom-seg/src/panels/callInputDialog.tsx +++ b/extensions/cornerstone-dicom-seg/src/panels/callInputDialog.tsx @@ -39,7 +39,7 @@ function callInputDialog(uiDialogService, label, callback) { label="Enter the segment label" labelClassName="text-white text-[14px] leading-[1.2]" autoFocus - className="bg-black border-primary-main" + className="border-primary-main bg-black" type="text" value={value.label} onChange={event => { diff --git a/extensions/cornerstone-dicom-seg/src/panels/colorPickerDialog.css b/extensions/cornerstone-dicom-seg/src/panels/colorPickerDialog.css new file mode 100644 index 00000000000..1c6bb206701 --- /dev/null +++ b/extensions/cornerstone-dicom-seg/src/panels/colorPickerDialog.css @@ -0,0 +1,3 @@ +.chrome-picker { + background: #090c29 !important; +} diff --git a/extensions/cornerstone-dicom-seg/src/panels/colorPickerDialog.tsx b/extensions/cornerstone-dicom-seg/src/panels/colorPickerDialog.tsx new file mode 100644 index 00000000000..38e85efb29c --- /dev/null +++ b/extensions/cornerstone-dicom-seg/src/panels/colorPickerDialog.tsx @@ -0,0 +1,58 @@ +import React from 'react'; +import { Dialog } from '@ohif/ui'; +import { ChromePicker } from 'react-color'; + +import './colorPickerDialog.css'; + +function callColorPickerDialog(uiDialogService, rgbaColor, callback) { + const dialogId = 'pick-color'; + + const onSubmitHandler = ({ action, value }) => { + switch (action.id) { + case 'save': + callback(value.rgbaColor, action.id); + break; + case 'cancel': + callback('', action.id); + break; + } + uiDialogService.dismiss({ id: dialogId }); + }; + + if (uiDialogService) { + uiDialogService.create({ + id: dialogId, + centralize: true, + isDraggable: false, + showOverlay: true, + content: Dialog, + contentProps: { + title: 'Segment Color', + value: { rgbaColor }, + noCloseButton: true, + onClose: () => uiDialogService.dismiss({ id: dialogId }), + actions: [ + { id: 'cancel', text: 'Cancel', type: 'primary' }, + { id: 'save', text: 'Save', type: 'secondary' }, + ], + onSubmit: onSubmitHandler, + body: ({ value, setValue }) => { + const handleChange = color => { + setValue({ rgbaColor: color.rgb }); + }; + + return ( + + ); + }, + }, + }); + } +} + +export default callColorPickerDialog; diff --git a/extensions/cornerstone-dicom-seg/src/utils/_hydrateSEG.ts b/extensions/cornerstone-dicom-seg/src/utils/_hydrateSEG.ts deleted file mode 100644 index f6b1522be7d..00000000000 --- a/extensions/cornerstone-dicom-seg/src/utils/_hydrateSEG.ts +++ /dev/null @@ -1,69 +0,0 @@ -async function _hydrateSEGDisplaySet({ - segDisplaySet, - viewportIndex, - servicesManager, -}) { - const { - segmentationService, - hangingProtocolService, - viewportGridService, - } = servicesManager.services; - - const displaySetInstanceUID = segDisplaySet.referencedDisplaySetInstanceUID; - - let segmentationId = null; - - // We need the hydration to notify panels about the new segmentation added - const suppressEvents = false; - - segmentationId = await segmentationService.createSegmentationForSEGDisplaySet( - segDisplaySet, - segmentationId, - suppressEvents - ); - - segmentationService.hydrateSegmentation(segDisplaySet.displaySetInstanceUID); - - const { viewports } = viewportGridService.getState(); - - const updatedViewports = hangingProtocolService.getViewportsRequireUpdate( - viewportIndex, - displaySetInstanceUID - ); - - // Todo: fix this after we have a better way for stack viewport segmentations - - // check every viewport in the viewports to see if the displaySetInstanceUID - // is being displayed, if so we need to update the viewport to use volume viewport - // (if already is not using it) since Cornerstone3D currently only supports - // volume viewport for segmentation - viewports.forEach((viewport, index) => { - if (index === viewportIndex) { - return; - } - - const shouldDisplaySeg = segmentationService.shouldRenderSegmentation( - viewport.displaySetInstanceUIDs, - segDisplaySet.displaySetInstanceUID - ); - - if (shouldDisplaySeg) { - updatedViewports.push({ - viewportIndex: index, - displaySetInstanceUIDs: viewport.displaySetInstanceUIDs, - viewportOptions: { - initialImageOptions: { - preset: 'middle', - }, - }, - }); - } - }); - - // Do the entire update at once - viewportGridService.setDisplaySetsForViewports(updatedViewports); - - return true; -} - -export default _hydrateSEGDisplaySet; diff --git a/extensions/cornerstone-dicom-seg/src/utils/dicomlabToRGB.ts b/extensions/cornerstone-dicom-seg/src/utils/dicomlabToRGB.ts index a8668cf1278..34ce1e54f11 100644 --- a/extensions/cornerstone-dicom-seg/src/utils/dicomlabToRGB.ts +++ b/extensions/cornerstone-dicom-seg/src/utils/dicomlabToRGB.ts @@ -6,9 +6,7 @@ import dcmjs from 'dcmjs'; * @returns The RGB color as an array of three integers between 0 and 255. */ function dicomlabToRGB(cielab: number[]): number[] { - const rgb = dcmjs.data.Colors.dicomlab2RGB(cielab).map(x => - Math.round(x * 255) - ); + const rgb = dcmjs.data.Colors.dicomlab2RGB(cielab).map(x => Math.round(x * 255)); return rgb; } diff --git a/extensions/cornerstone-dicom-seg/src/utils/hydrationUtils.ts b/extensions/cornerstone-dicom-seg/src/utils/hydrationUtils.ts new file mode 100644 index 00000000000..fa3c6d47c86 --- /dev/null +++ b/extensions/cornerstone-dicom-seg/src/utils/hydrationUtils.ts @@ -0,0 +1,190 @@ +import { Enums, cache } from '@cornerstonejs/core'; + +/** + * Updates the viewports in preparation for rendering segmentations. + * Evaluates each viewport to determine which need modifications, + * then for those viewports, changes them to a volume type and ensures + * they are ready for segmentation rendering. + * + * @param {Object} params - Parameters for the function. + * @param params.viewportId - ID of the viewport to be updated. + * @param params.loadFn - Function to load the segmentation data. + * @param params.servicesManager - The services manager. + * @param params.referencedDisplaySetInstanceUID - Optional UID for the referenced display set instance. + * + * @returns Returns true upon successful update of viewports for segmentation rendering. + */ +async function updateViewportsForSegmentationRendering({ + viewportId, + loadFn, + servicesManager, + referencedDisplaySetInstanceUID, +}: { + viewportId: string; + loadFn: () => Promise; + servicesManager: any; + referencedDisplaySetInstanceUID?: string; +}) { + const { cornerstoneViewportService, segmentationService, viewportGridService } = + servicesManager.services; + + const viewport = getTargetViewport({ viewportId, viewportGridService }); + const targetViewportId = viewport.viewportOptions.viewportId; + + referencedDisplaySetInstanceUID = + referencedDisplaySetInstanceUID || viewport?.displaySetInstanceUIDs[0]; + + const updatedViewports = getUpdatedViewportsForSegmentation({ + servicesManager, + viewportId, + referencedDisplaySetInstanceUID, + }); + + // create Segmentation callback which needs to be waited until + // the volume is created (if coming from stack) + const createSegmentationForVolume = async () => { + const segmentationId = await loadFn(); + segmentationService.hydrateSegmentation(segmentationId); + }; + + // the reference volume that is used to draw the segmentation. so check if the + // volume exists in the cache (the target Viewport is already a volume viewport) + const volumeExists = Array.from(cache._volumeCache.keys()).some(volumeId => + volumeId.includes(referencedDisplaySetInstanceUID) + ); + + updatedViewports.forEach(async viewport => { + viewport.viewportOptions = { + ...viewport.viewportOptions, + viewportType: 'volume', + needsRerendering: true, + }; + const viewportId = viewport.viewportId; + + const csViewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); + const prevCamera = csViewport.getCamera(); + + // only run the createSegmentationForVolume for the targetViewportId + // since the rest will get handled by cornerstoneViewportService + if (volumeExists && viewportId === targetViewportId) { + await createSegmentationForVolume(); + return; + } + + const createNewSegmentationWhenVolumeMounts = async evt => { + const isTheActiveViewportVolumeMounted = evt.detail.volumeActors?.find(ac => + ac.uid.includes(referencedDisplaySetInstanceUID) + ); + + // Note: make sure to re-grab the viewport since it might have changed + // during the time it took for the volume to be mounted, for instance + // the stack viewport has been changed to a volume viewport + const volumeViewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); + volumeViewport.setCamera(prevCamera); + + volumeViewport.element.removeEventListener( + Enums.Events.VOLUME_VIEWPORT_NEW_VOLUME, + createNewSegmentationWhenVolumeMounts + ); + + if (!isTheActiveViewportVolumeMounted) { + // it means it is one of those other updated viewports so just update the camera + return; + } + + if (viewportId === targetViewportId) { + await createSegmentationForVolume(); + } + }; + + csViewport.element.addEventListener( + Enums.Events.VOLUME_VIEWPORT_NEW_VOLUME, + createNewSegmentationWhenVolumeMounts + ); + }); + + // Set the displaySets for the viewports that require to be updated + viewportGridService.setDisplaySetsForViewports(updatedViewports); + + return true; +} + +const getTargetViewport = ({ viewportId, viewportGridService }) => { + const { viewports, activeViewportId } = viewportGridService.getState(); + const targetViewportId = viewportId || activeViewportId; + + const viewport = viewports.get(targetViewportId); + + return viewport; +}; + +/** + * Retrieves a list of viewports that require updates in preparation for segmentation rendering. + * This function evaluates viewports based on their compatibility with the provided segmentation's + * frame of reference UID and appends them to the updated list if they should render the segmentation. + * + * @param {Object} params - Parameters for the function. + * @param params.viewportId - the ID of the viewport to be updated. + * @param params.servicesManager - The services manager + * @param params.referencedDisplaySetInstanceUID - Optional UID for the referenced display set instance. + * + * @returns {Array} Returns an array of viewports that require updates for segmentation rendering. + */ +function getUpdatedViewportsForSegmentation({ + viewportId, + servicesManager, + referencedDisplaySetInstanceUID, +}) { + const { hangingProtocolService, displaySetService, segmentationService, viewportGridService } = + servicesManager.services; + + const { viewports } = viewportGridService.getState(); + + const viewport = getTargetViewport({ viewportId, viewportGridService }); + const targetViewportId = viewport.viewportOptions.viewportId; + + const displaySetInstanceUIDs = viewports.get(targetViewportId).displaySetInstanceUIDs; + + const referenceDisplaySetInstanceUID = + referencedDisplaySetInstanceUID || displaySetInstanceUIDs[0]; + + const referencedDisplaySet = displaySetService.getDisplaySetByUID(referenceDisplaySetInstanceUID); + const segmentationFrameOfReferenceUID = referencedDisplaySet.instances[0].FrameOfReferenceUID; + + const updatedViewports = hangingProtocolService.getViewportsRequireUpdate( + targetViewportId, + referenceDisplaySetInstanceUID + ); + + viewports.forEach((viewport, viewportId) => { + if ( + targetViewportId === viewportId || + updatedViewports.find(v => v.viewportId === viewportId) + ) { + return; + } + + const shouldDisplaySeg = segmentationService.shouldRenderSegmentation( + viewport.displaySetInstanceUIDs, + segmentationFrameOfReferenceUID + ); + + if (shouldDisplaySeg) { + updatedViewports.push({ + viewportId, + displaySetInstanceUIDs: viewport.displaySetInstanceUIDs, + viewportOptions: { + viewportType: 'volume', + needsRerendering: true, + }, + }); + } + }); + return updatedViewports; +} + +export { + updateViewportsForSegmentationRendering, + getUpdatedViewportsForSegmentation, + getTargetViewport, +}; diff --git a/extensions/cornerstone-dicom-seg/src/utils/initSEGToolGroup.ts b/extensions/cornerstone-dicom-seg/src/utils/initSEGToolGroup.ts index e032542f133..8ddc088c6c8 100644 --- a/extensions/cornerstone-dicom-seg/src/utils/initSEGToolGroup.ts +++ b/extensions/cornerstone-dicom-seg/src/utils/initSEGToolGroup.ts @@ -1,12 +1,7 @@ -function createSEGToolGroupAndAddTools( - ToolGroupService, - customizationService, - toolGroupId -) { - const { tools } = - customizationService.get('cornerstone.overlayViewportTools') ?? {}; +function createSEGToolGroupAndAddTools(ToolGroupService, customizationService, toolGroupId) { + const { tools } = customizationService.get('cornerstone.overlayViewportTools') ?? {}; - return ToolGroupService.createToolGroupAndAddTools(toolGroupId, tools, {}); + return ToolGroupService.createToolGroupAndAddTools(toolGroupId, tools); } export default createSEGToolGroupAndAddTools; diff --git a/extensions/cornerstone-dicom-seg/src/utils/promptHydrateSEG.ts b/extensions/cornerstone-dicom-seg/src/utils/promptHydrateSEG.ts index 76b5ab53174..9f8c1dddf78 100644 --- a/extensions/cornerstone-dicom-seg/src/utils/promptHydrateSEG.ts +++ b/extensions/cornerstone-dicom-seg/src/utils/promptHydrateSEG.ts @@ -1,5 +1,4 @@ import { ButtonEnums } from '@ohif/ui'; -import hydrateSEGDisplaySet from './_hydrateSEG'; const RESPONSE = { NO_NEVER: -1, @@ -10,16 +9,14 @@ const RESPONSE = { function promptHydrateSEG({ servicesManager, segDisplaySet, - viewportIndex, + viewportId, preHydrateCallbacks, + hydrateSEGDisplaySet, }) { const { uiViewportDialogService } = servicesManager.services; - return new Promise(async function(resolve, reject) { - const promptResult = await _askHydrate( - uiViewportDialogService, - viewportIndex - ); + return new Promise(async function (resolve, reject) { + const promptResult = await _askHydrate(uiViewportDialogService, viewportId); if (promptResult === RESPONSE.HYDRATE_SEG) { preHydrateCallbacks?.forEach(callback => { @@ -28,8 +25,7 @@ function promptHydrateSEG({ const isHydrated = await hydrateSEGDisplaySet({ segDisplaySet, - viewportIndex, - servicesManager, + viewportId, }); resolve(isHydrated); @@ -37,8 +33,8 @@ function promptHydrateSEG({ }); } -function _askHydrate(uiViewportDialogService, viewportIndex) { - return new Promise(function(resolve, reject) { +function _askHydrate(uiViewportDialogService, viewportId) { + return new Promise(function (resolve, reject) { const message = 'Do you want to open this Segmentation?'; const actions = [ { @@ -58,7 +54,7 @@ function _askHydrate(uiViewportDialogService, viewportIndex) { }; uiViewportDialogService.show({ - viewportIndex, + viewportId, type: 'info', message, actions, diff --git a/extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx b/extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx index c5fb133de6d..0d14c68f52a 100644 --- a/extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx +++ b/extensions/cornerstone-dicom-seg/src/viewports/OHIFCornerstoneSEGViewport.tsx @@ -2,14 +2,9 @@ import PropTypes from 'prop-types'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import OHIF, { utils } from '@ohif/core'; -import { - LoadingIndicatorTotalPercent, - useViewportGrid, - ViewportActionBar, -} from '@ohif/ui'; +import { LoadingIndicatorTotalPercent, useViewportGrid, ViewportActionBar } from '@ohif/ui'; import createSEGToolGroupAndAddTools from '../utils/initSEGToolGroup'; import promptHydrateSEG from '../utils/promptHydrateSEG'; -import hydrateSEGDisplaySet from '../utils/_hydrateSEG'; import _getStatusComponent from './_getStatusComponent'; const { formatDate } = utils; @@ -20,7 +15,6 @@ function OHIFCornerstoneSEGViewport(props) { children, displaySets, viewportOptions, - viewportIndex, viewportLabel, servicesManager, extensionManager, @@ -28,6 +22,7 @@ function OHIFCornerstoneSEGViewport(props) { } = props; const { t } = useTranslation('SEGViewport'); + const viewportId = viewportOptions.viewportId; const { displaySetService, @@ -37,7 +32,7 @@ function OHIFCornerstoneSEGViewport(props) { customizationService, } = servicesManager.services; - const toolGroupId = `${SEG_TOOLGROUP_BASE_NAME}-${viewportIndex}`; + const toolGroupId = `${SEG_TOOLGROUP_BASE_NAME}-${viewportId}`; // SEG viewport will always have a single display set if (displaySets.length > 1) { @@ -67,7 +62,7 @@ function OHIFCornerstoneSEGViewport(props) { // refs const referencedDisplaySetRef = useRef(null); - const { viewports, activeViewportIndex } = viewportGrid; + const { viewports, activeViewportId } = viewportGrid; const referencedDisplaySet = segDisplaySet.getReferenceDisplaySet(); const referencedDisplaySetMetadata = _getReferencedDisplaySetMetadata( @@ -94,9 +89,9 @@ function OHIFCornerstoneSEGViewport(props) { }; const storePresentationState = useCallback(() => { - viewportGrid?.viewports.forEach(({ viewportIndex }) => { + viewportGrid?.viewports.forEach(({ viewportId }) => { commandsManager.runCommand('storePresentation', { - viewportIndex, + viewportId, }); }); }, [viewportGrid]); @@ -106,9 +101,7 @@ function OHIFCornerstoneSEGViewport(props) { '@ohif/extension-cornerstone.viewportModule.cornerstone' ); - const { - displaySet: referencedDisplaySet, - } = referencedDisplaySetRef.current; + const { displaySet: referencedDisplaySet } = referencedDisplaySetRef.current; // Todo: jump to the center of the first segment return ( @@ -123,10 +116,9 @@ function OHIFCornerstoneSEGViewport(props) { }} onElementEnabled={onElementEnabled} onElementDisabled={onElementDisabled} - // initialImageIndex={initialImageIndex} > ); - }, [viewportIndex, segDisplaySet, toolGroupId]); + }, [viewportId, segDisplaySet, toolGroupId]); const onSegmentChange = useCallback( direction => { @@ -148,11 +140,7 @@ function OHIFCornerstoneSEGViewport(props) { newSelectedSegmentIndex = numberOfSegments - 1; } - segmentationService.jumpToSegmentCenter( - segmentationId, - newSelectedSegmentIndex, - toolGroupId - ); + segmentationService.jumpToSegmentCenter(segmentationId, newSelectedSegmentIndex, toolGroupId); setSelectedSegment(newSelectedSegmentIndex); }, [selectedSegment] @@ -165,35 +153,24 @@ function OHIFCornerstoneSEGViewport(props) { promptHydrateSEG({ servicesManager, - viewportIndex, + viewportId, segDisplaySet, preHydrateCallbacks: [storePresentationState], + hydrateSEGDisplaySet, }).then(isHydrated => { if (isHydrated) { setIsHydrated(true); } }); - }, [servicesManager, viewportIndex, segDisplaySet, segIsLoading]); + }, [servicesManager, viewportId, segDisplaySet, segIsLoading]); useEffect(() => { const { unsubscribe } = segmentationService.subscribe( segmentationService.EVENTS.SEGMENTATION_LOADING_COMPLETE, evt => { - if ( - evt.segDisplaySet.displaySetInstanceUID === - segDisplaySet.displaySetInstanceUID - ) { + if (evt.segDisplaySet.displaySetInstanceUID === segDisplaySet.displaySetInstanceUID) { setSegIsLoading(false); } - - if (evt.overlappingSegments) { - uiNotificationService.show({ - title: 'Overlapping Segments', - message: - 'Overlapping segments detected which is not currently supported', - type: 'warning', - }); - } } ); @@ -225,12 +202,10 @@ function OHIFCornerstoneSEGViewport(props) { const onDisplaySetsRemovedSubscription = displaySetService.subscribe( displaySetService.EVENTS.DISPLAY_SETS_REMOVED, ({ displaySetInstanceUIDs }) => { - const activeViewport = viewports[activeViewportIndex]; - if ( - displaySetInstanceUIDs.includes(activeViewport.displaySetInstanceUID) - ) { + const activeViewport = viewports.get(activeViewportId); + if (displaySetInstanceUIDs.includes(activeViewport.displaySetInstanceUID)) { viewportGridService.setDisplaySetsForViewport({ - viewportIndex: activeViewportIndex, + viewportId: activeViewportId, displaySetInstanceUIDs: [], }); } @@ -251,17 +226,11 @@ function OHIFCornerstoneSEGViewport(props) { // This creates a custom tool group which has the lifetime of this view // only, and does NOT interfere with currently displayed segmentations. - toolGroup = createSEGToolGroupAndAddTools( - toolGroupService, - customizationService, - toolGroupId - ); + toolGroup = createSEGToolGroupAndAddTools(toolGroupService, customizationService, toolGroupId); return () => { // remove the segmentation representations if seg displayset changed - segmentationService.removeSegmentationRepresentationFromToolGroup( - toolGroupId - ); + segmentationService.removeSegmentationRepresentationFromToolGroup(toolGroupId); // Only destroy the viewport specific implementation toolGroupService.destroyToolGroup(toolGroupId); @@ -273,9 +242,7 @@ function OHIFCornerstoneSEGViewport(props) { return () => { // remove the segmentation representations if seg displayset changed - segmentationService.removeSegmentationRepresentationFromToolGroup( - toolGroupId - ); + segmentationService.removeSegmentationRepresentationFromToolGroup(toolGroupId); referencedDisplaySetRef.current = null; }; }, [segDisplaySet]); @@ -296,7 +263,7 @@ function OHIFCornerstoneSEGViewport(props) { return ( child && React.cloneElement(child, { - viewportIndex, + viewportId, key: index, }) ); @@ -315,6 +282,13 @@ function OHIFCornerstoneSEGViewport(props) { SpacingBetweenSlices, } = referencedDisplaySetRef.current.metadata; + const hydrateSEGDisplaySet = ({ segDisplaySet, viewportId }) => { + commandsManager.runCommand('loadSegmentationDisplaySetsForViewport', { + displaySets: [segDisplaySet], + viewportId, + }); + }; + const onStatusClick = async () => { // Before hydrating a SEG and make it added to all viewports in the grid // that share the same frameOfReferenceUID, we need to store the viewport grid @@ -325,8 +299,7 @@ function OHIFCornerstoneSEGViewport(props) { storePresentationState(); const isHydrated = await hydrateSEGDisplaySet({ segDisplaySet, - viewportIndex, - servicesManager, + viewportId, }); setIsHydrated(isHydrated); @@ -351,29 +324,23 @@ function OHIFCornerstoneSEGViewport(props) { studyDate: formatDate(StudyDate), seriesDescription: `SEG Viewport ${SeriesDescription}`, patientInformation: { - patientName: PatientName - ? OHIF.utils.formatPN(PatientName.Alphabetic) - : '', + patientName: PatientName ? OHIF.utils.formatPN(PatientName.Alphabetic) : '', patientSex: PatientSex || '', patientAge: PatientAge || '', MRN: PatientID || '', - thickness: SliceThickness - ? utils.roundNumber(SliceThickness, 2) - : '', + thickness: SliceThickness ? utils.roundNumber(SliceThickness, 2) : '', thicknessUnits: SliceThickness !== undefined ? 'mm' : '', spacing: - SpacingBetweenSlices !== undefined - ? utils.roundNumber(SpacingBetweenSlices, 2) - : '', + SpacingBetweenSlices !== undefined ? utils.roundNumber(SpacingBetweenSlices, 2) : '', scanner: ManufacturerModelName || '', }, }} /> -
+
{segIsLoading && ( ; - ToolTipMessage = () => ( -
This Segmentation is loaded in the segmentation panel
- ); + ToolTipMessage = () =>
This Segmentation is loaded in the segmentation panel
; break; case false: - StatusIcon = () => ; + StatusIcon = () => ( + + ); ToolTipMessage = () =>
Click LOAD to load segmentation.
; } const StatusArea = () => ( -
-
+
+
SEG
{!isHydrated && (
@@ -44,7 +47,10 @@ export default function _getStatusComponent({ isHydrated, onStatusClick }) { return ( <> {ToolTipMessage && ( - } position="bottom-left"> + } + position="bottom-left" + > )} diff --git a/extensions/cornerstone-dicom-sr/.webpack/webpack.prod.js b/extensions/cornerstone-dicom-sr/.webpack/webpack.prod.js index 76dcd9ecfaf..9e07dd8ac16 100644 --- a/extensions/cornerstone-dicom-sr/.webpack/webpack.prod.js +++ b/extensions/cornerstone-dicom-sr/.webpack/webpack.prod.js @@ -41,13 +41,7 @@ module.exports = (env, argv) => { libraryTarget: 'umd', filename: pkg.main, }, - externals: [ - /\b(vtk.js)/, - /\b(dcmjs)/, - /\b(gl-matrix)/, - /^@ohif/, - /^@cornerstonejs/, - ], + externals: [/\b(vtk.js)/, /\b(dcmjs)/, /\b(gl-matrix)/, /^@ohif/, /^@cornerstonejs/], plugins: [ new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1, diff --git a/extensions/cornerstone-dicom-sr/CHANGELOG.md b/extensions/cornerstone-dicom-sr/CHANGELOG.md new file mode 100644 index 00000000000..fc809bb4775 --- /dev/null +++ b/extensions/cornerstone-dicom-sr/CHANGELOG.md @@ -0,0 +1,983 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [3.8.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.59...v3.8.0-beta.60) (2024-03-15) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.58...v3.8.0-beta.59) (2024-03-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.57...v3.8.0-beta.58) (2024-03-05) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.56...v3.8.0-beta.57) (2024-02-28) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.55...v3.8.0-beta.56) (2024-02-22) + + +### Bug Fixes + +* **demo:** Deploy issue ([#3951](https://github.com/OHIF/Viewers/issues/3951)) ([21e8a2b](https://github.com/OHIF/Viewers/commit/21e8a2bd0b7cc72f90a31e472d285d761be15d30)) + + + + + +# [3.8.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.54...v3.8.0-beta.55) (2024-02-21) + + +### Features + +* **resize:** Optimize resizing process and maintain zoom level ([#3889](https://github.com/OHIF/Viewers/issues/3889)) ([b3a0faf](https://github.com/OHIF/Viewers/commit/b3a0faf5f5f0a1993b2b017eb4cc1216164ea2c6)) + + + + + +# [3.8.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.53...v3.8.0-beta.54) (2024-02-14) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.52...v3.8.0-beta.53) (2024-02-05) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.51...v3.8.0-beta.52) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.50...v3.8.0-beta.51) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.49...v3.8.0-beta.50) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.48...v3.8.0-beta.49) (2024-01-19) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.47...v3.8.0-beta.48) (2024-01-17) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.46...v3.8.0-beta.47) (2024-01-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.45...v3.8.0-beta.46) (2024-01-12) + + +### Bug Fixes + +* Update CS3D to fix second render ([#3892](https://github.com/OHIF/Viewers/issues/3892)) ([d00a86b](https://github.com/OHIF/Viewers/commit/d00a86b022742ea089d246d06cfd691f43b64412)) + + + + + +# [3.8.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.44...v3.8.0-beta.45) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.43...v3.8.0-beta.44) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.42...v3.8.0-beta.43) (2024-01-09) + + +### Bug Fixes + +* **segmentation:** upgrade cs3d to fix various segmentation bugs ([#3885](https://github.com/OHIF/Viewers/issues/3885)) ([b1efe40](https://github.com/OHIF/Viewers/commit/b1efe40aa146e4052cc47b3f774cabbb47a8d1a6)) + + + + + +# [3.8.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.41...v3.8.0-beta.42) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.40...v3.8.0-beta.41) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.39...v3.8.0-beta.40) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.38...v3.8.0-beta.39) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.37...v3.8.0-beta.38) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.36...v3.8.0-beta.37) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.35...v3.8.0-beta.36) (2023-12-15) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.34...v3.8.0-beta.35) (2023-12-14) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.33...v3.8.0-beta.34) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.32...v3.8.0-beta.33) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.31...v3.8.0-beta.32) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.30...v3.8.0-beta.31) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.29...v3.8.0-beta.30) (2023-12-13) + + +### Features + +* **customizationService:** Enable saving and loading of private tags in SRs ([#3842](https://github.com/OHIF/Viewers/issues/3842)) ([e1f55e6](https://github.com/OHIF/Viewers/commit/e1f55e65f2d2a34136ad5d0b1ada77d337a0ea23)) + + + + + +# [3.8.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.28...v3.8.0-beta.29) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.27...v3.8.0-beta.28) (2023-12-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.26...v3.8.0-beta.27) (2023-12-06) + + +### Bug Fixes + +* **auth:** fix the issue with oauth at a non root path ([#3840](https://github.com/OHIF/Viewers/issues/3840)) ([6651008](https://github.com/OHIF/Viewers/commit/6651008fbb35dabd5991c7f61128e6ef324012df)) + + + + + +# [3.8.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.25...v3.8.0-beta.26) (2023-11-28) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.24...v3.8.0-beta.25) (2023-11-27) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.23...v3.8.0-beta.24) (2023-11-24) + + +### Bug Fixes + +* Update the CS3D packages to add the most recent HTJ2K TSUIDS ([#3806](https://github.com/OHIF/Viewers/issues/3806)) ([9d1884d](https://github.com/OHIF/Viewers/commit/9d1884d7d8b6b2a1cdc26965a96995838aa72682)) + + + + + +# [3.8.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.22...v3.8.0-beta.23) (2023-11-24) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.21...v3.8.0-beta.22) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.20...v3.8.0-beta.21) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.19...v3.8.0-beta.20) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.18...v3.8.0-beta.19) (2023-11-18) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.17...v3.8.0-beta.18) (2023-11-15) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.16...v3.8.0-beta.17) (2023-11-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.15...v3.8.0-beta.16) (2023-11-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.14...v3.8.0-beta.15) (2023-11-10) + + +### Features + +* **dicomJSON:** Add Loading Other Display Sets and JSON Metadata Generation script ([#3777](https://github.com/OHIF/Viewers/issues/3777)) ([43b1c17](https://github.com/OHIF/Viewers/commit/43b1c17209502e4876ad59bae09ed9442eda8024)) + + + + + +# [3.8.0-beta.14](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.13...v3.8.0-beta.14) (2023-11-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.13](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.12...v3.8.0-beta.13) (2023-11-09) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.12](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.11...v3.8.0-beta.12) (2023-11-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.11](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.10...v3.8.0-beta.11) (2023-11-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.10](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.9...v3.8.0-beta.10) (2023-11-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.9](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.8...v3.8.0-beta.9) (2023-11-02) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.8](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.7...v3.8.0-beta.8) (2023-10-31) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.7](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.6...v3.8.0-beta.7) (2023-10-30) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.6](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.5...v3.8.0-beta.6) (2023-10-25) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.5](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.4...v3.8.0-beta.5) (2023-10-24) + + +### Bug Fixes + +* **sr:** dcm4chee requires the patient name for an SR to match what is in the original study ([#3739](https://github.com/OHIF/Viewers/issues/3739)) ([d98439f](https://github.com/OHIF/Viewers/commit/d98439fe7f3825076dbc87b664a1d1480ff414d3)) + + + + + +# [3.8.0-beta.4](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.3...v3.8.0-beta.4) (2023-10-23) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.3](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.2...v3.8.0-beta.3) (2023-10-23) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.1...v3.8.0-beta.2) (2023-10-19) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.0...v3.8.0-beta.1) (2023-10-19) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.8.0-beta.0](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.110...v3.8.0-beta.0) (2023-10-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.110](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.109...v3.7.0-beta.110) (2023-10-11) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.109](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.108...v3.7.0-beta.109) (2023-10-11) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.107...v3.7.0-beta.108) (2023-10-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.106...v3.7.0-beta.107) (2023-10-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.105...v3.7.0-beta.106) (2023-10-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.104...v3.7.0-beta.105) (2023-10-10) + + +### Bug Fixes + +* **voi:** should publish voi change event on reset ([#3707](https://github.com/OHIF/Viewers/issues/3707)) ([52f34c6](https://github.com/OHIF/Viewers/commit/52f34c64d014f433ec1661a39b47e7fb27f15332)) + + + + + +# [3.7.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.103...v3.7.0-beta.104) (2023-10-09) + + +### Bug Fixes + +* **modality unit:** fix the modality unit per target via upgrade of cs3d ([#3706](https://github.com/OHIF/Viewers/issues/3706)) ([0a42d57](https://github.com/OHIF/Viewers/commit/0a42d573bbca7f2551a831a46d3aa6b56674a580)) + + + + + +# [3.7.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.102...v3.7.0-beta.103) (2023-10-09) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.101...v3.7.0-beta.102) (2023-10-06) + + +### Features + +* **Segmentation:** download RTSS from Labelmap([#3692](https://github.com/OHIF/Viewers/issues/3692)) ([40673f6](https://github.com/OHIF/Viewers/commit/40673f64b36b1150149c55632aa1825178a39e65)) + + + + + +# [3.7.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.100...v3.7.0-beta.101) (2023-10-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.99...v3.7.0-beta.100) (2023-10-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.98...v3.7.0-beta.99) (2023-10-04) + + +### Bug Fixes + +* **measurement and microscopy:** various small fixes for measurement and microscopy side panel ([#3696](https://github.com/OHIF/Viewers/issues/3696)) ([c1d5ee7](https://github.com/OHIF/Viewers/commit/c1d5ee7e3f7f4c0c6bed9ae81eba5519741c5155)) + + + + + +# [3.7.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.97...v3.7.0-beta.98) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.96...v3.7.0-beta.97) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.95...v3.7.0-beta.96) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.94...v3.7.0-beta.95) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.93...v3.7.0-beta.94) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.92...v3.7.0-beta.93) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.91...v3.7.0-beta.92) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.90...v3.7.0-beta.91) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.89...v3.7.0-beta.90) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.88...v3.7.0-beta.89) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.87...v3.7.0-beta.88) (2023-10-03) + + +### Bug Fixes + +* **config:** support more values for the useSharedArrayBuffer ([#3688](https://github.com/OHIF/Viewers/issues/3688)) ([1129c15](https://github.com/OHIF/Viewers/commit/1129c155d2c7d46c98a5df7c09879aa3d459fa7e)) + + + + + +# [3.7.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.86...v3.7.0-beta.87) (2023-09-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.85...v3.7.0-beta.86) (2023-09-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22) + + +### Features + +* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1)) + + + + + +# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22) + + +### Performance Improvements + +* **memory:** add 16 bit texture via configuration - reduces memory by half ([#3662](https://github.com/OHIF/Viewers/issues/3662)) ([2bd3b26](https://github.com/OHIF/Viewers/commit/2bd3b26a6aa54b211ef988f3ad64ef1fe5648bab)) + + + + + +# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06) + + +### Features + +* **ImageOverlayViewerTool:** add ImageOverlayViewer tool that can render image overlay (pixel overlay) of the DICOM images ([#3163](https://github.com/OHIF/Viewers/issues/3163)) ([69115da](https://github.com/OHIF/Viewers/commit/69115da06d2d437b57e66608b435bb0bc919a90f)) + + + + + +# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01) + + +### Features + +* **grid:** remove viewportIndex and only rely on viewportId ([#3591](https://github.com/OHIF/Viewers/issues/3591)) ([4c6ff87](https://github.com/OHIF/Viewers/commit/4c6ff873e887cc30ffc09223f5cb99e5f94c9cdd)) + + + + + +# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30) + + +### Features + +* **data source UI config:** Popup the configuration dialogue whenever a data source is not fully configured ([#3620](https://github.com/OHIF/Viewers/issues/3620)) ([adedc8c](https://github.com/OHIF/Viewers/commit/adedc8c382e18a2e86a569e3d023cc55a157363f)) + + + + + +# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone-dicom-sr + + + + + +# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25) + + +### Features + +* **cloud data source config:** GUI and API for configuring a cloud data source with Google cloud healthcare implementation ([#3589](https://github.com/OHIF/Viewers/issues/3589)) ([a336992](https://github.com/OHIF/Viewers/commit/a336992971c07552c9dbb6e1de43169d37762ef1)) + + + + + +# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23) + + +### Bug Fixes + +* **memory leak:** array buffer was sticking around in volume viewports ([#3611](https://github.com/OHIF/Viewers/issues/3611)) ([65b49ae](https://github.com/OHIF/Viewers/commit/65b49aeb1b5f38224e4892bdf32453500ee351f8)) diff --git a/extensions/cornerstone-dicom-sr/package.json b/extensions/cornerstone-dicom-sr/package.json index 3b8d3b91086..cef94db8bfd 100644 --- a/extensions/cornerstone-dicom-sr/package.json +++ b/extensions/cornerstone-dicom-sr/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-cornerstone-dicom-sr", - "version": "3.7.0-beta.46", + "version": "3.8.0-beta.60", "description": "OHIF extension for an SR Cornerstone Viewport", "author": "OHIF", "license": "MIT", @@ -23,6 +23,8 @@ "ohif-extension" ], "scripts": { + "clean": "shx rm -rf dist", + "clean:deep": "yarn run clean && shx rm -rf node_modules", "dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --output-pathinfo", "dev:cornerstone": "yarn run dev", "build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js", @@ -32,11 +34,11 @@ "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" }, "peerDependencies": { - "@ohif/core": "3.7.0-beta.46", - "@ohif/extension-cornerstone": "3.7.0-beta.46", - "@ohif/extension-measurement-tracking": "3.7.0-beta.46", - "@ohif/ui": "3.7.0-beta.46", - "dcmjs": "^0.29.5", + "@ohif/core": "3.8.0-beta.60", + "@ohif/extension-cornerstone": "3.8.0-beta.60", + "@ohif/extension-measurement-tracking": "3.8.0-beta.60", + "@ohif/ui": "3.8.0-beta.60", + "dcmjs": "^0.29.12", "dicom-parser": "^1.8.9", "hammerjs": "^2.0.8", "prop-types": "^15.6.2", @@ -44,9 +46,9 @@ }, "dependencies": { "@babel/runtime": "^7.20.13", - "@cornerstonejs/adapters": "^1.9.3", - "@cornerstonejs/core": "^1.9.3", - "@cornerstonejs/tools": "^1.9.3", + "@cornerstonejs/adapters": "^1.63.4", + "@cornerstonejs/core": "^1.63.4", + "@cornerstonejs/tools": "^1.63.4", "classnames": "^2.3.2" } } diff --git a/extensions/cornerstone-dicom-sr/src/commandsModule.js b/extensions/cornerstone-dicom-sr/src/commandsModule.js index f8c9459e4dc..33e8a869f18 100644 --- a/extensions/cornerstone-dicom-sr/src/commandsModule.js +++ b/extensions/cornerstone-dicom-sr/src/commandsModule.js @@ -17,11 +17,7 @@ const { log } = OHIF; * @param options Naturalized DICOM JSON headers to merge into the displaySet. * */ -const _generateReport = ( - measurementData, - additionalFindingTypes, - options = {} -) => { +const _generateReport = (measurementData, additionalFindingTypes, options = {}) => { const filteredToolState = getFilteredCornerstoneToolState( measurementData, additionalFindingTypes @@ -44,7 +40,9 @@ const _generateReport = ( return dataset; }; -const commandsModule = ({}) => { +const commandsModule = props => { + const { servicesManager } = props; + const { customizationService } = servicesManager.services; const actions = { /** * @@ -54,16 +52,8 @@ const commandsModule = ({}) => { * as opposed to Finding Sites. * that you wish to serialize. */ - downloadReport: ({ - measurementData, - additionalFindingTypes, - options = {}, - }) => { - const srDataset = actions.generateReport( - measurementData, - additionalFindingTypes, - options - ); + downloadReport: ({ measurementData, additionalFindingTypes, options = {} }) => { + const srDataset = actions.generateReport(measurementData, additionalFindingTypes, options); const reportBlob = dcmjs.data.datasetToBlob(srDataset); //Create a URL for the binary. @@ -91,32 +81,31 @@ const commandsModule = ({}) => { log.info('[DICOMSR] storeMeasurements'); if (!dataSource || !dataSource.store || !dataSource.store.dicom) { - log.error( - '[DICOMSR] datasource has no dataSource.store.dicom endpoint!' - ); + log.error('[DICOMSR] datasource has no dataSource.store.dicom endpoint!'); return Promise.reject({}); } try { - const naturalizedReport = _generateReport( - measurementData, - additionalFindingTypes, - options - ); + const naturalizedReport = _generateReport(measurementData, additionalFindingTypes, options); const { StudyInstanceUID, ContentSequence } = naturalizedReport; // The content sequence has 5 or more elements, of which // the `[4]` element contains the annotation data, so this is // checking that there is some annotation data present. if (!ContentSequence?.[4].ContentSequence?.length) { - console.log( - 'naturalizedReport missing imaging content', - naturalizedReport - ); + console.log('naturalizedReport missing imaging content', naturalizedReport); throw new Error('Invalid report, no content'); } - await dataSource.store.dicom(naturalizedReport); + const onBeforeDicomStore = + customizationService.getModeCustomization('onBeforeDicomStore')?.value; + + let dicomDict; + if (typeof onBeforeDicomStore === 'function') { + dicomDict = onBeforeDicomStore({ measurementData, naturalizedReport }); + } + + await dataSource.store.dicom(naturalizedReport, null, dicomDict); if (StudyInstanceUID) { dataSource.deleteStudyMetadataPromise(StudyInstanceUID); @@ -130,12 +119,8 @@ const commandsModule = ({}) => { return naturalizedReport; } catch (error) { console.warn(error); - log.error( - `[DICOMSR] Error while saving the measurements: ${error.message}` - ); - throw new Error( - error.message || 'Error while saving the measurements.' - ); + log.error(`[DICOMSR] Error while saving the measurements: ${error.message}`); + throw new Error(error.message || 'Error while saving the measurements.'); } }, }; diff --git a/extensions/cornerstone-dicom-sr/src/getHangingProtocolModule.ts b/extensions/cornerstone-dicom-sr/src/getHangingProtocolModule.ts index e9fcf31be59..8d47aca0c58 100644 --- a/extensions/cornerstone-dicom-sr/src/getHangingProtocolModule.ts +++ b/extensions/cornerstone-dicom-sr/src/getHangingProtocolModule.ts @@ -5,7 +5,6 @@ const srProtocol: Types.HangingProtocol.Protocol = { // Don't store this hanging protocol as it applies to the currently active // display set by default // cacheId: null, - hasUpdatedPriorsInformation: false, name: 'SR Key Images', // Just apply this one when specifically listed protocolMatchingRules: [], diff --git a/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.ts b/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.ts index 9601acb0b94..fbeaa531643 100644 --- a/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.ts +++ b/extensions/cornerstone-dicom-sr/src/getSopClassHandlerModule.ts @@ -29,9 +29,7 @@ const validateSameStudyUID = (uid: string, instances): void => { instances.forEach(it => { if (it.StudyInstanceUID !== uid) { console.warn('Not all instances have the same UID', uid, it); - throw new Error( - `Instances ${it.SOPInstanceUID} does not belong to ${uid}` - ); + throw new Error(`Instances ${it.SOPInstanceUID} does not belong to ${uid}`); } }); }; @@ -51,10 +49,7 @@ const CodeNameCodeSequenceValues = { const CodingSchemeDesignators = { SRT: 'SRT', - CornerstoneCodeSchemes: [ - Cornerstone3DCodeScheme.CodingSchemeDesignator, - 'CST4', - ], + CornerstoneCodeSchemes: [Cornerstone3DCodeScheme.CodingSchemeDesignator, 'CST4'], }; const RELATIONSHIP_TYPE = { @@ -71,10 +66,7 @@ const CORNERSTONE_FREETEXT_CODE_VALUE = 'CORNERSTONEFREETEXT'; * @param instances is a list of instances from THIS series that are not * in this DICOM SR Display Set already. */ -function addInstances( - instances: InstanceMetadata[], - displaySetService: DisplaySetService -) { +function addInstances(instances: InstanceMetadata[], displaySetService: DisplaySetService) { this.instances.push(...instances); utils.sortStudyInstances(this.instances); // The last instance is the newest one, so is the one most interesting. @@ -93,11 +85,7 @@ function addInstances( * @param servicesManager is the services that can be used for creating * @returns The list of display sets created for the given instances object */ -function _getDisplaySetsFromSeries( - instances, - servicesManager, - extensionManager -) { +function _getDisplaySetsFromSeries(instances, servicesManager, extensionManager) { // If the series has no instances, stop here if (!instances || !instances.length) { throw new Error('No instances were provided'); @@ -123,8 +111,7 @@ function _getDisplaySetsFromSeries( if ( !ConceptNameCodeSequence || - ConceptNameCodeSequence.CodeValue !== - CodeNameCodeSequenceValues.ImagingMeasurementReport + ConceptNameCodeSequence.CodeValue !== CodeNameCodeSequenceValues.ImagingMeasurementReport ) { servicesManager.services.uiNotificationService.show({ title: 'DICOM SR', @@ -187,33 +174,34 @@ function _load(displaySet, servicesManager, extensionManager) { _checkIfCanAddMeasurementsToDisplaySet( displaySet, activeDisplaySet, - dataSource + dataSource, + servicesManager ); }); // Subscribe to new displaySets as the source may come in after. - displaySetService.subscribe( - displaySetService.EVENTS.DISPLAY_SETS_ADDED, - data => { - const { displaySetsAdded } = data; - // If there are still some measurements that have not yet been loaded into cornerstone, - // See if we can load them onto any of the new displaySets. - displaySetsAdded.forEach(newDisplaySet => { - _checkIfCanAddMeasurementsToDisplaySet( - displaySet, - newDisplaySet, - dataSource - ); - }); - } - ); + displaySetService.subscribe(displaySetService.EVENTS.DISPLAY_SETS_ADDED, data => { + const { displaySetsAdded } = data; + // If there are still some measurements that have not yet been loaded into cornerstone, + // See if we can load them onto any of the new displaySets. + displaySetsAdded.forEach(newDisplaySet => { + _checkIfCanAddMeasurementsToDisplaySet( + displaySet, + newDisplaySet, + dataSource, + servicesManager + ); + }); + }); } function _checkIfCanAddMeasurementsToDisplaySet( srDisplaySet, newDisplaySet, - dataSource + dataSource, + servicesManager ) { + const { customizationService } = servicesManager.services; let unloadedMeasurements = srDisplaySet.measurements.filter( measurement => measurement.loaded === false ); @@ -228,7 +216,11 @@ function _checkIfCanAddMeasurementsToDisplaySet( return; } - const { sopClassUids, images } = newDisplaySet; + if (newDisplaySet.unsupported) { + return; + } + + const { sopClassUids } = newDisplaySet; // Check if any have the newDisplaySet is the correct SOPClass. unloadedMeasurements = unloadedMeasurements.filter(measurement => @@ -248,8 +240,7 @@ function _checkIfCanAddMeasurementsToDisplaySet( const { coords } = measurement; coords.forEach(coord => { - const SOPInstanceUID = - coord.ReferencedSOPSequence.ReferencedSOPInstanceUID; + const SOPInstanceUID = coord.ReferencedSOPSequence.ReferencedSOPInstanceUID; if (!SOPInstanceUIDs.includes(SOPInstanceUID)) { SOPInstanceUIDs.push(SOPInstanceUID); @@ -257,9 +248,7 @@ function _checkIfCanAddMeasurementsToDisplaySet( }); }); - const imageIdsForDisplaySet = dataSource.getImageIdsForDisplaySet( - newDisplaySet - ); + const imageIdsForDisplaySet = dataSource.getImageIdsForDisplaySet(newDisplaySet); for (const imageId of imageIdsForDisplaySet) { if (!unloadedMeasurements.length) { @@ -267,25 +256,26 @@ function _checkIfCanAddMeasurementsToDisplaySet( return; } - const { SOPInstanceUID, frameNumber } = metadataProvider.getUIDsFromImageID( - imageId - ); + const { SOPInstanceUID, frameNumber } = metadataProvider.getUIDsFromImageID(imageId); if (SOPInstanceUIDs.includes(SOPInstanceUID)) { for (let j = unloadedMeasurements.length - 1; j >= 0; j--) { - const measurement = unloadedMeasurements[j]; - if ( - _measurementReferencesSOPInstanceUID( - measurement, - SOPInstanceUID, - frameNumber - ) - ) { - addMeasurement( + let measurement = unloadedMeasurements[j]; + + const onBeforeSRAddMeasurement = customizationService.getModeCustomization( + 'onBeforeSRAddMeasurement' + )?.value; + + if (typeof onBeforeSRAddMeasurement === 'function') { + measurement = onBeforeSRAddMeasurement({ measurement, - imageId, - newDisplaySet.displaySetInstanceUID - ); + StudyInstanceUID: srDisplaySet.StudyInstanceUID, + SeriesInstanceUID: srDisplaySet.SeriesInstanceUID, + }); + } + + if (_measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID, frameNumber)) { + addMeasurement(measurement, imageId, newDisplaySet.displaySetInstanceUID); unloadedMeasurements.splice(j, 1); } @@ -294,11 +284,7 @@ function _checkIfCanAddMeasurementsToDisplaySet( } } -function _measurementReferencesSOPInstanceUID( - measurement, - SOPInstanceUID, - frameNumber -) { +function _measurementReferencesSOPInstanceUID(measurement, SOPInstanceUID, frameNumber) { const { coords } = measurement; // NOTE: The ReferencedFrameNumber can be multiple values according to the DICOM @@ -324,11 +310,7 @@ function _measurementReferencesSOPInstanceUID( function getSopClassHandlerModule({ servicesManager, extensionManager }) { const getDisplaySetsFromSeries = instances => { - return _getDisplaySetsFromSeries( - instances, - servicesManager, - extensionManager - ); + return _getDisplaySetsFromSeries(instances, servicesManager, extensionManager); }; return [ @@ -343,30 +325,22 @@ function getSopClassHandlerModule({ servicesManager, extensionManager }) { function _getMeasurements(ImagingMeasurementReportContentSequence) { const ImagingMeasurements = ImagingMeasurementReportContentSequence.find( item => - item.ConceptNameCodeSequence.CodeValue === - CodeNameCodeSequenceValues.ImagingMeasurements + item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.ImagingMeasurements ); - const MeasurementGroups = _getSequenceAsArray( - ImagingMeasurements.ContentSequence - ).filter( - item => - item.ConceptNameCodeSequence.CodeValue === - CodeNameCodeSequenceValues.MeasurementGroup + const MeasurementGroups = _getSequenceAsArray(ImagingMeasurements.ContentSequence).filter( + item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.MeasurementGroup ); - const mergedContentSequencesByTrackingUniqueIdentifiers = _getMergedContentSequencesByTrackingUniqueIdentifiers( - MeasurementGroups - ); + const mergedContentSequencesByTrackingUniqueIdentifiers = + _getMergedContentSequencesByTrackingUniqueIdentifiers(MeasurementGroups); const measurements = []; Object.keys(mergedContentSequencesByTrackingUniqueIdentifiers).forEach( trackingUniqueIdentifier => { const mergedContentSequence = - mergedContentSequencesByTrackingUniqueIdentifiers[ - trackingUniqueIdentifier - ]; + mergedContentSequencesByTrackingUniqueIdentifiers[trackingUniqueIdentifier]; const measurement = _processMeasurement(mergedContentSequence); @@ -379,15 +353,11 @@ function _getMeasurements(ImagingMeasurementReportContentSequence) { return measurements; } -function _getMergedContentSequencesByTrackingUniqueIdentifiers( - MeasurementGroups -) { +function _getMergedContentSequencesByTrackingUniqueIdentifiers(MeasurementGroups) { const mergedContentSequencesByTrackingUniqueIdentifiers = {}; MeasurementGroups.forEach(MeasurementGroup => { - const ContentSequence = _getSequenceAsArray( - MeasurementGroup.ContentSequence - ); + const ContentSequence = _getSequenceAsArray(MeasurementGroup.ContentSequence); const TrackingUniqueIdentifierItem = ContentSequence.find( item => @@ -396,22 +366,16 @@ function _getMergedContentSequencesByTrackingUniqueIdentifiers( ); if (!TrackingUniqueIdentifierItem) { - console.warn( - 'No Tracking Unique Identifier, skipping ambiguous measurement.' - ); + console.warn('No Tracking Unique Identifier, skipping ambiguous measurement.'); } const trackingUniqueIdentifier = TrackingUniqueIdentifierItem.UID; - if ( - mergedContentSequencesByTrackingUniqueIdentifiers[ - trackingUniqueIdentifier - ] === undefined - ) { + if (mergedContentSequencesByTrackingUniqueIdentifiers[trackingUniqueIdentifier] === undefined) { // Add the full ContentSequence - mergedContentSequencesByTrackingUniqueIdentifiers[ - trackingUniqueIdentifier - ] = [...ContentSequence]; + mergedContentSequencesByTrackingUniqueIdentifiers[trackingUniqueIdentifier] = [ + ...ContentSequence, + ]; } else { // Add the ContentSequence minus the tracking identifier, as we have this // Information in the merged ContentSequence anyway. @@ -420,9 +384,7 @@ function _getMergedContentSequencesByTrackingUniqueIdentifiers( item.ConceptNameCodeSequence.CodeValue !== CodeNameCodeSequenceValues.TrackingUniqueIdentifier ) { - mergedContentSequencesByTrackingUniqueIdentifiers[ - trackingUniqueIdentifier - ].push(item); + mergedContentSequencesByTrackingUniqueIdentifiers[trackingUniqueIdentifier].push(item); } }); } @@ -447,18 +409,12 @@ function _processTID1410Measurement(mergedContentSequence) { // Need to deal with TID 1410 style measurements, which will have a SCOORD or SCOORD3D at the top level, // And non-geometric representations where each NUM has "INFERRED FROM" SCOORD/SCOORD3D - const graphicItem = mergedContentSequence.find( - group => group.ValueType === 'SCOORD' - ); + const graphicItem = mergedContentSequence.find(group => group.ValueType === 'SCOORD'); - const UIDREFContentItem = mergedContentSequence.find( - group => group.ValueType === 'UIDREF' - ); + const UIDREFContentItem = mergedContentSequence.find(group => group.ValueType === 'UIDREF'); const TrackingIdentifierContentItem = mergedContentSequence.find( - item => - item.ConceptNameCodeSequence.CodeValue === - CodeNameCodeSequenceValues.TrackingIdentifier + item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.TrackingIdentifier ); if (!graphicItem) { @@ -468,9 +424,7 @@ function _processTID1410Measurement(mergedContentSequence) { return; } - const NUMContentItems = mergedContentSequence.filter( - group => group.ValueType === 'NUM' - ); + const NUMContentItems = mergedContentSequence.filter(group => group.ValueType === 'NUM'); const measurement = { loaded: false, @@ -485,10 +439,7 @@ function _processTID1410Measurement(mergedContentSequence) { if (MeasuredValueSequence) { measurement.labels.push( - _getLabelFromMeasuredValueSequence( - ConceptNameCodeSequence, - MeasuredValueSequence - ) + _getLabelFromMeasuredValueSequence(ConceptNameCodeSequence, MeasuredValueSequence) ); } }); @@ -497,32 +448,22 @@ function _processTID1410Measurement(mergedContentSequence) { } function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) { - const NUMContentItems = mergedContentSequence.filter( - group => group.ValueType === 'NUM' - ); + const NUMContentItems = mergedContentSequence.filter(group => group.ValueType === 'NUM'); - const UIDREFContentItem = mergedContentSequence.find( - group => group.ValueType === 'UIDREF' - ); + const UIDREFContentItem = mergedContentSequence.find(group => group.ValueType === 'UIDREF'); const TrackingIdentifierContentItem = mergedContentSequence.find( - item => - item.ConceptNameCodeSequence.CodeValue === - CodeNameCodeSequenceValues.TrackingIdentifier + item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.TrackingIdentifier ); const finding = mergedContentSequence.find( - item => - item.ConceptNameCodeSequence.CodeValue === - CodeNameCodeSequenceValues.Finding + item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.Finding ); const findingSites = mergedContentSequence.filter( item => - item.ConceptNameCodeSequence.CodingSchemeDesignator === - CodingSchemeDesignators.SRT && - item.ConceptNameCodeSequence.CodeValue === - CodeNameCodeSequenceValues.FindingSite + item.ConceptNameCodeSequence.CodingSchemeDesignator === CodingSchemeDesignators.SRT && + item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.FindingSite ); const measurement = { @@ -538,8 +479,7 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) { CodingSchemeDesignators.CornerstoneCodeSchemes.includes( finding.ConceptCodeSequence.CodingSchemeDesignator ) && - finding.ConceptCodeSequence.CodeValue === - CodeNameCodeSequenceValues.CornerstoneFreeText + finding.ConceptCodeSequence.CodeValue === CodeNameCodeSequenceValues.CornerstoneFreeText ) { measurement.labels.push({ label: CORNERSTONE_FREETEXT_CODE_VALUE, @@ -554,8 +494,7 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) { CodingSchemeDesignators.CornerstoneCodeSchemes.includes( FindingSite.ConceptCodeSequence.CodingSchemeDesignator ) && - FindingSite.ConceptCodeSequence.CodeValue === - CodeNameCodeSequenceValues.CornerstoneFreeText + FindingSite.ConceptCodeSequence.CodeValue === CodeNameCodeSequenceValues.CornerstoneFreeText ); if (cornerstoneFreeTextFindingSite) { @@ -567,18 +506,12 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) { } NUMContentItems.forEach(item => { - const { - ConceptNameCodeSequence, - ContentSequence, - MeasuredValueSequence, - } = item; + const { ConceptNameCodeSequence, ContentSequence, MeasuredValueSequence } = item; const { ValueType } = ContentSequence; if (!ValueType === 'SCOORD') { - console.warn( - `Graphic ${ValueType} not currently supported, skipping annotation.` - ); + console.warn(`Graphic ${ValueType} not currently supported, skipping annotation.`); return; } @@ -591,10 +524,7 @@ function _processNonGeometricallyDefinedMeasurement(mergedContentSequence) { if (MeasuredValueSequence) { measurement.labels.push( - _getLabelFromMeasuredValueSequence( - ConceptNameCodeSequence, - MeasuredValueSequence - ) + _getLabelFromMeasuredValueSequence(ConceptNameCodeSequence, MeasuredValueSequence) ); } }); @@ -634,17 +564,12 @@ function _getCoordsFromSCOORDOrSCOORD3D(item) { return coords; } -function _getLabelFromMeasuredValueSequence( - ConceptNameCodeSequence, - MeasuredValueSequence -) { +function _getLabelFromMeasuredValueSequence(ConceptNameCodeSequence, MeasuredValueSequence) { const { CodeMeaning } = ConceptNameCodeSequence; const { NumericValue, MeasurementUnitsCodeSequence } = MeasuredValueSequence; const { CodeValue } = MeasurementUnitsCodeSequence; - const formatedNumericValue = NumericValue - ? Number(NumericValue).toFixed(2) - : ''; + const formatedNumericValue = NumericValue ? Number(NumericValue).toFixed(2) : ''; return { label: CodeMeaning, @@ -654,17 +579,11 @@ function _getLabelFromMeasuredValueSequence( function _getReferencedImagesList(ImagingMeasurementReportContentSequence) { const ImageLibrary = ImagingMeasurementReportContentSequence.find( - item => - item.ConceptNameCodeSequence.CodeValue === - CodeNameCodeSequenceValues.ImageLibrary + item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.ImageLibrary ); - const ImageLibraryGroup = _getSequenceAsArray( - ImageLibrary.ContentSequence - ).find( - item => - item.ConceptNameCodeSequence.CodeValue === - CodeNameCodeSequenceValues.ImageLibraryGroup + const ImageLibraryGroup = _getSequenceAsArray(ImageLibrary.ContentSequence).find( + item => item.ConceptNameCodeSequence.CodeValue === CodeNameCodeSequenceValues.ImageLibraryGroup ); const referencedImages = []; diff --git a/extensions/cornerstone-dicom-sr/src/index.tsx b/extensions/cornerstone-dicom-sr/src/index.tsx index 9b87ea98cbf..9b0700481ed 100644 --- a/extensions/cornerstone-dicom-sr/src/index.tsx +++ b/extensions/cornerstone-dicom-sr/src/index.tsx @@ -1,8 +1,6 @@ import React from 'react'; import getSopClassHandlerModule from './getSopClassHandlerModule'; -import getHangingProtocolModule, { - srProtocol, -} from './getHangingProtocolModule'; +import getHangingProtocolModule, { srProtocol } from './getHangingProtocolModule'; import onModeEnter from './onModeEnter'; import getCommandsModule from './commandsModule'; import preRegistration from './init'; @@ -12,9 +10,7 @@ import hydrateStructuredReport from './utils/hydrateStructuredReport'; import createReferencedImageDisplaySet from './utils/createReferencedImageDisplaySet'; const Component = React.lazy(() => { - return import( - /* webpackPrefetch: true */ './viewports/OHIFCornerstoneSRViewport' - ); + return import(/* webpackPrefetch: true */ './viewports/OHIFCornerstoneSRViewport'); }); const OHIFCornerstoneSRViewport = props => { @@ -58,7 +54,7 @@ const dicomSRExtension = { }, getCommandsModule, getSopClassHandlerModule, - // Include dynmically computed values such as toolNames not known till instantiation + // Include dynamically computed values such as toolNames not known till instantiation getUtilityModule({ servicesManager }) { return [ { diff --git a/extensions/cornerstone-dicom-sr/src/init.ts b/extensions/cornerstone-dicom-sr/src/init.ts index 84cbcaf37f9..1ab3300d778 100644 --- a/extensions/cornerstone-dicom-sr/src/init.ts +++ b/extensions/cornerstone-dicom-sr/src/init.ts @@ -18,9 +18,7 @@ import toolNames from './tools/toolNames'; /** * @param {object} configuration */ -export default function init({ - configuration = {}, -}: Types.Extensions.ExtensionParams): void { +export default function init({ configuration = {} }: Types.Extensions.ExtensionParams): void { addTool(DICOMSRDisplayTool); addToolInstance(toolNames.SRLength, LengthTool, {}); addToolInstance(toolNames.SRBidirectional, BidirectionalTool); diff --git a/extensions/cornerstone-dicom-sr/src/tools/DICOMSRDisplayTool.ts b/extensions/cornerstone-dicom-sr/src/tools/DICOMSRDisplayTool.ts index 8b656e972fb..7b14b107f59 100644 --- a/extensions/cornerstone-dicom-sr/src/tools/DICOMSRDisplayTool.ts +++ b/extensions/cornerstone-dicom-sr/src/tools/DICOMSRDisplayTool.ts @@ -40,49 +40,32 @@ export default class DICOMSRDisplayTool extends AnnotationTool { isPointNearTool = () => null; getHandleNearImagePoint = () => null; - renderAnnotation = ( - enabledElement: Types.IEnabledElement, - svgDrawingHelper: any - ): void => { + renderAnnotation = (enabledElement: Types.IEnabledElement, svgDrawingHelper: any): void => { const { viewport } = enabledElement; const { element } = viewport; - let annotations = annotation.state.getAnnotations( - this.getToolName(), - element - ); + let annotations = annotation.state.getAnnotations(this.getToolName(), element); // Todo: We don't need this anymore, filtering happens in triggerAnnotationRender if (!annotations?.length) { return; } - annotations = this.filterInteractableAnnotationsForElement( - element, - annotations - ); + annotations = this.filterInteractableAnnotationsForElement(element, annotations); if (!annotations?.length) { return; } - const trackingUniqueIdentifiersForElement = getTrackingUniqueIdentifiersForElement( - element - ); + const trackingUniqueIdentifiersForElement = getTrackingUniqueIdentifiersForElement(element); - const { - activeIndex, - trackingUniqueIdentifiers, - } = trackingUniqueIdentifiersForElement; + const { activeIndex, trackingUniqueIdentifiers } = trackingUniqueIdentifiersForElement; - const activeTrackingUniqueIdentifier = - trackingUniqueIdentifiers[activeIndex]; + const activeTrackingUniqueIdentifier = trackingUniqueIdentifiers[activeIndex]; // Filter toolData to only render the data for the active SR. const filteredAnnotations = annotations.filter(annotation => - trackingUniqueIdentifiers.includes( - annotation.data?.cachedStats?.TrackingUniqueIdentifier - ) + trackingUniqueIdentifiers.includes(annotation.data?.cachedStats?.TrackingUniqueIdentifier) ); if (!viewport._actors?.size) { @@ -138,8 +121,7 @@ export default class DICOMSRDisplayTool extends AnnotationTool { break; case SCOORD_TYPES.ELLIPSE: renderMethod = this.renderEllipse; - canvasCoordinatesAdapter = - utilities.math.ellipse.getCanvasEllipseCorners; + canvasCoordinatesAdapter = utilities.math.ellipse.getCanvasEllipseCorners; break; default: throw new Error(`Unsupported GraphicType: ${GraphicType}`); @@ -221,15 +203,9 @@ export default class DICOMSRDisplayTool extends AnnotationTool { renderableData.map((data, index) => { canvasCoordinates = data.map(p => viewport.worldToCanvas(p)); const handleGroupUID = '0'; - drawing.drawHandles( - svgDrawingHelper, - annotationUID, - handleGroupUID, - canvasCoordinates, - { - color: options.color, - } - ); + drawing.drawHandles(svgDrawingHelper, annotationUID, handleGroupUID, canvasCoordinates, { + color: options.color, + }); }); } @@ -248,10 +224,7 @@ export default class DICOMSRDisplayTool extends AnnotationTool { canvasCoordinates.push(viewport.worldToCanvas(point)); // We get the other point for the arrow by using the image size - const imagePixelModule = metaData.get( - 'imagePixelModule', - referencedImageId - ); + const imagePixelModule = metaData.get('imagePixelModule', referencedImageId); let xOffset = 10; let yOffset = 10; @@ -309,9 +282,7 @@ export default class DICOMSRDisplayTool extends AnnotationTool { const rotation = viewport.getRotation(); - canvasCoordinates = ellipsePointsWorld.map(p => - viewport.worldToCanvas(p) - ); + canvasCoordinates = ellipsePointsWorld.map(p => viewport.worldToCanvas(p)); let canvasCorners; if (rotation == 90 || rotation == 270) { canvasCorners = utilities.math.ellipse.getCanvasEllipseCorners([ @@ -366,23 +337,16 @@ export default class DICOMSRDisplayTool extends AnnotationTool { adaptedCanvasCoordinates = canvasCoordinatesAdapter(canvasCoordinates); } const textLines = this._getTextBoxLinesFromLabels(label); - const canvasTextBoxCoords = utilities.drawing.getTextBoxCoordsCanvas( - adaptedCanvasCoordinates - ); + const canvasTextBoxCoords = utilities.drawing.getTextBoxCoordsCanvas(adaptedCanvasCoordinates); - annotation.data.handles.textBox.worldPosition = viewport.canvasToWorld( - canvasTextBoxCoords - ); + if (!annotation.data?.handles?.textBox?.worldPosition) { + annotation.data.handles.textBox.worldPosition = viewport.canvasToWorld(canvasTextBoxCoords); + } - const textBoxPosition = viewport.worldToCanvas( - annotation.data.handles.textBox.worldPosition - ); + const textBoxPosition = viewport.worldToCanvas(annotation.data.handles.textBox.worldPosition); const textBoxUID = '1'; - const textBoxOptions = this.getLinkedTextBoxStyle( - styleSpecifier, - annotation - ); + const textBoxOptions = this.getLinkedTextBoxStyle(styleSpecifier, annotation); const boundingBox = drawing.drawLinkedTextBox( svgDrawingHelper, diff --git a/extensions/cornerstone-dicom-sr/src/tools/modules/dicomSRModule.js b/extensions/cornerstone-dicom-sr/src/tools/modules/dicomSRModule.js index f359e3d2737..5636e6563d2 100644 --- a/extensions/cornerstone-dicom-sr/src/tools/modules/dicomSRModule.js +++ b/extensions/cornerstone-dicom-sr/src/tools/modules/dicomSRModule.js @@ -27,15 +27,11 @@ function setTrackingUniqueIdentifiersForElement( }; } -function setActiveTrackingUniqueIdentifierForElement( - element, - TrackingUniqueIdentifier -) { +function setActiveTrackingUniqueIdentifierForElement(element, TrackingUniqueIdentifier) { const enabledElement = getEnabledElement(element); const { viewport } = enabledElement; - const trackingIdentifiersForElement = - state.trackingIdentifiersByViewportId[viewport.id]; + const trackingIdentifiersForElement = state.trackingIdentifiersByViewportId[viewport.id]; if (trackingIdentifiersForElement) { const activeIndex = trackingIdentifiersForElement.trackingUniqueIdentifiers.findIndex( diff --git a/extensions/cornerstone-dicom-sr/src/utils/addMeasurement.ts b/extensions/cornerstone-dicom-sr/src/utils/addMeasurement.ts index ac8e43003fa..348a7a79d85 100644 --- a/extensions/cornerstone-dicom-sr/src/utils/addMeasurement.ts +++ b/extensions/cornerstone-dicom-sr/src/utils/addMeasurement.ts @@ -8,11 +8,7 @@ const EPSILON = 1e-4; const supportedLegacyCornerstoneTags = ['cornerstoneTools@^4.0.0']; -export default function addMeasurement( - measurement, - imageId, - displaySetInstanceUID -) { +export default function addMeasurement(measurement, imageId, displaySetInstanceUID) { // TODO -> Render rotated ellipse . const toolName = toolNames.DICOMSRDisplay; @@ -31,12 +27,7 @@ export default function addMeasurement( } measurementData.renderableData[GraphicType].push( - _getRenderableData( - GraphicType, - GraphicData, - imageId, - measurement.TrackingIdentifier - ) + _getRenderableData(GraphicType, GraphicData, imageId, measurement.TrackingIdentifier) ); }); @@ -61,7 +52,7 @@ export default function addMeasurement( data: { label: measurement.labels, handles: { - textBox: {}, + textBox: measurement.textBox ?? {}, }, cachedStats: { TrackingUniqueIdentifier: measurementData.TrackingUniqueIdentifier, @@ -86,12 +77,7 @@ export default function addMeasurement( delete measurement.coords; } -function _getRenderableData( - GraphicType, - GraphicData, - imageId, - TrackingIdentifier -) { +function _getRenderableData(GraphicType, GraphicData, imageId, TrackingIdentifier) { const [cornerstoneTag, toolName] = TrackingIdentifier.split(':'); let renderableData: csTypes.Point3[]; @@ -207,38 +193,22 @@ function _getRenderableData( throw new Error('imageId does not have imagePlaneModule metadata'); } - const { - columnCosines, - }: { columnCosines: csTypes.Point3 } = imagePlaneModule; + const { columnCosines }: { columnCosines: csTypes.Point3 } = imagePlaneModule; // find which axis is parallel to the columnCosines const columnCosinesVec = vec3.fromValues(...columnCosines); - const projectedMajorAxisOnColVec = Math.abs( - vec3.dot(columnCosinesVec, majorAxisVec) - ); - const projectedMinorAxisOnColVec = Math.abs( - vec3.dot(columnCosinesVec, minorAxisVec) - ); + const projectedMajorAxisOnColVec = Math.abs(vec3.dot(columnCosinesVec, majorAxisVec)); + const projectedMinorAxisOnColVec = Math.abs(vec3.dot(columnCosinesVec, minorAxisVec)); const absoluteOfMajorDotProduct = Math.abs(projectedMajorAxisOnColVec); const absoluteOfMinorDotProduct = Math.abs(projectedMinorAxisOnColVec); renderableData = []; if (Math.abs(absoluteOfMajorDotProduct - 1) < EPSILON) { - renderableData = [ - pointsWorld[0], - pointsWorld[1], - pointsWorld[2], - pointsWorld[3], - ]; + renderableData = [pointsWorld[0], pointsWorld[1], pointsWorld[2], pointsWorld[3]]; } else if (Math.abs(absoluteOfMinorDotProduct - 1) < EPSILON) { - renderableData = [ - pointsWorld[2], - pointsWorld[3], - pointsWorld[0], - pointsWorld[1], - ]; + renderableData = [pointsWorld[2], pointsWorld[3], pointsWorld[0], pointsWorld[1]]; } else { console.warn('OBLIQUE ELLIPSE NOT YET SUPPORTED'); } diff --git a/extensions/cornerstone-dicom-sr/src/utils/addToolInstance.ts b/extensions/cornerstone-dicom-sr/src/utils/addToolInstance.ts index 889926d812b..bd4cf2d1e95 100644 --- a/extensions/cornerstone-dicom-sr/src/utils/addToolInstance.ts +++ b/extensions/cornerstone-dicom-sr/src/utils/addToolInstance.ts @@ -1,10 +1,6 @@ import { addTool } from '@cornerstonejs/tools'; -export default function addToolInstance( - name: string, - toolClass, - configuration? -): void { +export default function addToolInstance(name: string, toolClass, configuration?): void { class InstanceClass extends toolClass { static toolName = name; } diff --git a/extensions/cornerstone-dicom-sr/src/utils/createReferencedImageDisplaySet.ts b/extensions/cornerstone-dicom-sr/src/utils/createReferencedImageDisplaySet.ts index bc39b80806f..72416416510 100644 --- a/extensions/cornerstone-dicom-sr/src/utils/createReferencedImageDisplaySet.ts +++ b/extensions/cornerstone-dicom-sr/src/utils/createReferencedImageDisplaySet.ts @@ -3,13 +3,8 @@ import { DisplaySetService, classes } from '@ohif/core'; const ImageSet = classes.ImageSet; const findInstance = (measurement, displaySetService: DisplaySetService) => { - const { - displaySetInstanceUID, - ReferencedSOPInstanceUID: sopUid, - } = measurement; - const referencedDisplaySet = displaySetService.getDisplaySetByUID( - displaySetInstanceUID - ); + const { displaySetInstanceUID, ReferencedSOPInstanceUID: sopUid } = measurement; + const referencedDisplaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID); if (!referencedDisplaySet.images) { return; } @@ -20,10 +15,7 @@ const findInstance = (measurement, displaySetService: DisplaySetService) => { * contained within the provided display set. * @return an array of instances referenced. */ -const findReferencedInstances = ( - displaySetService: DisplaySetService, - displaySet -) => { +const findReferencedInstances = (displaySetService: DisplaySetService, displaySet) => { const instances = []; const instanceById = {}; for (const measurement of displaySet.measurements) { @@ -58,7 +50,7 @@ const findReferencedInstances = ( const createReferencedImageDisplaySet = (displaySetService, displaySet) => { const instances = findReferencedInstances(displaySetService, displaySet); // This will be a member function of the created image set - const updateInstances = function() { + const updateInstances = function () { this.images.splice( 0, this.images.length, diff --git a/extensions/cornerstone-dicom-sr/src/utils/findInstanceMetadataBySopInstanceUid.js b/extensions/cornerstone-dicom-sr/src/utils/findInstanceMetadataBySopInstanceUid.js index 0e40ebb585f..30e68157655 100644 --- a/extensions/cornerstone-dicom-sr/src/utils/findInstanceMetadataBySopInstanceUid.js +++ b/extensions/cornerstone-dicom-sr/src/utils/findInstanceMetadataBySopInstanceUid.js @@ -14,8 +14,7 @@ const findInstanceMetadataBySopInstanceUID = (displaySets, SOPInstanceUID) => { } instanceFound = displaySet.images.find( - instanceMetadata => - instanceMetadata.getSOPInstanceUID() === SOPInstanceUID + instanceMetadata => instanceMetadata.getSOPInstanceUID() === SOPInstanceUID ); return !!instanceFound; diff --git a/extensions/cornerstone-dicom-sr/src/utils/findMostRecentStructuredReport.js b/extensions/cornerstone-dicom-sr/src/utils/findMostRecentStructuredReport.js index 9f8a3129ada..138d72df983 100644 --- a/extensions/cornerstone-dicom-sr/src/utils/findMostRecentStructuredReport.js +++ b/extensions/cornerstone-dicom-sr/src/utils/findMostRecentStructuredReport.js @@ -18,10 +18,7 @@ const findMostRecentStructuredReport = studies => { } if (isStructuredReportSeries(series)) { - if ( - !mostRecentStructuredReport || - compareSeriesDate(series, mostRecentStructuredReport) - ) { + if (!mostRecentStructuredReport || compareSeriesDate(series, mostRecentStructuredReport)) { mostRecentStructuredReport = series; } } @@ -38,10 +35,7 @@ const findMostRecentStructuredReport = studies => { * @returns {boolean} */ const isStructuredReportSeries = series => { - const supportedSopClassUIDs = [ - '1.2.840.10008.5.1.4.1.1.88.22', - '1.2.840.10008.5.1.4.1.1.11.1', - ]; + const supportedSopClassUIDs = ['1.2.840.10008.5.1.4.1.1.88.22', '1.2.840.10008.5.1.4.1.1.11.1']; const firstInstance = series.getFirstInstance(); const SOPClassUID = firstInstance.getData().metadata.SOPClassUID; @@ -50,7 +44,7 @@ const isStructuredReportSeries = series => { }; /** - * Checkes if series1 is newer than series2 + * Checks if series1 is newer than series2 * * @param {Object} series1 - Series Metadata 1 * @param {Object} series2 - Series Metadata 2 diff --git a/extensions/cornerstone-dicom-sr/src/utils/getFilteredCornerstoneToolState.ts b/extensions/cornerstone-dicom-sr/src/utils/getFilteredCornerstoneToolState.ts index 18c5b8465eb..2c28311d57c 100644 --- a/extensions/cornerstone-dicom-sr/src/utils/getFilteredCornerstoneToolState.ts +++ b/extensions/cornerstone-dicom-sr/src/utils/getFilteredCornerstoneToolState.ts @@ -2,17 +2,12 @@ import OHIF from '@ohif/core'; import { annotation } from '@cornerstonejs/tools'; const { log } = OHIF; -function getFilteredCornerstoneToolState( - measurementData, - additionalFindingTypes -) { +function getFilteredCornerstoneToolState(measurementData, additionalFindingTypes) { const filteredToolState = {}; function addToFilteredToolState(annotation, toolType) { if (!annotation.metadata?.referencedImageId) { - log.warn( - `[DICOMSR] No referencedImageId found for ${toolType} ${annotation.id}` - ); + log.warn(`[DICOMSR] No referencedImageId found for ${toolType} ${annotation.id}`); return; } @@ -30,9 +25,7 @@ function getFilteredCornerstoneToolState( }; } - const measurementDataI = measurementData.find( - md => md.uid === annotation.annotationUID - ); + const measurementDataI = measurementData.find(md => md.uid === annotation.annotationUID); const toolData = imageIdSpecificToolState[toolType].data; let { finding } = measurementDataI; @@ -77,9 +70,7 @@ function getFilteredCornerstoneToolState( for (let i = 0; i < framesOfReference.length; i++) { const frameOfReference = framesOfReference[i]; - const frameOfReferenceAnnotations = annotationManager.getAnnotations( - frameOfReference - ); + const frameOfReferenceAnnotations = annotationManager.getAnnotations(frameOfReference); const toolTypes = Object.keys(frameOfReferenceAnnotations); @@ -91,9 +82,7 @@ function getFilteredCornerstoneToolState( if (annotations) { for (let k = 0; k < annotations.length; k++) { const annotation = annotations[k]; - const uidIndex = uids.findIndex( - uid => uid === annotation.annotationUID - ); + const uidIndex = uids.findIndex(uid => uid === annotation.annotationUID); if (uidIndex !== -1) { addToFilteredToolState(annotation, toolType); diff --git a/extensions/cornerstone-dicom-sr/src/utils/getLabelFromDCMJSImportedToolData.js b/extensions/cornerstone-dicom-sr/src/utils/getLabelFromDCMJSImportedToolData.js index 18a2ad1f5cc..1fa18ee9c51 100644 --- a/extensions/cornerstone-dicom-sr/src/utils/getLabelFromDCMJSImportedToolData.js +++ b/extensions/cornerstone-dicom-sr/src/utils/getLabelFromDCMJSImportedToolData.js @@ -9,9 +9,7 @@ export default function getLabelFromDCMJSImportedToolData(toolData) { const { findingSites = [], finding } = toolData; - let freeTextLabel = findingSites.find( - fs => fs.CodeValue === 'CORNERSTONEFREETEXT' - ); + let freeTextLabel = findingSites.find(fs => fs.CodeValue === 'CORNERSTONEFREETEXT'); if (freeTextLabel) { return freeTextLabel.CodeMeaning; diff --git a/extensions/cornerstone-dicom-sr/src/utils/hydrateStructuredReport.js b/extensions/cornerstone-dicom-sr/src/utils/hydrateStructuredReport.js index a25a74c9156..7dc9e657adc 100644 --- a/extensions/cornerstone-dicom-sr/src/utils/hydrateStructuredReport.js +++ b/extensions/cornerstone-dicom-sr/src/utils/hydrateStructuredReport.js @@ -2,6 +2,8 @@ import { utilities, metaData } from '@cornerstonejs/core'; import OHIF, { DicomMetadataStore } from '@ohif/core'; import getLabelFromDCMJSImportedToolData from './getLabelFromDCMJSImportedToolData'; import { adaptersSR } from '@cornerstonejs/adapters'; +import { annotation as CsAnnotation } from '@cornerstonejs/tools'; +const { locking } = CsAnnotation; const { guid } = OHIF.utils; const { MeasurementReport, CORNERSTONE_3D_TAG } = adaptersSR.Cornerstone3D; @@ -41,23 +43,16 @@ const convertSites = (codingValues, sites) => { * */ export default function hydrateStructuredReport( - { servicesManager, extensionManager }, + { servicesManager, extensionManager, appConfig }, displaySetInstanceUID ) { + const annotationManager = CsAnnotation.state.getAnnotationManager(); + const disableEditing = appConfig?.disableEditing; const dataSource = extensionManager.getActiveDataSource()[0]; - const { - measurementService, - displaySetService, - customizationService, - } = servicesManager.services; - - const codingValues = customizationService.getCustomization( - 'codingValues', - {} - ); - const displaySet = displaySetService.getDisplaySetByUID( - displaySetInstanceUID - ); + const { measurementService, displaySetService, customizationService } = servicesManager.services; + + const codingValues = customizationService.getCustomization('codingValues', {}); + const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID); // TODO -> We should define a strict versioning somewhere. const mappings = measurementService.getSourceMappings( @@ -95,7 +90,7 @@ export default function hydrateStructuredReport( const datasetToUse = _mapLegacyDataSet(instance); // Use dcmjs to generate toolState. - const storedMeasurementByAnnotationType = MeasurementReport.generateToolState( + let storedMeasurementByAnnotationType = MeasurementReport.generateToolState( datasetToUse, // NOTE: we need to pass in the imageIds to dcmjs since the we use them // for the imageToWorld transformation. The following assumes that the order @@ -106,6 +101,16 @@ export default function hydrateStructuredReport( metaData ); + const onBeforeSRHydration = + customizationService.getModeCustomization('onBeforeSRHydration')?.value; + + if (typeof onBeforeSRHydration === 'function') { + storedMeasurementByAnnotationType = onBeforeSRHydration({ + storedMeasurementByAnnotationType, + displaySet, + }); + } + // Filter what is found by DICOM SR to measurements we support. const mappingDefinitions = mappings.map(m => m.annotationType); const hydratableMeasurementsInSR = {}; @@ -121,16 +126,14 @@ export default function hydrateStructuredReport( // TODO: notification if no hydratable? Object.keys(hydratableMeasurementsInSR).forEach(annotationType => { - const toolDataForAnnotationType = - hydratableMeasurementsInSR[annotationType]; + const toolDataForAnnotationType = hydratableMeasurementsInSR[annotationType]; toolDataForAnnotationType.forEach(toolData => { // Add the measurement to toolState // dcmjs and Cornerstone3D has structural defect in supporting multi-frame // files, and looking up the imageId from sopInstanceUIDToImageId results // in the wrong value. - const frameNumber = - (toolData.annotation.data && toolData.annotation.data.frameNumber) || 1; + const frameNumber = (toolData.annotation.data && toolData.annotation.data.frameNumber) || 1; const imageId = imageIdsForToolState[toolData.sopInstanceUid][frameNumber] || sopInstanceUIDToImageId[toolData.sopInstanceUid]; @@ -146,10 +149,7 @@ export default function hydrateStructuredReport( for (let i = 0; i < imageIds.length; i++) { const imageId = imageIds[i]; - const { SeriesInstanceUID, StudyInstanceUID } = metaData.get( - 'instance', - imageId - ); + const { SeriesInstanceUID, StudyInstanceUID } = metaData.get('instance', imageId); if (!SeriesInstanceUIDs.includes(SeriesInstanceUID)) { SeriesInstanceUIDs.push(SeriesInstanceUID); @@ -158,23 +158,19 @@ export default function hydrateStructuredReport( if (!targetStudyInstanceUID) { targetStudyInstanceUID = StudyInstanceUID; } else if (targetStudyInstanceUID !== StudyInstanceUID) { - console.warn( - 'NO SUPPORT FOR SRs THAT HAVE MEASUREMENTS FROM MULTIPLE STUDIES.' - ); + console.warn('NO SUPPORT FOR SRs THAT HAVE MEASUREMENTS FROM MULTIPLE STUDIES.'); } } Object.keys(hydratableMeasurementsInSR).forEach(annotationType => { - const toolDataForAnnotationType = - hydratableMeasurementsInSR[annotationType]; + const toolDataForAnnotationType = hydratableMeasurementsInSR[annotationType]; toolDataForAnnotationType.forEach(toolData => { // Add the measurement to toolState // dcmjs and Cornerstone3D has structural defect in supporting multi-frame // files, and looking up the imageId from sopInstanceUIDToImageId results // in the wrong value. - const frameNumber = - (toolData.annotation.data && toolData.annotation.data.frameNumber) || 1; + const frameNumber = (toolData.annotation.data && toolData.annotation.data.frameNumber) || 1; const imageId = imageIdsForToolState[toolData.sopInstanceUid][frameNumber] || sopInstanceUIDToImageId[toolData.sopInstanceUid]; @@ -204,21 +200,13 @@ export default function hydrateStructuredReport( CORNERSTONE_3D_TOOLS_SOURCE_VERSION ); annotation.data.label = getLabelFromDCMJSImportedToolData(toolData); - annotation.data.finding = convertCode( - codingValues, - toolData.finding?.[0] - ); - annotation.data.findingSites = convertSites( - codingValues, - toolData.findingSites - ); + annotation.data.finding = convertCode(codingValues, toolData.finding?.[0]); + annotation.data.findingSites = convertSites(codingValues, toolData.findingSites); annotation.data.site = annotation.data.findingSites?.[0]; - const matchingMapping = mappings.find( - m => m.annotationType === annotationType - ); + const matchingMapping = mappings.find(m => m.annotationType === annotationType); - measurementService.addRawMeasurement( + const newAnnotationUID = measurementService.addRawMeasurement( source, annotationType, { annotation }, @@ -226,6 +214,11 @@ export default function hydrateStructuredReport( dataSource ); + if (disableEditing) { + const addedAnnotation = annotationManager.getAnnotation(newAnnotationUID); + locking.setAnnotationLocked(addedAnnotation, true); + } + if (!imageIds.includes(imageId)) { imageIds.push(imageId); } @@ -251,15 +244,14 @@ function _mapLegacyDataSet(dataset) { ); // Retrieve the Measurements themselves - const measurementGroups = toArray( - imagingMeasurementContent.ContentSequence - ).filter(codeMeaningEquals(GROUP)); + 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 cornerstoneToolClasses = MeasurementReport.CORNERSTONE_TOOL_CLASSES_BY_UTILITY_TYPE; const registeredToolClasses = []; @@ -269,13 +261,10 @@ function _mapLegacyDataSet(dataset) { }); measurementGroups.forEach((measurementGroup, index) => { - const measurementGroupContentSequence = toArray( - measurementGroup.ContentSequence - ); + const measurementGroupContentSequence = toArray(measurementGroup.ContentSequence); const TrackingIdentifierGroup = measurementGroupContentSequence.find( - contentItem => - contentItem.ConceptNameCodeSequence.CodeMeaning === TRACKING_IDENTIFIER + contentItem => contentItem.ConceptNameCodeSequence.CodeMeaning === TRACKING_IDENTIFIER ); const TrackingIdentifier = TrackingIdentifierGroup.TextValue; @@ -293,7 +282,7 @@ function _mapLegacyDataSet(dataset) { return dataset; } -const toArray = function(x) { +const toArray = function (x) { return Array.isArray(x) ? x : [x]; }; diff --git a/extensions/cornerstone-dicom-sr/src/utils/isRehydratable.js b/extensions/cornerstone-dicom-sr/src/utils/isRehydratable.js index 41602dd257f..d6f6a748413 100644 --- a/extensions/cornerstone-dicom-sr/src/utils/isRehydratable.js +++ b/extensions/cornerstone-dicom-sr/src/utils/isRehydratable.js @@ -1,8 +1,7 @@ import { adaptersSR } from '@cornerstonejs/adapters'; const cornerstoneAdapters = - adaptersSR.Cornerstone3D.MeasurementReport - .CORNERSTONE_TOOL_CLASSES_BY_UTILITY_TYPE; + adaptersSR.Cornerstone3D.MeasurementReport.CORNERSTONE_TOOL_CLASSES_BY_UTILITY_TYPE; const supportedLegacyCornerstoneTags = ['cornerstoneTools@^4.0.0']; const CORNERSTONE_3D_TAG = cornerstoneAdapters.CORNERSTONE_3D_TAG; @@ -24,8 +23,7 @@ export default function isRehydratable(displaySet, mappings) { const adapterKeys = Object.keys(cornerstoneAdapters).filter( adapterKey => - typeof cornerstoneAdapters[adapterKey] - .isValidCornerstoneTrackingIdentifier === 'function' + typeof cornerstoneAdapters[adapterKey].isValidCornerstoneTrackingIdentifier === 'function' ); const adapters = []; @@ -48,19 +46,13 @@ export default function isRehydratable(displaySet, mappings) { const mappedTrackingIdentifier = `${cornerstoneTag}:${toolName}`; - return adapter.isValidCornerstoneTrackingIdentifier( - mappedTrackingIdentifier - ); + return adapter.isValidCornerstoneTrackingIdentifier(mappedTrackingIdentifier); }); if (hydratable) { return true; } - console.log( - 'Measurement is not rehydratable', - TrackingIdentifier, - measurements[i] - ); + console.log('Measurement is not rehydratable', TrackingIdentifier, measurements[i]); } console.log('No measurements found which were rehydratable'); diff --git a/extensions/cornerstone-dicom-sr/src/viewports/OHIFCornerstoneSRViewport.tsx b/extensions/cornerstone-dicom-sr/src/viewports/OHIFCornerstoneSRViewport.tsx index 3eb2dfe58ab..0937ca1ff10 100644 --- a/extensions/cornerstone-dicom-sr/src/viewports/OHIFCornerstoneSRViewport.tsx +++ b/extensions/cornerstone-dicom-sr/src/viewports/OHIFCornerstoneSRViewport.tsx @@ -7,11 +7,11 @@ import { setTrackingUniqueIdentifiersForElement } from '../tools/modules/dicomSR import { Icon, Tooltip, useViewportGrid, ViewportActionBar } from '@ohif/ui'; import hydrateStructuredReport from '../utils/hydrateStructuredReport'; +import { useAppConfig } from '@state'; const { formatDate } = utils; -const MEASUREMENT_TRACKING_EXTENSION_ID = - '@ohif/extension-measurement-tracking'; +const MEASUREMENT_TRACKING_EXTENSION_ID = '@ohif/extension-measurement-tracking'; const SR_TOOLGROUP_BASE_NAME = 'SRToolGroup'; @@ -20,18 +20,18 @@ function OHIFCornerstoneSRViewport(props) { children, dataSource, displaySets, - viewportIndex, viewportLabel, viewportOptions, servicesManager, extensionManager, } = props; - const { - displaySetService, - cornerstoneViewportService, - measurementService, - } = servicesManager.services; + const [appConfig] = useAppConfig(); + + const { displaySetService, cornerstoneViewportService, measurementService } = + servicesManager.services; + + const viewportId = viewportOptions.viewportId; // SR viewport will always have a single display set if (displaySets.length > 1) { @@ -43,15 +43,10 @@ function OHIFCornerstoneSRViewport(props) { const [viewportGrid, viewportGridService] = useViewportGrid(); const [measurementSelected, setMeasurementSelected] = useState(0); const [measurementCount, setMeasurementCount] = useState(1); - const [activeImageDisplaySetData, setActiveImageDisplaySetData] = useState( - null - ); - const [ - referencedDisplaySetMetadata, - setReferencedDisplaySetMetadata, - ] = useState(null); + const [activeImageDisplaySetData, setActiveImageDisplaySetData] = useState(null); + const [referencedDisplaySetMetadata, setReferencedDisplaySetMetadata] = useState(null); const [element, setElement] = useState(null); - const { viewports, activeViewportIndex } = viewportGrid; + const { viewports, activeViewportId } = viewportGrid; // Optional hook into tracking extension, if present. let trackedMeasurements; @@ -76,16 +71,14 @@ function OHIFCornerstoneSRViewport(props) { sendTrackedMeasurementsEvent = (eventName, { displaySetInstanceUID }) => { measurementService.clearMeasurements(); const { SeriesInstanceUIDs } = hydrateStructuredReport( - { servicesManager, extensionManager }, + { servicesManager, extensionManager, appConfig }, displaySetInstanceUID ); - const displaySets = displaySetService.getDisplaySetsForSeries( - SeriesInstanceUIDs[0] - ); + const displaySets = displaySetService.getDisplaySetsForSeries(SeriesInstanceUIDs[0]); if (displaySets.length) { viewportGridService.setDisplaySetsForViewports([ { - viewportIndex: activeViewportIndex, + viewportId: activeViewportId, displaySetInstanceUIDs: [displaySets[0].displaySetInstanceUID], }, ]); @@ -123,11 +116,7 @@ function OHIFCornerstoneSRViewport(props) { const updateViewport = useCallback( newMeasurementSelected => { - const { - StudyInstanceUID, - displaySetInstanceUID, - sopClassUids, - } = srDisplaySet; + const { StudyInstanceUID, displaySetInstanceUID, sopClassUids } = srDisplaySet; if (!StudyInstanceUID || !displaySetInstanceUID) { return; @@ -136,9 +125,7 @@ function OHIFCornerstoneSRViewport(props) { if (sopClassUids && sopClassUids.length > 1) { // Todo: what happens if there are multiple SOP Classes? Why we are // not throwing an error? - console.warn( - 'More than one SOPClassUID in the same series is not yet supported.' - ); + console.warn('More than one SOPClassUID in the same series is not yet supported.'); } _getViewportReferencedDisplaySetData( @@ -160,19 +147,11 @@ function OHIFCornerstoneSRViewport(props) { // imageIdIndex will handle it by updating the viewport, but if they // are the same we just need to use measurementService to jump to the // new measurement - const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex( - viewportIndex - ); - - const csViewport = cornerstoneViewportService.getCornerstoneViewport( - viewportInfo.getViewportId() - ); + const csViewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); const imageIds = csViewport.getImageIds(); - const imageIdIndex = imageIds.indexOf( - measurements[newMeasurementSelected].imageId - ); + const imageIdIndex = imageIds.indexOf(measurements[newMeasurementSelected].imageId); if (imageIdIndex !== -1) { csViewport.setImageIdIndex(imageIdIndex); @@ -180,7 +159,7 @@ function OHIFCornerstoneSRViewport(props) { } }); }, - [dataSource, srDisplaySet, activeImageDisplaySetData, viewportIndex] + [dataSource, srDisplaySet, activeImageDisplaySetData, viewportId] ); const getCornerstoneViewport = useCallback(() => { @@ -228,7 +207,7 @@ function OHIFCornerstoneSRViewport(props) { isJumpToMeasurementDisabled={true} > ); - }, [activeImageDisplaySetData, viewportIndex, measurementSelected]); + }, [activeImageDisplaySetData, viewportId, measurementSelected]); const onMeasurementChange = useCallback( direction => { @@ -251,12 +230,7 @@ function OHIFCornerstoneSRViewport(props) { setTrackingIdentifiers(newMeasurementSelected); updateViewport(newMeasurementSelected); }, - [ - measurementSelected, - measurementCount, - updateViewport, - setTrackingIdentifiers, - ] + [measurementSelected, measurementCount, updateViewport, setTrackingIdentifiers] ); /** @@ -266,12 +240,10 @@ function OHIFCornerstoneSRViewport(props) { const onDisplaySetsRemovedSubscription = displaySetService.subscribe( displaySetService.EVENTS.DISPLAY_SETS_REMOVED, ({ displaySetInstanceUIDs }) => { - const activeViewport = viewports[activeViewportIndex]; - if ( - displaySetInstanceUIDs.includes(activeViewport.displaySetInstanceUID) - ) { + const activeViewport = viewports.get(activeViewportId); + if (displaySetInstanceUIDs.includes(activeViewport.displaySetInstanceUID)) { viewportGridService.setDisplaySetsForViewport({ - viewportIndex: activeViewportIndex, + viewportId: activeViewportId, displaySetInstanceUIDs: [], }); } @@ -338,7 +310,7 @@ function OHIFCornerstoneSRViewport(props) { return ( child && React.cloneElement(child, { - viewportIndex, + viewportId, key: index, }) ); @@ -370,7 +342,7 @@ function OHIFCornerstoneSRViewport(props) { getStatusComponent={() => _getStatusComponent({ srDisplaySet, - viewportIndex, + viewportId, isTracked: false, isRehydratable: srDisplaySet.isRehydratable, isLocked, @@ -384,23 +356,19 @@ function OHIFCornerstoneSRViewport(props) { currentSeries: SeriesNumber, seriesDescription: SeriesDescription || '', patientInformation: { - patientName: PatientName - ? OHIF.utils.formatPN(PatientName.Alphabetic) - : '', + patientName: PatientName ? OHIF.utils.formatPN(PatientName.Alphabetic) : '', patientSex: PatientSex || '', patientAge: PatientAge || '', MRN: PatientID || '', - thickness: SliceThickness ? `${SliceThickness.toFixed(2)}mm` : '', + thickness: SliceThickness ? `${parseFloat(SliceThickness).toFixed(2)}mm` : '', spacing: - SpacingBetweenSlices !== undefined - ? `${SpacingBetweenSlices.toFixed(2)}mm` - : '', + SpacingBetweenSlices !== undefined ? `${SpacingBetweenSlices.toFixed(2)}mm` : '', scanner: ManufacturerModelName || '', }, }} /> -
+
{getCornerstoneViewport()} {childrenWithProps}
@@ -410,7 +378,7 @@ function OHIFCornerstoneSRViewport(props) { OHIFCornerstoneSRViewport.propTypes = { displaySets: PropTypes.arrayOf(PropTypes.object), - viewportIndex: PropTypes.number.isRequired, + viewportId: PropTypes.string.isRequired, dataSource: PropTypes.object, children: PropTypes.node, viewportLabel: PropTypes.string, @@ -435,9 +403,7 @@ async function _getViewportReferencedDisplaySetData( const { displaySetInstanceUID } = measurement; - const referencedDisplaySet = displaySetService.getDisplaySetByUID( - displaySetInstanceUID - ); + const referencedDisplaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID); const image0 = referencedDisplaySet.images[0]; const referencedDisplaySetMetadata = { @@ -459,7 +425,7 @@ async function _getViewportReferencedDisplaySetData( function _getStatusComponent({ srDisplaySet, - viewportIndex, + viewportId, isRehydratable, isLocked, sendTrackedMeasurementsEvent, @@ -467,7 +433,7 @@ function _getStatusComponent({ const handleMouseUp = () => { sendTrackedMeasurementsEvent('HYDRATE_SR', { displaySetInstanceUID: srDisplaySet.displaySetInstanceUID, - viewportIndex, + viewportId, }); }; @@ -477,8 +443,7 @@ function _getStatusComponent({ // 1 - Incompatible // 2 - Locked // 3 - Rehydratable / Open - const state = - isRehydratable && !isLocked ? 3 : isRehydratable && isLocked ? 2 : 1; + const state = isRehydratable && !isLocked ? 3 : isRehydratable && isLocked ? 2 : 1; let ToolTipMessage = null; let StatusIcon = null; @@ -508,22 +473,25 @@ function _getStatusComponent({ ); break; case 3: - StatusIcon = () => ; - - ToolTipMessage = () => ( -
{`Click ${loadStr} to restore measurements.`}
+ StatusIcon = () => ( + ); + + ToolTipMessage = () =>
{`Click ${loadStr} to restore measurements.`}
; } const StatusArea = () => ( -
-
+
+
SR
{state === 3 && (
@@ -536,7 +504,10 @@ function _getStatusComponent({ return ( <> {ToolTipMessage && ( - } position="bottom-left"> + } + position="bottom-left" + > )} diff --git a/extensions/cornerstone/.webpack/webpack.prod.js b/extensions/cornerstone/.webpack/webpack.prod.js index 74868eb44c2..c23590e69f9 100644 --- a/extensions/cornerstone/.webpack/webpack.prod.js +++ b/extensions/cornerstone/.webpack/webpack.prod.js @@ -40,13 +40,7 @@ module.exports = (env, argv) => { libraryTarget: 'umd', filename: pkg.main, }, - externals: [ - /\b(vtk.js)/, - /\b(dcmjs)/, - /\b(gl-matrix)/, - /^@ohif/, - /^@cornerstonejs/, - ], + externals: [/\b(vtk.js)/, /\b(dcmjs)/, /\b(gl-matrix)/, /^@ohif/, /^@cornerstonejs/], plugins: [ new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1, diff --git a/extensions/cornerstone/CHANGELOG.md b/extensions/cornerstone/CHANGELOG.md index e69de29bb2d..12cbe56d846 100644 --- a/extensions/cornerstone/CHANGELOG.md +++ b/extensions/cornerstone/CHANGELOG.md @@ -0,0 +1,1047 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [3.8.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.59...v3.8.0-beta.60) (2024-03-15) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.8.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.58...v3.8.0-beta.59) (2024-03-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.8.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.57...v3.8.0-beta.58) (2024-03-05) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.8.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.56...v3.8.0-beta.57) (2024-02-28) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.8.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.55...v3.8.0-beta.56) (2024-02-22) + + +### Bug Fixes + +* **demo:** Deploy issue ([#3951](https://github.com/OHIF/Viewers/issues/3951)) ([21e8a2b](https://github.com/OHIF/Viewers/commit/21e8a2bd0b7cc72f90a31e472d285d761be15d30)) + + + + + +# [3.8.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.54...v3.8.0-beta.55) (2024-02-21) + + +### Features + +* **resize:** Optimize resizing process and maintain zoom level ([#3889](https://github.com/OHIF/Viewers/issues/3889)) ([b3a0faf](https://github.com/OHIF/Viewers/commit/b3a0faf5f5f0a1993b2b017eb4cc1216164ea2c6)) + + + + + +# [3.8.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.53...v3.8.0-beta.54) (2024-02-14) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.8.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.52...v3.8.0-beta.53) (2024-02-05) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.8.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.51...v3.8.0-beta.52) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.8.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.50...v3.8.0-beta.51) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.8.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.49...v3.8.0-beta.50) (2024-01-22) + + +### Bug Fixes + +* **viewport-sync:** remember synced viewports bw stack and volume and RENAME StackImageSync to ImageSliceSync ([#3849](https://github.com/OHIF/Viewers/issues/3849)) ([e4a116b](https://github.com/OHIF/Viewers/commit/e4a116b074fcb85c8cbcc9db44fdec565f3386db)) + + + + + +# [3.8.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.48...v3.8.0-beta.49) (2024-01-19) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.8.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.47...v3.8.0-beta.48) (2024-01-17) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.8.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.46...v3.8.0-beta.47) (2024-01-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.8.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.45...v3.8.0-beta.46) (2024-01-12) + + +### Bug Fixes + +* Update CS3D to fix second render ([#3892](https://github.com/OHIF/Viewers/issues/3892)) ([d00a86b](https://github.com/OHIF/Viewers/commit/d00a86b022742ea089d246d06cfd691f43b64412)) + + + + + +# [3.8.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.44...v3.8.0-beta.45) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.8.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.43...v3.8.0-beta.44) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.8.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.42...v3.8.0-beta.43) (2024-01-09) + + +### Bug Fixes + +* **segmentation:** upgrade cs3d to fix various segmentation bugs ([#3885](https://github.com/OHIF/Viewers/issues/3885)) ([b1efe40](https://github.com/OHIF/Viewers/commit/b1efe40aa146e4052cc47b3f774cabbb47a8d1a6)) + + + + + +# [3.8.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.41...v3.8.0-beta.42) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.8.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.40...v3.8.0-beta.41) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.8.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.39...v3.8.0-beta.40) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.8.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.38...v3.8.0-beta.39) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.8.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.37...v3.8.0-beta.38) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.8.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.36...v3.8.0-beta.37) (2024-01-08) + + +### Bug Fixes + +* colormap for stack viewports via HangingProtocol ([#3866](https://github.com/OHIF/Viewers/issues/3866)) ([e8858f3](https://github.com/OHIF/Viewers/commit/e8858f3eb55552f695af4a55980f9ae2e9af7291)) + + + + + +# [3.8.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.35...v3.8.0-beta.36) (2023-12-15) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.8.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.34...v3.8.0-beta.35) (2023-12-14) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.8.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.33...v3.8.0-beta.34) (2023-12-13) + + +### Features + +* **overlay:** add inline binary overlays ([#3852](https://github.com/OHIF/Viewers/issues/3852)) ([0177b62](https://github.com/OHIF/Viewers/commit/0177b625ba86760168bc4db58c8a109aa9ee83cb)) + + + + + +# [3.8.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.32...v3.8.0-beta.33) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.8.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.31...v3.8.0-beta.32) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.8.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.30...v3.8.0-beta.31) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.8.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.29...v3.8.0-beta.30) (2023-12-13) + + +### Features + +* **customizationService:** Enable saving and loading of private tags in SRs ([#3842](https://github.com/OHIF/Viewers/issues/3842)) ([e1f55e6](https://github.com/OHIF/Viewers/commit/e1f55e65f2d2a34136ad5d0b1ada77d337a0ea23)) + + + + + +# [3.8.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.28...v3.8.0-beta.29) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.8.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.27...v3.8.0-beta.28) (2023-12-08) + + +### Features + +* **HP:** Added new 3D hanging protocols to be used in the new layout selector ([#3844](https://github.com/OHIF/Viewers/issues/3844)) ([59576d6](https://github.com/OHIF/Viewers/commit/59576d695d4d26601d35c43f73d602f0b12d72bf)) + + + + + +# [3.8.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.26...v3.8.0-beta.27) (2023-12-06) + + +### Bug Fixes + +* **auth:** fix the issue with oauth at a non root path ([#3840](https://github.com/OHIF/Viewers/issues/3840)) ([6651008](https://github.com/OHIF/Viewers/commit/6651008fbb35dabd5991c7f61128e6ef324012df)) + + + + + +# [3.8.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.25...v3.8.0-beta.26) (2023-11-28) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.8.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.24...v3.8.0-beta.25) (2023-11-27) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.8.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.23...v3.8.0-beta.24) (2023-11-24) + + +### Bug Fixes + +* Update the CS3D packages to add the most recent HTJ2K TSUIDS ([#3806](https://github.com/OHIF/Viewers/issues/3806)) ([9d1884d](https://github.com/OHIF/Viewers/commit/9d1884d7d8b6b2a1cdc26965a96995838aa72682)) + + + + + +# [3.8.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.22...v3.8.0-beta.23) (2023-11-24) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.8.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.21...v3.8.0-beta.22) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.8.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.20...v3.8.0-beta.21) (2023-11-21) + + +### Bug Fixes + +* **DICOM Overlay:** The overlay data wasn't being refreshed on change ([#3793](https://github.com/OHIF/Viewers/issues/3793)) ([00e7519](https://github.com/OHIF/Viewers/commit/00e751933ac6d611a34773fa69594243f1b99082)) + + + + + +# [3.8.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.19...v3.8.0-beta.20) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.8.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.18...v3.8.0-beta.19) (2023-11-18) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.8.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.17...v3.8.0-beta.18) (2023-11-15) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.8.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.16...v3.8.0-beta.17) (2023-11-13) + + +### Bug Fixes + +* 🐛 Run error handler for failed image requests ([#3773](https://github.com/OHIF/Viewers/issues/3773)) ([3234014](https://github.com/OHIF/Viewers/commit/323401418e7ccab74655ba02f990bbe0ed4e523b)) + + + + + +# [3.8.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.15...v3.8.0-beta.16) (2023-11-13) + + +### Bug Fixes + +* **overlay:** Overlays aren't shown on undefined origin ([#3781](https://github.com/OHIF/Viewers/issues/3781)) ([fd1251f](https://github.com/OHIF/Viewers/commit/fd1251f751d8147b8a78c7f4d81c67ba69769afa)) + + + + + +# [3.8.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.14...v3.8.0-beta.15) (2023-11-10) + + +### Features + +* **dicomJSON:** Add Loading Other Display Sets and JSON Metadata Generation script ([#3777](https://github.com/OHIF/Viewers/issues/3777)) ([43b1c17](https://github.com/OHIF/Viewers/commit/43b1c17209502e4876ad59bae09ed9442eda8024)) + + + + + +# [3.8.0-beta.14](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.13...v3.8.0-beta.14) (2023-11-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.8.0-beta.13](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.12...v3.8.0-beta.13) (2023-11-09) + + +### Bug Fixes + +* **arrow:** ArrowAnnotate text key cause validation error ([#3771](https://github.com/OHIF/Viewers/issues/3771)) ([8af1046](https://github.com/OHIF/Viewers/commit/8af10468035f1f59e0a21e579d50ad63c8cbf7ad)) + + + + + +# [3.8.0-beta.12](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.11...v3.8.0-beta.12) (2023-11-08) + + +### Features + +* add VolumeViewport rotation ([#3776](https://github.com/OHIF/Viewers/issues/3776)) ([442f99d](https://github.com/OHIF/Viewers/commit/442f99d5eb2ceece7def20e14da59af1dd7d8442)) + + + + + +# [3.8.0-beta.11](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.10...v3.8.0-beta.11) (2023-11-08) + + +### Features + +* **hp callback:** Add viewport ready callback ([#3772](https://github.com/OHIF/Viewers/issues/3772)) ([bf252bc](https://github.com/OHIF/Viewers/commit/bf252bcec2aae3a00479fdcb732110b344bcf2c0)) + + + + + +# [3.8.0-beta.10](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.9...v3.8.0-beta.10) (2023-11-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.8.0-beta.9](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.8...v3.8.0-beta.9) (2023-11-02) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.8.0-beta.8](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.7...v3.8.0-beta.8) (2023-10-31) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.8.0-beta.7](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.6...v3.8.0-beta.7) (2023-10-30) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.8.0-beta.6](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.5...v3.8.0-beta.6) (2023-10-25) + + +### Bug Fixes + +* **toolbar:** allow customizable toolbar for active viewport and allow active tool to be deactivated via a click ([#3608](https://github.com/OHIF/Viewers/issues/3608)) ([dd6d976](https://github.com/OHIF/Viewers/commit/dd6d9768bbca1d3cc472e8c1e6d85822500b96ef)) + + + + + +# [3.8.0-beta.5](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.4...v3.8.0-beta.5) (2023-10-24) + + +### Bug Fixes + +* **sr:** dcm4chee requires the patient name for an SR to match what is in the original study ([#3739](https://github.com/OHIF/Viewers/issues/3739)) ([d98439f](https://github.com/OHIF/Viewers/commit/d98439fe7f3825076dbc87b664a1d1480ff414d3)) + + + + + +# [3.8.0-beta.4](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.3...v3.8.0-beta.4) (2023-10-23) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.8.0-beta.3](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.2...v3.8.0-beta.3) (2023-10-23) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.8.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.1...v3.8.0-beta.2) (2023-10-19) + + +### Bug Fixes + +* **cine:** Use the frame rate specified in DICOM and optionally auto play cine ([#3735](https://github.com/OHIF/Viewers/issues/3735)) ([d9258ec](https://github.com/OHIF/Viewers/commit/d9258eca70587cf4dc18be4e56c79b16bae73d6d)) + + + + + +# [3.8.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.0...v3.8.0-beta.1) (2023-10-19) + + +### Bug Fixes + +* **calibration:** No calibration popup caused by perhaps an unused code optimization for production builds ([#3736](https://github.com/OHIF/Viewers/issues/3736)) ([93d798d](https://github.com/OHIF/Viewers/commit/93d798db99c0dee53ef73c376f8a74ac3049cf3f)) + + + + + +# [3.8.0-beta.0](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.110...v3.8.0-beta.0) (2023-10-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.110](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.109...v3.7.0-beta.110) (2023-10-11) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.109](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.108...v3.7.0-beta.109) (2023-10-11) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.107...v3.7.0-beta.108) (2023-10-10) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.106...v3.7.0-beta.107) (2023-10-10) + + +### Bug Fixes + +* **modules:** add stylus loader as an option to be uncommented ([#3710](https://github.com/OHIF/Viewers/issues/3710)) ([7c57f67](https://github.com/OHIF/Viewers/commit/7c57f67844b790fc6e47ac3f9708bf9d576389c8)) + + + + + +# [3.7.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.105...v3.7.0-beta.106) (2023-10-10) + + +### Bug Fixes + +* **segmentation:** Various fixes for segmentation mode and other ([#3709](https://github.com/OHIF/Viewers/issues/3709)) ([a9a6ad5](https://github.com/OHIF/Viewers/commit/a9a6ad50eae67b43b8b34efc07182d788cacdcfe)) + + + + + +# [3.7.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.104...v3.7.0-beta.105) (2023-10-10) + + +### Bug Fixes + +* **voi:** should publish voi change event on reset ([#3707](https://github.com/OHIF/Viewers/issues/3707)) ([52f34c6](https://github.com/OHIF/Viewers/commit/52f34c64d014f433ec1661a39b47e7fb27f15332)) + + + + + +# [3.7.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.103...v3.7.0-beta.104) (2023-10-09) + + +### Bug Fixes + +* **modality unit:** fix the modality unit per target via upgrade of cs3d ([#3706](https://github.com/OHIF/Viewers/issues/3706)) ([0a42d57](https://github.com/OHIF/Viewers/commit/0a42d573bbca7f2551a831a46d3aa6b56674a580)) + + + + + +# [3.7.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.102...v3.7.0-beta.103) (2023-10-09) + + +### Bug Fixes + +* **segmentation:** do not use SAB if not specified ([#3705](https://github.com/OHIF/Viewers/issues/3705)) ([4911e47](https://github.com/OHIF/Viewers/commit/4911e4796cef5e22cb7cc0ca73dc5c956bc75339)) + + + + + +# [3.7.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.101...v3.7.0-beta.102) (2023-10-06) + + +### Features + +* **Segmentation:** download RTSS from Labelmap([#3692](https://github.com/OHIF/Viewers/issues/3692)) ([40673f6](https://github.com/OHIF/Viewers/commit/40673f64b36b1150149c55632aa1825178a39e65)) + + + + + +# [3.7.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.100...v3.7.0-beta.101) (2023-10-06) + + +### Bug Fixes + +* **bugs:** fixing lots of bugs regarding release candidate ([#3700](https://github.com/OHIF/Viewers/issues/3700)) ([8bc12a3](https://github.com/OHIF/Viewers/commit/8bc12a37d0353160ae5ea4624dc0b244b7d59c07)) + + + + + +# [3.7.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.99...v3.7.0-beta.100) (2023-10-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.98...v3.7.0-beta.99) (2023-10-04) + + +### Bug Fixes + +* **measurement and microscopy:** various small fixes for measurement and microscopy side panel ([#3696](https://github.com/OHIF/Viewers/issues/3696)) ([c1d5ee7](https://github.com/OHIF/Viewers/commit/c1d5ee7e3f7f4c0c6bed9ae81eba5519741c5155)) + + + + + +# [3.7.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.97...v3.7.0-beta.98) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.96...v3.7.0-beta.97) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.95...v3.7.0-beta.96) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.94...v3.7.0-beta.95) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.93...v3.7.0-beta.94) (2023-10-03) + + +### Features + +* **debug:** Add timing information about time to first image/all images, and query time ([#3681](https://github.com/OHIF/Viewers/issues/3681)) ([108383b](https://github.com/OHIF/Viewers/commit/108383b9ef51e4bef82d9c932b9bc7aa5354e799)) + + + + + +# [3.7.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.92...v3.7.0-beta.93) (2023-10-03) + + +### Features + +* **displayArea:** add display area to hanging protocol ([#3691](https://github.com/OHIF/Viewers/issues/3691)) ([5e7fe91](https://github.com/OHIF/Viewers/commit/5e7fe91617d7399f85702d82e7bfa028b8010a89)) + + + + + +# [3.7.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.91...v3.7.0-beta.92) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.90...v3.7.0-beta.91) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.89...v3.7.0-beta.90) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.88...v3.7.0-beta.89) (2023-10-03) + + +### Bug Fixes + +* **dicom overlay:** Handle special cases of ArrayBuffer for various DICOM overlay attributes. ([#3684](https://github.com/OHIF/Viewers/issues/3684)) ([e36a604](https://github.com/OHIF/Viewers/commit/e36a6043315e900eeb6ce183772c7f852f478e96)) +* **StackSync:** Miscellaneous fixes for stack image sync ([#3663](https://github.com/OHIF/Viewers/issues/3663)) ([8a335bd](https://github.com/OHIF/Viewers/commit/8a335bd03d14ba87d65d7468d93f74040aa828d9)) + + + + + +# [3.7.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.87...v3.7.0-beta.88) (2023-10-03) + + +### Bug Fixes + +* **config:** support more values for the useSharedArrayBuffer ([#3688](https://github.com/OHIF/Viewers/issues/3688)) ([1129c15](https://github.com/OHIF/Viewers/commit/1129c155d2c7d46c98a5df7c09879aa3d459fa7e)) + + + + + +# [3.7.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.86...v3.7.0-beta.87) (2023-09-29) + + +### Bug Fixes + +* **no sab:** should work when shared array buffer is not required ([#3686](https://github.com/OHIF/Viewers/issues/3686)) ([a67d72d](https://github.com/OHIF/Viewers/commit/a67d72de85238b369a18c010bf6d147daefc6df5)) + + + + + +# [3.7.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.85...v3.7.0-beta.86) (2023-09-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22) + + +### Features + +* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1)) + + + + + +# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22) + + +### Performance Improvements + +* **memory:** add 16 bit texture via configuration - reduces memory by half ([#3662](https://github.com/OHIF/Viewers/issues/3662)) ([2bd3b26](https://github.com/OHIF/Viewers/commit/2bd3b26a6aa54b211ef988f3ad64ef1fe5648bab)) + + + + + +# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15) + + +### Bug Fixes + +* **measurements:** Update the calibration tool to match changes in CS3D ([#3505](https://github.com/OHIF/Viewers/issues/3505)) ([38af311](https://github.com/OHIF/Viewers/commit/38af3112ec1f94f36c0ef64ff1cf9d21c0981c81)) + + + + + +# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06) + + +### Features + +* **ImageOverlayViewerTool:** add ImageOverlayViewer tool that can render image overlay (pixel overlay) of the DICOM images ([#3163](https://github.com/OHIF/Viewers/issues/3163)) ([69115da](https://github.com/OHIF/Viewers/commit/69115da06d2d437b57e66608b435bb0bc919a90f)) + + + + + +# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01) + + +### Features + +* **grid:** remove viewportIndex and only rely on viewportId ([#3591](https://github.com/OHIF/Viewers/issues/3591)) ([4c6ff87](https://github.com/OHIF/Viewers/commit/4c6ff873e887cc30ffc09223f5cb99e5f94c9cdd)) + + + + + +# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30) + + +### Features + +* **data source UI config:** Popup the configuration dialogue whenever a data source is not fully configured ([#3620](https://github.com/OHIF/Viewers/issues/3620)) ([adedc8c](https://github.com/OHIF/Viewers/commit/adedc8c382e18a2e86a569e3d023cc55a157363f)) + + + + + +# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25) + +**Note:** Version bump only for package @ohif/extension-cornerstone + + + + + +# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23) + + +### Bug Fixes + +* **memory leak:** array buffer was sticking around in volume viewports ([#3611](https://github.com/OHIF/Viewers/issues/3611)) ([65b49ae](https://github.com/OHIF/Viewers/commit/65b49aeb1b5f38224e4892bdf32453500ee351f8)) diff --git a/extensions/cornerstone/package.json b/extensions/cornerstone/package.json index d1d650996a4..47f2ac25b7e 100644 --- a/extensions/cornerstone/package.json +++ b/extensions/cornerstone/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-cornerstone", - "version": "3.7.0-beta.46", + "version": "3.8.0-beta.60", "description": "OHIF extension for Cornerstone", "author": "OHIF", "license": "MIT", @@ -25,6 +25,8 @@ "access": "public" }, "scripts": { + "clean": "shx rm -rf dist", + "clean:deep": "yarn run clean && shx rm -rf node_modules", "dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --output-pathinfo", "dev:cornerstone": "yarn run dev", "build": "cross-env NODE_ENV=production webpack --progress --config .webpack/webpack.prod.js", @@ -36,10 +38,10 @@ "@cornerstonejs/codec-libjpeg-turbo-8bit": "^1.2.2", "@cornerstonejs/codec-openjpeg": "^1.2.2", "@cornerstonejs/codec-openjph": "^2.4.2", - "@cornerstonejs/dicom-image-loader": "^1.9.3", - "@ohif/core": "3.7.0-beta.46", - "@ohif/ui": "3.7.0-beta.46", - "dcmjs": "^0.29.6", + "@cornerstonejs/dicom-image-loader": "^1.63.4", + "@ohif/core": "3.8.0-beta.60", + "@ohif/ui": "3.8.0-beta.60", + "dcmjs": "^0.29.12", "dicom-parser": "^1.8.21", "hammerjs": "^2.0.8", "prop-types": "^15.6.2", @@ -52,11 +54,11 @@ }, "dependencies": { "@babel/runtime": "^7.20.13", - "@cornerstonejs/adapters": "^1.9.3", - "@cornerstonejs/core": "^1.9.3", - "@cornerstonejs/streaming-image-volume-loader": "^1.9.3", - "@cornerstonejs/tools": "^1.9.3", - "@kitware/vtk.js": "27.3.1", + "@cornerstonejs/adapters": "^1.63.4", + "@cornerstonejs/core": "^1.63.4", + "@cornerstonejs/streaming-image-volume-loader": "^1.63.4", + "@cornerstonejs/tools": "^1.63.4", + "@kitware/vtk.js": "29.7.0", "html2canvas": "^1.4.1", "lodash.debounce": "4.0.8", "lodash.merge": "^4.6.2", diff --git a/extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx b/extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx index b1c98c2fb47..ce051e99958 100644 --- a/extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx +++ b/extensions/cornerstone/src/Viewport/OHIFCornerstoneViewport.tsx @@ -11,10 +11,7 @@ import { } from '@cornerstonejs/core'; import { MeasurementService } from '@ohif/core'; import { Notification, useViewportDialog } from '@ohif/ui'; -import { - IStackViewport, - IVolumeViewport, -} from '@cornerstonejs/core/dist/esm/types'; +import { IStackViewport, IVolumeViewport } from '@cornerstonejs/core/dist/esm/types'; import { setEnabledElement } from '../state'; @@ -23,6 +20,8 @@ import CornerstoneOverlays from './Overlays/CornerstoneOverlays'; import getSOPInstanceAttributes from '../utils/measurementServiceMappings/utils/getSOPInstanceAttributes'; import CornerstoneServices from '../types/CornerstoneServices'; import CinePlayer from '../components/CinePlayer'; +import { Types } from '@ohif/core'; +import { LutPresentation, PositionPresentation } from '../types/Presentation'; const STACK = 'stack'; @@ -41,24 +40,19 @@ function areEqual(prevProps, nextProps) { return false; } - if ( - prevProps.viewportOptions.orientation !== - nextProps.viewportOptions.orientation - ) { + if (prevProps.viewportOptions.orientation !== nextProps.viewportOptions.orientation) { return false; } - if ( - prevProps.viewportOptions.toolGroupId !== - nextProps.viewportOptions.toolGroupId - ) { + if (prevProps.viewportOptions.toolGroupId !== nextProps.viewportOptions.toolGroupId) { return false; } - if ( - prevProps.viewportOptions.viewportType !== - nextProps.viewportOptions.viewportType - ) { + if (prevProps.viewportOptions.viewportType !== nextProps.viewportOptions.viewportType) { + return false; + } + + if (nextProps.viewportOptions.needsRerendering) { return false; } @@ -74,8 +68,7 @@ function areEqual(prevProps, nextProps) { const foundDisplaySet = nextDisplaySets.find( nextDisplaySet => - nextDisplaySet.displaySetInstanceUID === - prevDisplaySet.displaySetInstanceUID + nextDisplaySet.displaySetInstanceUID === prevDisplaySet.displaySetInstanceUID ); if (!foundDisplaySet) { @@ -90,9 +83,7 @@ function areEqual(prevProps, nextProps) { // check if their imageIds are the same if (foundDisplaySet.images?.length) { for (let j = 0; j < foundDisplaySet.images.length; j++) { - if ( - foundDisplaySet.images[j].imageId !== prevDisplaySet.images[j].imageId - ) { + if (foundDisplaySet.images[j].imageId !== prevDisplaySet.images[j].imageId) { return false; } } @@ -106,13 +97,11 @@ function areEqual(prevProps, nextProps) { // Then we don't need to worry about the re-renders if the props change. const OHIFCornerstoneViewport = React.memo(props => { const { - viewportIndex, displaySets, dataSource, viewportOptions, displaySetOptions, servicesManager, - commandsManager, onElementEnabled, onElementDisabled, isJumpToMeasurementDisabled, @@ -122,6 +111,7 @@ const OHIFCornerstoneViewport = React.memo(props => { initialImageIndex, } = props; + const viewportId = viewportOptions.viewportId; const [scrollbarHeight, setScrollbarHeight] = useState('100px'); const [enabledVPElement, setEnabledVPElement] = useState(null); const elementRef = useRef(); @@ -153,27 +143,17 @@ const OHIFCornerstoneViewport = React.memo(props => { } }, [elementRef]); - const cleanUpServices = useCallback(() => { - const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex( - viewportIndex - ); - - if (!viewportInfo) { - return; - } - - const viewportId = viewportInfo.getViewportId(); - const renderingEngineId = viewportInfo.getRenderingEngineId(); - const syncGroups = viewportInfo.getSyncGroups(); + const cleanUpServices = useCallback( + viewportInfo => { + const renderingEngineId = viewportInfo.getRenderingEngineId(); + const syncGroups = viewportInfo.getSyncGroups(); - toolGroupService.removeViewportFromToolGroup(viewportId, renderingEngineId); + toolGroupService.removeViewportFromToolGroup(viewportId, renderingEngineId); - syncGroupService.removeViewportFromSyncGroup( - viewportId, - renderingEngineId, - syncGroups - ); - }, [viewportIndex, viewportOptions.viewportId]); + syncGroupService.removeViewportFromSyncGroup(viewportId, renderingEngineId, syncGroups); + }, + [viewportId] + ); const elementEnabledHandler = useCallback( evt => { @@ -183,73 +163,60 @@ const OHIFCornerstoneViewport = React.memo(props => { } const { viewportId, element } = evt.detail; - const viewportInfo = cornerstoneViewportService.getViewportInfo( - viewportId - ); - const viewportIndex = viewportInfo.getViewportIndex(); - - setEnabledElement(viewportIndex, element); + const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId); + setEnabledElement(viewportId, element); setEnabledVPElement(element); const renderingEngineId = viewportInfo.getRenderingEngineId(); const toolGroupId = viewportInfo.getToolGroupId(); const syncGroups = viewportInfo.getSyncGroups(); - toolGroupService.addViewportToToolGroup( - viewportId, - renderingEngineId, - toolGroupId - ); + toolGroupService.addViewportToToolGroup(viewportId, renderingEngineId, toolGroupId); - syncGroupService.addViewportToSyncGroup( - viewportId, - renderingEngineId, - syncGroups - ); + syncGroupService.addViewportToSyncGroup(viewportId, renderingEngineId, syncGroups); + + const synchronizersStore = stateSyncService.getState().synchronizersStore; + + if (synchronizersStore?.[viewportId]?.length) { + // If the viewport used to have a synchronizer, re apply it again + _rehydrateSynchronizers(synchronizersStore, viewportId, syncGroupService); + } if (onElementEnabled) { onElementEnabled(evt); } }, - [viewportIndex, onElementEnabled, toolGroupService] + [viewportId, onElementEnabled, toolGroupService] ); // disable the element upon unmounting useEffect(() => { - cornerstoneViewportService.enableViewport( - viewportIndex, - viewportOptions, - elementRef.current - ); + cornerstoneViewportService.enableViewport(viewportId, elementRef.current); - eventTarget.addEventListener( - Enums.Events.ELEMENT_ENABLED, - elementEnabledHandler - ); + eventTarget.addEventListener(Enums.Events.ELEMENT_ENABLED, elementEnabledHandler); setImageScrollBarHeight(); return () => { - commandsManager.runCommand('storePresentation', { - viewportIndex, - }); + const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId); - cleanUpServices(); + if (!viewportInfo) { + return; + } - const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex( - viewportIndex - ); + cornerstoneViewportService.storePresentation({ viewportId }); - cornerstoneViewportService.disableElement(viewportIndex); + // This should be done after the store presentation since synchronizers + // will get cleaned up and they need the viewportInfo to be present + cleanUpServices(viewportInfo); if (onElementDisabled) { onElementDisabled(viewportInfo); } - eventTarget.removeEventListener( - Enums.Events.ELEMENT_ENABLED, - elementEnabledHandler - ); + cornerstoneViewportService.disableElement(viewportId); + + eventTarget.removeEventListener(Enums.Events.ELEMENT_ENABLED, elementEnabledHandler); }; }, []); @@ -264,10 +231,15 @@ const OHIFCornerstoneViewport = React.memo(props => { useEffect(() => { const { unsubscribe } = displaySetService.subscribe( displaySetService.EVENTS.DISPLAY_SET_SERIES_METADATA_INVALIDATED, - async invalidatedDisplaySetInstanceUID => { - const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex( - viewportIndex - ); + async ({ + displaySetInstanceUID: invalidatedDisplaySetInstanceUID, + invalidateData, + }: Types.DisplaySetSeriesMetadataInvalidatedEvent) => { + if (!invalidateData) { + return; + } + + const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId); if (viewportInfo.hasDisplaySet(invalidatedDisplaySetInstanceUID)) { const viewportData = viewportInfo.getViewportData(); @@ -279,18 +251,14 @@ const OHIFCornerstoneViewport = React.memo(props => { ); const keepCamera = true; - cornerstoneViewportService.updateViewport( - viewportIndex, - newViewportData, - keepCamera - ); + cornerstoneViewportService.updateViewport(viewportId, newViewportData, keepCamera); } } ); return () => { unsubscribe(); }; - }, [viewportIndex]); + }, [viewportId]); useEffect(() => { // handle the default viewportType to be stack @@ -309,27 +277,35 @@ const OHIFCornerstoneViewport = React.memo(props => { // The presentation state will have been stored previously by closing // a viewport. Otherwise, this viewport will be unchanged and the // presentation information will be directly carried over. - const { - lutPresentationStore, - positionPresentationStore, - } = stateSyncService.getState(); + const state = stateSyncService.getState(); + const lutPresentationStore = state.lutPresentationStore as LutPresentation; + const positionPresentationStore = state.positionPresentationStore as PositionPresentation; + const { presentationIds } = viewportOptions; const presentations = { - positionPresentation: - positionPresentationStore[presentationIds?.positionPresentationId], - lutPresentation: - lutPresentationStore[presentationIds?.lutPresentationId], + positionPresentation: positionPresentationStore[presentationIds?.positionPresentationId], + lutPresentation: lutPresentationStore[presentationIds?.lutPresentationId], }; let measurement; - if (cacheJumpToMeasurementEvent?.viewportIndex === viewportIndex) { + 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 + // so we need to re-render (force update via change of the needsRerendering) so that React + // does the diffing and decides we should render this again (although the id and element has not changed) + // so that the CornerstoneViewportService can decide whether to render the segmentation or not. Not that we reached here we can turn it off. + if (viewportOptions.needsRerendering) { + viewportOptions.needsRerendering = false; + } + cornerstoneViewportService.setViewportData( - viewportIndex, + viewportId, viewportData, viewportOptions, displaySetOptions, @@ -362,7 +338,7 @@ const OHIFCornerstoneViewport = React.memo(props => { measurementService, displaySetService, elementRef, - viewportIndex, + viewportId, displaySets, viewportGridService, cornerstoneViewportService @@ -372,7 +348,7 @@ const OHIFCornerstoneViewport = React.memo(props => { measurementService, displaySetService, elementRef, - viewportIndex, + viewportId, displaySets, viewportGridService, cornerstoneViewportService @@ -381,14 +357,12 @@ const OHIFCornerstoneViewport = React.memo(props => { return () => { unsubscribeFromJumpToMeasurementEvents(); }; - }, [displaySets, elementRef, viewportIndex]); + }, [displaySets, elementRef, viewportId]); return (
@@ -400,7 +374,7 @@ const OHIFCornerstoneViewport = React.memo(props => { ref={elementRef} >
{ />
- {viewportDialogState.viewportIndex === viewportIndex && ( + {viewportDialogState.viewportId === viewportId && ( displaySet.displaySetInstanceUID - ); const { unsubscribe } = measurementService.subscribe( MeasurementService.EVENTS.JUMP_TO_MEASUREMENT_VIEWPORT, props => { cacheJumpToMeasurementEvent = props; - const { viewportIndex: jumpIndex, measurement, isConsumed } = 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.getViewportIndexToJump( - jumpIndex, - measurement.displaySetInstanceUID, - { referencedImageId: measurement.referencedImageId } - ); + cacheJumpToMeasurementEvent.cornerstoneViewport = + cornerstoneViewportService.getViewportIdToJump( + jumpId, + measurement.displaySetInstanceUID, + { referencedImageId: measurement.referencedImageId } + ); } - if (cacheJumpToMeasurementEvent.cornerstoneViewport !== viewportIndex) { + if (cacheJumpToMeasurementEvent.cornerstoneViewport !== viewportId) { return; } _jumpToMeasurement( measurement, elementRef, - viewportIndex, + viewportId, measurementService, displaySetService, viewportGridService, @@ -479,7 +451,7 @@ function _checkForCachedJumpToMeasurementEvents( measurementService, displaySetService, elementRef, - viewportIndex, + viewportId, displaySets, viewportGridService, cornerstoneViewportService @@ -491,9 +463,7 @@ function _checkForCachedJumpToMeasurementEvents( cacheJumpToMeasurementEvent = null; return; } - const displaysUIDs = displaySets.map( - displaySet => displaySet.displaySetInstanceUID - ); + const displaysUIDs = displaySets.map(displaySet => displaySet.displaySetInstanceUID); if (!displaysUIDs?.length) { return; } @@ -505,7 +475,7 @@ function _checkForCachedJumpToMeasurementEvents( _jumpToMeasurement( measurement, elementRef, - viewportIndex, + viewportId, measurementService, displaySetService, viewportGridService, @@ -518,7 +488,7 @@ function _checkForCachedJumpToMeasurementEvents( function _jumpToMeasurement( measurement, targetElementRef, - viewportIndex, + viewportId, measurementService, displaySetService, viewportGridService, @@ -532,26 +502,19 @@ function _jumpToMeasurement( return; } - const referencedDisplaySet = displaySetService.getDisplaySetByUID( - displaySetInstanceUID - ); + const referencedDisplaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID); // Todo: setCornerstoneMeasurementActive should be handled by the toolGroupManager // to set it properly // setCornerstoneMeasurementActive(measurement); - viewportGridService.setActiveViewportIndex(viewportIndex); + viewportGridService.setActiveViewportId(viewportId); const enabledElement = getEnabledElement(targetElement); - const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex( - viewportIndex - ); if (enabledElement) { // See how the jumpToSlice() of Cornerstone3D deals with imageIdx param. - const viewport = enabledElement.viewport as - | IStackViewport - | IVolumeViewport; + const viewport = enabledElement.viewport as IStackViewport | IVolumeViewport; let imageIdIndex = 0; let viewportCameraDirectionMatch = true; @@ -559,14 +522,9 @@ function _jumpToMeasurement( if (viewport instanceof StackViewport) { const imageIds = viewport.getImageIds(); imageIdIndex = imageIds.findIndex(imageId => { - const { - SOPInstanceUID: aSOPInstanceUID, - frameNumber: aFrameNumber, - } = getSOPInstanceAttributes(imageId); - return ( - aSOPInstanceUID === SOPInstanceUID && - (!frameNumber || frameNumber === aFrameNumber) - ); + const { SOPInstanceUID: aSOPInstanceUID, frameNumber: aFrameNumber } = + getSOPInstanceAttributes(imageId); + return aSOPInstanceUID === SOPInstanceUID && (!frameNumber || frameNumber === aFrameNumber); }); } else { // for volume viewport we can't rely on the imageIdIndex since it can be @@ -581,10 +539,7 @@ function _jumpToMeasurement( // should compare abs for both planes since the direction can be flipped if ( measurementViewPlane && - !csUtils.isEqual( - measurementViewPlane.map(Math.abs), - viewportViewPlane.map(Math.abs) - ) + !csUtils.isEqual(measurementViewPlane.map(Math.abs), viewportViewPlane.map(Math.abs)) ) { viewportCameraDirectionMatch = false; } @@ -605,6 +560,58 @@ function _jumpToMeasurement( } } +function _rehydrateSynchronizers( + synchronizersStore: { [key: string]: unknown }, + viewportId: string, + syncGroupService: any +) { + synchronizersStore[viewportId].forEach(synchronizerObj => { + if (!synchronizerObj.id) { + return; + } + + const { id, sourceViewports, targetViewports } = synchronizerObj; + + const synchronizer = syncGroupService.getSynchronizer(id); + + if (!synchronizer) { + return; + } + + const sourceViewportInfo = sourceViewports.find( + sourceViewport => sourceViewport.viewportId === viewportId + ); + + const targetViewportInfo = targetViewports.find( + targetViewport => targetViewport.viewportId === viewportId + ); + + const isSourceViewportInSynchronizer = synchronizer + .getSourceViewports() + .find(sourceViewport => sourceViewport.viewportId === viewportId); + + const isTargetViewportInSynchronizer = synchronizer + .getTargetViewports() + .find(targetViewport => targetViewport.viewportId === viewportId); + + // if the viewport was previously a source viewport, add it again + if (sourceViewportInfo && !isSourceViewportInSynchronizer) { + synchronizer.addSource({ + viewportId: sourceViewportInfo.viewportId, + renderingEngineId: sourceViewportInfo.renderingEngineId, + }); + } + + // if the viewport was previously a target viewport, add it again + if (targetViewportInfo && !isTargetViewportInSynchronizer) { + synchronizer.addTarget({ + viewportId: targetViewportInfo.viewportId, + renderingEngineId: targetViewportInfo.renderingEngineId, + }); + } + }); +} + // Component displayName OHIFCornerstoneViewport.displayName = 'OHIFCornerstoneViewport'; @@ -613,7 +620,6 @@ OHIFCornerstoneViewport.defaultProps = { }; OHIFCornerstoneViewport.propTypes = { - viewportIndex: PropTypes.number.isRequired, displaySets: PropTypes.array.isRequired, dataSource: PropTypes.object.isRequired, viewportOptions: PropTypes.object, @@ -624,10 +630,7 @@ OHIFCornerstoneViewport.propTypes = { // Note: you SHOULD NOT use the initialImageIdOrIndex for manipulation // of the imageData in the OHIFCornerstoneViewport. This prop is used // to set the initial state of the viewport's first image to render - initialImageIdOrIndex: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.number, - ]), + initialImageIdOrIndex: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), }; export default OHIFCornerstoneViewport; diff --git a/extensions/cornerstone/src/Viewport/Overlays/CornerstoneOverlays.tsx b/extensions/cornerstone/src/Viewport/Overlays/CornerstoneOverlays.tsx index a860753d1e8..69f7b10faa2 100644 --- a/extensions/cornerstone/src/Viewport/Overlays/CornerstoneOverlays.tsx +++ b/extensions/cornerstone/src/Viewport/Overlays/CornerstoneOverlays.tsx @@ -6,7 +6,7 @@ import ViewportOrientationMarkers from './ViewportOrientationMarkers'; import ViewportImageSliceLoadingIndicator from './ViewportImageSliceLoadingIndicator'; function CornerstoneOverlays(props) { - const { viewportIndex, element, scrollbarHeight, servicesManager } = props; + const { viewportId, element, scrollbarHeight, servicesManager } = props; const { cornerstoneViewportService } = servicesManager.services; const [imageSliceData, setImageSliceData] = useState({ imageIndex: 0, @@ -18,7 +18,7 @@ function CornerstoneOverlays(props) { const { unsubscribe } = cornerstoneViewportService.subscribe( cornerstoneViewportService.EVENTS.VIEWPORT_DATA_CHANGED, props => { - if (props.viewportIndex !== viewportIndex) { + if (props.viewportId !== viewportId) { return; } @@ -29,16 +29,14 @@ function CornerstoneOverlays(props) { return () => { unsubscribe(); }; - }, [viewportIndex]); + }, [viewportId]); if (!element) { return null; } if (viewportData) { - const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex( - viewportIndex - ); + const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId); if (viewportInfo?.viewportOptions?.customViewportProps?.hideOverlays) { return null; @@ -48,7 +46,7 @@ function CornerstoneOverlays(props) { return (
@@ -75,7 +73,7 @@ function CornerstoneOverlays(props) { element={element} viewportData={viewportData} servicesManager={servicesManager} - viewportIndex={viewportIndex} + viewportId={viewportId} />
); diff --git a/extensions/cornerstone/src/Viewport/Overlays/CustomizableViewportOverlay.tsx b/extensions/cornerstone/src/Viewport/Overlays/CustomizableViewportOverlay.tsx index 3b92c97efce..8745fdec7f0 100644 --- a/extensions/cornerstone/src/Viewport/Overlays/CustomizableViewportOverlay.tsx +++ b/extensions/cornerstone/src/Viewport/Overlays/CustomizableViewportOverlay.tsx @@ -3,12 +3,7 @@ import { vec3 } from 'gl-matrix'; import PropTypes from 'prop-types'; import { metaData, Enums, utilities } from '@cornerstonejs/core'; import { ViewportOverlay } from '@ohif/ui'; -import { - formatPN, - formatDICOMDate, - formatDICOMTime, - formatNumberPrecision, -} from './utils'; +import { formatPN, formatDICOMDate, formatDICOMTime, formatNumberPrecision } from './utils'; import { InstanceMetadata } from 'platform/core/src/types'; import { ServicesManager } from '@ohif/core'; import { ImageSliceData } from '@cornerstonejs/core/dist/esm/types'; @@ -21,7 +16,6 @@ interface OverlayItemProps { element: any; viewportData: any; imageSliceData: ImageSliceData; - viewportIndex: number | null; servicesManager: ServicesManager; instance: InstanceMetadata; customization: any; @@ -56,13 +50,9 @@ function VOIOverlayItem({ voi, customization }: OverlayItemProps) { style={{ color: (customization && customization.color) || undefined }} > W: - - {windowWidth.toFixed(0)} - + {windowWidth.toFixed(0)} L: - - {windowCenter.toFixed(0)} - + {windowCenter.toFixed(0)}
); } @@ -114,14 +104,11 @@ function CustomizableViewportOverlay({ element, viewportData, imageSliceData, - viewportIndex, + viewportId, servicesManager, }) { - const { - toolbarService, - cornerstoneViewportService, - customizationService, - } = servicesManager.services; + const { toolbarService, cornerstoneViewportService, customizationService } = + servicesManager.services; const [voi, setVOI] = useState({ windowCenter: null, windowWidth: null }); const [scale, setScale] = useState(1); const [activeTools, setActiveTools] = useState([]); @@ -150,15 +137,10 @@ function CustomizableViewportOverlay({ const instanceNumber = useMemo(() => { if (viewportData != null) { - return _getInstanceNumber( - viewportData, - viewportIndex, - imageIndex, - cornerstoneViewportService - ); + return _getInstanceNumber(viewportData, viewportId, imageIndex, cornerstoneViewportService); } return null; - }, [viewportData, viewportIndex, imageIndex, cornerstoneViewportService]); + }, [viewportData, viewportId, imageIndex, cornerstoneViewportService]); /** * Initial toolbar state @@ -179,10 +161,7 @@ function CustomizableViewportOverlay({ } const { lower, upper } = range; - const { windowWidth, windowCenter } = utilities.windowLevel.toWindowLevel( - lower, - upper - ); + const { windowWidth, windowCenter } = utilities.windowLevel.toWindowLevel(lower, upper); setVOI({ windowCenter, windowWidth }); }; @@ -192,7 +171,7 @@ function CustomizableViewportOverlay({ return () => { element.removeEventListener(Enums.Events.VOI_MODIFIED, updateVOI); }; - }, [viewportIndex, viewportData, voi, element]); + }, [viewportId, viewportData, voi, element]); /** * Updating the scale when the viewport changes its zoom @@ -205,9 +184,7 @@ function CustomizableViewportOverlay({ previousCamera.parallelScale !== camera.parallelScale || previousCamera.scale !== camera.scale ) { - const viewport = cornerstoneViewportService.getCornerstoneViewportByIndex( - viewportIndex - ); + const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); if (!viewport) { return; @@ -226,8 +203,7 @@ function CustomizableViewportOverlay({ const { spacing } = imageData; // convert parallel scale to scale - const scale = - (element.clientHeight * spacing[0] * 0.5) / camera.parallelScale; + const scale = (element.clientHeight * spacing[0] * 0.5) / camera.parallelScale; setScale(scale); } }; @@ -237,7 +213,7 @@ function CustomizableViewportOverlay({ return () => { element.removeEventListener(Enums.Events.CAMERA_MODIFIED, updateScale); }; - }, [viewportIndex, viewportData, cornerstoneViewportService, element]); + }, [viewportId, viewportData, cornerstoneViewportService, element]); /** * Updating the active tools when the toolbar changes @@ -262,7 +238,7 @@ function CustomizableViewportOverlay({ element, viewportData, imageSliceData, - viewportIndex, + viewportId, servicesManager, customization: item, formatters: { @@ -296,7 +272,7 @@ function CustomizableViewportOverlay({ element, viewportData, imageSliceData, - viewportIndex, + viewportId, servicesManager, customizationService, instance, @@ -343,9 +319,7 @@ function CustomizableViewportOverlay({ return ( <> {items.map((item, i) => ( -
- {_renderOverlayItem(item)} -
+
{_renderOverlayItem(item)}
))} ); @@ -356,9 +330,7 @@ function CustomizableViewportOverlay({ return ( <> {items.map((item, i) => ( -
- {_renderOverlayItem(item)} -
+
{_renderOverlayItem(item)}
))} ); @@ -388,12 +360,7 @@ function _getViewportInstance(viewportData, imageIndex) { return imageId ? metaData.get('instance', imageId) || {} : {}; } -function _getInstanceNumber( - viewportData, - viewportIndex, - imageIndex, - cornerstoneViewportService -) { +function _getInstanceNumber(viewportData, viewportId, imageIndex, cornerstoneViewportService) { let instanceNumber; if (viewportData.viewportType === Enums.ViewportType.STACK) { @@ -406,7 +373,7 @@ function _getInstanceNumber( instanceNumber = _getInstanceNumberFromVolume( viewportData, imageIndex, - viewportIndex, + viewportId, cornerstoneViewportService ); } @@ -436,12 +403,7 @@ function _getInstanceNumberFromStack(viewportData, imageIndex) { // Since volume viewports can be in any view direction, they can render // a reconstructed image which don't have imageIds; therefore, no instance and instanceNumber // Here we check if viewport is in the acquisition direction and if so, we get the instanceNumber -function _getInstanceNumberFromVolume( - viewportData, - imageIndex, - viewportIndex, - cornerstoneViewportService -) { +function _getInstanceNumberFromVolume(viewportData, viewportId, cornerstoneViewportService) { const volumes = viewportData.volumes; // Todo: support fusion of acquisition plane which has instanceNumber @@ -452,9 +414,7 @@ function _getInstanceNumberFromVolume( const volume = volumes[0]; const { direction, imageIds } = volume; - const cornerstoneViewport = cornerstoneViewportService.getCornerstoneViewportByIndex( - viewportIndex - ); + const cornerstoneViewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); if (!cornerstoneViewport) { return; @@ -477,8 +437,7 @@ function _getInstanceNumberFromVolume( return {}; } - const { instanceNumber } = - metaData.get('generalImageModule', imageId) || {}; + const { instanceNumber } = metaData.get('generalImageModule', imageId) || {}; return parseInt(instanceNumber); } } @@ -486,7 +445,7 @@ function _getInstanceNumberFromVolume( CustomizableViewportOverlay.propTypes = { viewportData: PropTypes.object, imageIndex: PropTypes.number, - viewportIndex: PropTypes.number, + viewportId: PropTypes.string, }; export default CustomizableViewportOverlay; diff --git a/extensions/cornerstone/src/Viewport/Overlays/ViewportImageScrollbar.tsx b/extensions/cornerstone/src/Viewport/Overlays/ViewportImageScrollbar.tsx index 074e0a969ff..2f7b71a45da 100644 --- a/extensions/cornerstone/src/Viewport/Overlays/ViewportImageScrollbar.tsx +++ b/extensions/cornerstone/src/Viewport/Overlays/ViewportImageScrollbar.tsx @@ -7,34 +7,24 @@ import { ServicesManger } from '@ohif/core'; function CornerstoneImageScrollbar({ viewportData, - viewportIndex, + viewportId, element, imageSliceData, setImageSliceData, scrollbarHeight, servicesManager, }) { - const { - cineService, - cornerstoneViewportService, - } = (servicesManager as ServicesManger).services; + const { cineService, cornerstoneViewportService } = (servicesManager as ServicesManger).services; - const onImageScrollbarChange = (imageIndex, viewportIndex) => { - const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex( - viewportIndex - ); - - const viewportId = viewportInfo.getViewportId(); - const viewport = cornerstoneViewportService.getCornerstoneViewport( - viewportId - ); + const onImageScrollbarChange = (imageIndex, viewportId) => { + const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); const { isCineEnabled } = cineService.getState(); if (isCineEnabled) { // on image scrollbar change, stop the CINE if it is playing cineService.stopClip(element); - cineService.setCine({ id: viewportIndex, isPlaying: false }); + cineService.setCine({ id: viewportId, isPlaying: false }); } csToolsUtils.jumpToSlice(viewport.element, { @@ -48,9 +38,7 @@ function CornerstoneImageScrollbar({ return; } - const viewport = cornerstoneViewportService.getCornerstoneViewportByIndex( - viewportIndex - ); + const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); if (!viewport) { return; @@ -79,7 +67,7 @@ function CornerstoneImageScrollbar({ const { imageIndex, numberOfSlices } = sliceData; setImageSliceData({ imageIndex, numberOfSlices }); } - }, [viewportIndex, viewportData]); + }, [viewportId, viewportData]); useEffect(() => { if (viewportData?.viewportType !== Enums.ViewportType.STACK) { @@ -95,16 +83,10 @@ function CornerstoneImageScrollbar({ }); }; - element.addEventListener( - Enums.Events.STACK_VIEWPORT_SCROLL, - updateStackIndex - ); + element.addEventListener(Enums.Events.STACK_VIEWPORT_SCROLL, updateStackIndex); return () => { - element.removeEventListener( - Enums.Events.STACK_VIEWPORT_SCROLL, - updateStackIndex - ); + element.removeEventListener(Enums.Events.STACK_VIEWPORT_SCROLL, updateStackIndex); }; }, [viewportData, element]); @@ -122,19 +104,14 @@ function CornerstoneImageScrollbar({ element.addEventListener(Enums.Events.VOLUME_NEW_IMAGE, updateVolumeIndex); return () => { - element.removeEventListener( - Enums.Events.VOLUME_NEW_IMAGE, - updateVolumeIndex - ); + element.removeEventListener(Enums.Events.VOLUME_NEW_IMAGE, updateVolumeIndex); }; }, [viewportData, element]); return ( onImageScrollbarChange(evt, viewportIndex)} - max={ - imageSliceData.numberOfSlices ? imageSliceData.numberOfSlices - 1 : 0 - } + onChange={evt => onImageScrollbarChange(evt, viewportId)} + max={imageSliceData.numberOfSlices ? imageSliceData.numberOfSlices - 1 : 0} height={scrollbarHeight} value={imageSliceData.imageIndex} /> @@ -143,7 +120,7 @@ function CornerstoneImageScrollbar({ CornerstoneImageScrollbar.propTypes = { viewportData: PropTypes.object, - viewportIndex: PropTypes.number.isRequired, + viewportId: PropTypes.string.isRequired, element: PropTypes.instanceOf(Element), scrollbarHeight: PropTypes.string, imageSliceData: PropTypes.object.isRequired, diff --git a/extensions/cornerstone/src/Viewport/Overlays/ViewportImageSliceLoadingIndicator.tsx b/extensions/cornerstone/src/Viewport/Overlays/ViewportImageSliceLoadingIndicator.tsx index fc483b5f475..70e03e534cb 100644 --- a/extensions/cornerstone/src/Viewport/Overlays/ViewportImageSliceLoadingIndicator.tsx +++ b/extensions/cornerstone/src/Viewport/Overlays/ViewportImageSliceLoadingIndicator.tsx @@ -33,26 +33,14 @@ function ViewportImageSliceLoadingIndicator({ viewportData, element }) { }; useEffect(() => { - element.addEventListener( - Enums.Events.STACK_VIEWPORT_SCROLL, - setLoadingState - ); + element.addEventListener(Enums.Events.STACK_VIEWPORT_SCROLL, setLoadingState); element.addEventListener(Enums.Events.IMAGE_LOAD_ERROR, setErrorState); - element.addEventListener( - Enums.Events.STACK_NEW_IMAGE, - setFinishLoadingState - ); + element.addEventListener(Enums.Events.STACK_NEW_IMAGE, setFinishLoadingState); return () => { - element.removeEventListener( - Enums.Events.STACK_VIEWPORT_SCROLL, - setLoadingState - ); + element.removeEventListener(Enums.Events.STACK_VIEWPORT_SCROLL, setLoadingState); - element.removeEventListener( - Enums.Events.STACK_NEW_IMAGE, - setFinishLoadingState - ); + element.removeEventListener(Enums.Events.STACK_NEW_IMAGE, setFinishLoadingState); element.removeEventListener(Enums.Events.IMAGE_LOAD_ERROR, setErrorState); }; @@ -61,8 +49,8 @@ function ViewportImageSliceLoadingIndicator({ viewportData, element }) { if (error) { return ( <> -
-
+
+

Error Loading Image

An error has occurred.

@@ -78,8 +66,8 @@ function ViewportImageSliceLoadingIndicator({ viewportData, element }) { return ( // IMPORTANT: we need to use the pointer-events-none class to prevent the loading indicator from // interacting with the mouse, since scrolling should propagate to the viewport underneath -
-
+
+

Loading...

diff --git a/extensions/cornerstone/src/Viewport/Overlays/ViewportOrientationMarkers.tsx b/extensions/cornerstone/src/Viewport/Overlays/ViewportOrientationMarkers.tsx index 8e431ac525e..2f33456a42c 100644 --- a/extensions/cornerstone/src/Viewport/Overlays/ViewportOrientationMarkers.tsx +++ b/extensions/cornerstone/src/Viewport/Overlays/ViewportOrientationMarkers.tsx @@ -13,16 +13,13 @@ import { vec3 } from 'gl-matrix'; import './ViewportOrientationMarkers.css'; -const { - getOrientationStringLPS, - invertOrientationStringLPS, -} = utilities.orientation; +const { getOrientationStringLPS, invertOrientationStringLPS } = utilities.orientation; function ViewportOrientationMarkers({ element, viewportData, imageSliceData, - viewportIndex, + viewportId, servicesManager, orientationMarkers = ['top', 'left'], }) { @@ -33,9 +30,7 @@ function ViewportOrientationMarkers({ const { cornerstoneViewportService } = servicesManager.services; useEffect(() => { - const cameraModifiedListener = ( - evt: Types.EventTypes.CameraModifiedEvent - ) => { + const cameraModifiedListener = (evt: Types.EventTypes.CameraModifiedEvent) => { const { rotation, previousCamera, camera } = evt.detail; if (rotation !== undefined) { @@ -57,16 +52,10 @@ function ViewportOrientationMarkers({ } }; - element.addEventListener( - Enums.Events.CAMERA_MODIFIED, - cameraModifiedListener - ); + element.addEventListener(Enums.Events.CAMERA_MODIFIED, cameraModifiedListener); return () => { - element.removeEventListener( - Enums.Events.CAMERA_MODIFIED, - cameraModifiedListener - ); + element.removeEventListener(Enums.Events.CAMERA_MODIFIED, cameraModifiedListener); }; }, []); @@ -85,8 +74,7 @@ function ViewportOrientationMarkers({ return false; } - ({ rowCosines, columnCosines } = - metaData.get('imagePlaneModule', imageId) || {}); + ({ rowCosines, columnCosines } = metaData.get('imagePlaneModule', imageId) || {}); } else { if (!element || !getEnabledElement(element)) { return ''; @@ -114,9 +102,7 @@ function ViewportOrientationMarkers({ flipHorizontal ); - const ohifViewport = cornerstoneViewportService.getViewportInfoByIndex( - viewportIndex - ); + const ohifViewport = cornerstoneViewportService.getViewportInfo(viewportId); if (!ohifViewport) { console.log('ViewportOrientationMarkers::No viewport'); @@ -126,9 +112,7 @@ function ViewportOrientationMarkers({ // Todo: probably this can be done in a better way in which we identify bright // background - const isLight = backgroundColor - ? csUtils.isEqual(backgroundColor, [1, 1, 1]) - : false; + const isLight = backgroundColor ? csUtils.isEqual(backgroundColor, [1, 1, 1]) : false; return orientationMarkers.map((m, index) => (
{ element.removeEventListener(Enums.Events.VOI_MODIFIED, updateVOI); }; - }, [viewportIndex, viewportData, voi, element]); + }, [viewportId, viewportData, voi, element]); /** * Updating the scale when the viewport changes its zoom @@ -86,9 +80,7 @@ function CornerstoneViewportOverlay({ previousCamera.parallelScale !== camera.parallelScale || previousCamera.scale !== camera.scale ) { - const viewport = cornerstoneViewportService.getCornerstoneViewportByIndex( - viewportIndex - ); + const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); if (!viewport) { return; @@ -107,8 +99,7 @@ function CornerstoneViewportOverlay({ const { spacing } = imageData; // convert parallel scale to scale - const scale = - (element.clientHeight * spacing[0] * 0.5) / camera.parallelScale; + const scale = (element.clientHeight * spacing[0] * 0.5) / camera.parallelScale; setScale(scale); } }; @@ -118,7 +109,7 @@ function CornerstoneViewportOverlay({ return () => { element.removeEventListener(Enums.Events.CAMERA_MODIFIED, updateScale); }; - }, [viewportIndex, viewportData]); + }, [viewportId, viewportData]); const getTopLeftContent = useCallback(() => { const { windowWidth, windowCenter } = voi; @@ -168,7 +159,7 @@ function CornerstoneViewportOverlay({ instanceNumber = _getInstanceNumberFromVolume( viewportData, imageIndex, - viewportIndex, + viewportId, cornerstoneViewportService ); } @@ -183,15 +174,13 @@ function CornerstoneViewportOverlay({
); - }, [imageSliceData, viewportData, viewportIndex]); + }, [imageSliceData, viewportData, viewportId]); if (!viewportData) { return null; } - const ohifViewport = cornerstoneViewportService.getViewportInfoByIndex( - viewportIndex - ); + const ohifViewport = cornerstoneViewportService.getViewportInfo(viewportId); if (!ohifViewport) { return null; @@ -201,9 +190,7 @@ function CornerstoneViewportOverlay({ // Todo: probably this can be done in a better way in which we identify bright // background - const isLight = backgroundColor - ? utilities.isEqual(backgroundColor, [1, 1, 1]) - : false; + const isLight = backgroundColor ? utilities.isEqual(backgroundColor, [1, 1, 1]) : false; return ( { - const viewportInfo = cornerstoneViewportService.getViewportInfo( - viewportId - ); + const viewportInfo = cornerstoneViewportService.getViewportInfo(viewportId); if (!viewportInfo) { console.warn('No viewport found for viewportId:', viewportId); return; } - const viewportIndex = viewportInfo.getViewportIndex(); - viewportGridService.setActiveViewportIndex(viewportIndex); + viewportGridService.setActiveViewportId(viewportId); }, arrowTextCallback: ({ callback, data }) => { callInputDialog(uiDialogService, data, callback); }, + cleanUpCrosshairs: () => { + // if the crosshairs tool is active, deactivate it and set window level active + // since we are going back to main non-mpr HP + const activeViewportToolGroup = toolGroupService.getToolGroup(null); + + if (activeViewportToolGroup._toolInstances?.Crosshairs?.mode === Enums.ToolModes.Active) { + actions.toolbarServiceRecordInteraction({ + interactionType: 'tool', + commands: [ + { + commandOptions: { + toolName: 'WindowLevel', + }, + context: 'CORNERSTONE', + }, + ], + }); + } + }, toggleCine: () => { const { viewports } = viewportGridService.getState(); const { isCineEnabled } = cineService.getState(); cineService.setIsCineEnabled(!isCineEnabled); toolbarService.setButton('Cine', { props: { isActive: !isCineEnabled } }); - viewports.forEach((_, index) => - cineService.setCine({ id: index, isPlaying: false }) - ); + viewports.forEach((_, index) => cineService.setCine({ id: index, isPlaying: false })); }, setWindowLevel({ window, level, toolGroupId }) { // convert to numbers @@ -260,9 +259,7 @@ function commandsModule({ const windowCenterNum = Number(level); const { viewportId } = _getActiveViewportEnabledElement(); - const viewportToolGroupId = toolGroupService.getToolGroupForViewport( - viewportId - ); + const viewportToolGroupId = toolGroupService.getToolGroupForViewport(viewportId); if (toolGroupId && toolGroupId !== viewportToolGroupId) { return; @@ -272,10 +269,7 @@ function commandsModule({ const renderingEngine = cornerstoneViewportService.getRenderingEngine(); const viewport = renderingEngine.getViewport(viewportId); - const { lower, upper } = csUtils.windowLevel.toLowHighRange( - windowWidthNum, - windowCenterNum - ); + const { lower, upper } = csUtils.windowLevel.toLowHighRange(windowWidthNum, windowCenterNum); viewport.setProperties({ voiRange: { @@ -292,8 +286,12 @@ function commandsModule({ toolbarServiceRecordInteraction: props => { toolbarService.recordInteraction(props); }, - - setToolActive: ({ toolName, toolGroupId = null }) => { + // Enable or disable a toggleable command, without calling the activation + // Used to setup already active tools from hanging protocols + setToolbarToggled: props => { + toolbarService.setToggled(props.toolId, props.isActive ?? true); + }, + setToolActive: ({ toolName, toolGroupId = null, toggledState }) => { if (toolName === 'Crosshairs') { const activeViewportToolGroup = toolGroupService.getToolGroup(null); @@ -310,29 +308,15 @@ function commandsModule({ } } - const { viewports } = viewportGridService.getState() || { - viewports: [], - }; - - const toolGroup = toolGroupService.getToolGroup(toolGroupId); - const toolGroupViewportIds = toolGroup?.getViewportIds?.(); + const { viewports } = viewportGridService.getState(); - // if toolGroup has been destroyed, or its viewports have been removed - if (!toolGroupViewportIds || !toolGroupViewportIds.length) { + if (!viewports.size) { return; } - const filteredViewports = viewports.filter(viewport => { - if (!viewport.viewportOptions) { - return false; - } - - return toolGroupViewportIds.includes( - viewport.viewportOptions.viewportId - ); - }); + const toolGroup = toolGroupService.getToolGroup(toolGroupId); - if (!filteredViewports.length) { + if (!toolGroup) { return; } @@ -358,6 +342,14 @@ function commandsModule({ toolGroup.setToolPassive(activeToolName); } } + + // If there is a toggle state, then simply set the enabled/disabled state without + // setting the tool active. + if (toggledState != null) { + toggledState ? toolGroup.setToolEnabled(toolName) : toolGroup.setToolDisabled(toolName); + return; + } + // Set the new toolName to be active toolGroup.setToolActive(toolName, { bindings: [ @@ -368,13 +360,9 @@ function commandsModule({ }); }, showDownloadViewportModal: () => { - const { activeViewportIndex } = viewportGridService.getState(); + const { activeViewportId } = viewportGridService.getState(); - if ( - !cornerstoneViewportService.getCornerstoneViewportByIndex( - activeViewportIndex - ) - ) { + if (!cornerstoneViewportService.getCornerstoneViewport(activeViewportId)) { // Cannot download a non-cornerstone viewport (image). uiNotificationService.show({ title: 'Download Image', @@ -391,7 +379,7 @@ function commandsModule({ content: CornerstoneViewportDownloadForm, title: 'Download High Quality Image', contentProps: { - activeViewportIndex, + activeViewportId, onClose: uiModalService.hide, cornerstoneViewportService, }, @@ -406,8 +394,16 @@ function commandsModule({ const { viewport } = enabledElement; - if (viewport instanceof StackViewport) { - const { rotation: currentRotation } = viewport.getProperties(); + if (viewport instanceof BaseVolumeViewport) { + const camera = viewport.getCamera(); + const rotAngle = (rotation * Math.PI) / 180; + const rotMat = mat4.identity(new Float32Array(16)); + mat4.rotate(rotMat, rotMat, rotAngle, camera.viewPlaneNormal); + const rotatedViewUp = vec3.transformMat4(vec3.create(), camera.viewUp, rotMat); + viewport.setCamera({ viewUp: rotatedViewUp as CoreTypes.Point3 }); + viewport.render(); + } else if (viewport.getRotation !== undefined) { + const currentRotation = viewport.getRotation(); const newRotation = (currentRotation + rotation) % 360; viewport.setProperties({ rotation: newRotation }); viewport.render(); @@ -471,13 +467,8 @@ function commandsModule({ const { viewport } = enabledElement; - if (viewport instanceof StackViewport) { - viewport.resetProperties(); - viewport.resetCamera(); - } else { - // Todo: add reset properties for volume viewport - viewport.resetCamera(); - } + viewport.resetProperties?.(); + viewport.resetCamera(); viewport.render(); }, @@ -513,9 +504,7 @@ function commandsModule({ } viewport = enabledElement.viewport; } else { - viewport = cornerstoneViewportService.getCornerstoneViewport( - gridViewport.id - ); + viewport = cornerstoneViewportService.getCornerstoneViewport(gridViewport.id); } // Get number of slices @@ -525,14 +514,12 @@ function commandsModule({ if (viewport instanceof StackViewport) { numberOfSlices = viewport.getImageIds().length; } else if (viewport instanceof VolumeViewport) { - numberOfSlices = csUtils.getImageSliceDataForVolumeViewport(viewport) - .numberOfSlices; + numberOfSlices = csUtils.getImageSliceDataForVolumeViewport(viewport).numberOfSlices; } else { throw new Error('Unsupported viewport type'); } - const jumpIndex = - imageIndex < 0 ? numberOfSlices + imageIndex : imageIndex; + const jumpIndex = imageIndex < 0 ? numberOfSlices + imageIndex : imageIndex; if (jumpIndex >= numberOfSlices || jumpIndex < 0) { throw new Error(`Can't jump to ${imageIndex}`); } @@ -553,15 +540,8 @@ function commandsModule({ cstUtils.scroll(viewport, options); }, - setViewportColormap: ({ - viewportIndex, - displaySetInstanceUID, - colormap, - immediate = false, - }) => { - const viewport = cornerstoneViewportService.getCornerstoneViewportByIndex( - viewportIndex - ); + setViewportColormap: ({ viewportId, displaySetInstanceUID, colormap, immediate = false }) => { + const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); const actorEntries = viewport.getActors(); @@ -577,37 +557,29 @@ function commandsModule({ viewport.render(); } }, - incrementActiveViewport: () => { - const { activeViewportIndex, viewports } = viewportGridService.getState(); - const nextViewportIndex = (activeViewportIndex + 1) % viewports.length; - viewportGridService.setActiveViewportIndex(nextViewportIndex); - }, - decrementActiveViewport: () => { - const { activeViewportIndex, viewports } = viewportGridService.getState(); + changeActiveViewport: ({ direction = 1 }) => { + const { activeViewportId, viewports } = viewportGridService.getState(); + const viewportIds = Array.from(viewports.keys()); + const currentIndex = viewportIds.indexOf(activeViewportId); const nextViewportIndex = - (activeViewportIndex - 1 + viewports.length) % viewports.length; - viewportGridService.setActiveViewportIndex(nextViewportIndex); + (currentIndex + direction + viewportIds.length) % viewportIds.length; + viewportGridService.setActiveViewportId(viewportIds[nextViewportIndex] as string); }, - toggleStackImageSync: ({ toggledState }) => { - toggleStackImageSync({ - getEnabledElement, + + toggleImageSliceSync: ({ toggledState }) => { + toggleImageSliceSync({ servicesManager, toggledState, }); }, - toggleReferenceLines: ({ toggledState }) => { - const { activeViewportIndex } = viewportGridService.getState(); - const viewportInfo = cornerstoneViewportService.getViewportInfoByIndex( - activeViewportIndex - ); + setSourceViewportForReferenceLinesTool: ({ toggledState, viewportId }) => { + if (!viewportId) { + const { activeViewportId } = viewportGridService.getState(); + viewportId = activeViewportId; + } - const viewportId = viewportInfo.getViewportId(); const toolGroup = toolGroupService.getToolGroupForViewport(viewportId); - if (!toggledState) { - toolGroup.setToolDisabled(ReferenceLinesTool.toolName); - } - toolGroup.setToolConfiguration( ReferenceLinesTool.toolName, { @@ -615,36 +587,26 @@ function commandsModule({ }, true // overwrite ); - toolGroup.setToolEnabled(ReferenceLinesTool.toolName); }, - storePresentation: ({ viewportIndex }) => { - const presentation = cornerstoneViewportService.getPresentation( - viewportIndex - ); - if (!presentation || !presentation.presentationIds) { - return; - } - const { - lutPresentationStore, - positionPresentationStore, - } = stateSyncService.getState(); - const { presentationIds } = presentation; - const { lutPresentationId, positionPresentationId } = - presentationIds || {}; - const storeState = {}; - if (lutPresentationId) { - storeState.lutPresentationStore = { - ...lutPresentationStore, - [lutPresentationId]: presentation, - }; - } - if (positionPresentationId) { - storeState.positionPresentationStore = { - ...positionPresentationStore, - [positionPresentationId]: presentation, - }; - } - stateSyncService.store(storeState); + storePresentation: ({ viewportId }) => { + cornerstoneViewportService.storePresentation({ viewportId }); + }, + + attachProtocolViewportDataListener: ({ protocol, stageIndex }) => { + const EVENT = cornerstoneViewportService.EVENTS.VIEWPORT_DATA_CHANGED; + const command = protocol.callbacks.onViewportDataInitialized; + const numPanes = protocol.stages?.[stageIndex]?.viewports.length ?? 1; + let numPanesWithData = 0; + const { unsubscribe } = cornerstoneViewportService.subscribe(EVENT, evt => { + numPanesWithData++; + + if (numPanesWithData === numPanes) { + commandsManager.run(...command); + + // Unsubscribe from the event + unsubscribe(EVENT); + } + }); }, }; @@ -672,7 +634,6 @@ function commandsModule({ storeContexts: [], options: {}, }, - deleteMeasurement: { commandFn: actions.deleteMeasurement, }, @@ -701,10 +662,11 @@ function commandsModule({ options: { rotation: -90 }, }, incrementActiveViewport: { - commandFn: actions.incrementActiveViewport, + commandFn: actions.changeActiveViewport, }, decrementActiveViewport: { - commandFn: actions.decrementActiveViewport, + commandFn: actions.changeActiveViewport, + options: { direction: -1 }, }, flipViewportHorizontal: { commandFn: actions.flipViewportHorizontal, @@ -764,16 +726,23 @@ function commandsModule({ setViewportColormap: { commandFn: actions.setViewportColormap, }, - toggleStackImageSync: { - commandFn: actions.toggleStackImageSync, + toggleImageSliceSync: { + commandFn: actions.toggleImageSliceSync, }, - toggleReferenceLines: { - commandFn: actions.toggleReferenceLines, + setSourceViewportForReferenceLinesTool: { + commandFn: actions.setSourceViewportForReferenceLinesTool, }, storePresentation: { commandFn: actions.storePresentation, - storeContexts: [], - options: {}, + }, + setToolbarToggled: { + commandFn: actions.setToolbarToggled, + }, + cleanUpCrosshairs: { + commandFn: actions.cleanUpCrosshairs, + }, + attachProtocolViewportDataListener: { + commandFn: actions.attachProtocolViewportDataListener, }, }; diff --git a/extensions/cornerstone/src/components/CinePlayer/CinePlayer.tsx b/extensions/cornerstone/src/components/CinePlayer/CinePlayer.tsx index ef54108b692..be007920ede 100644 --- a/extensions/cornerstone/src/components/CinePlayer/CinePlayer.tsx +++ b/extensions/cornerstone/src/components/CinePlayer/CinePlayer.tsx @@ -1,15 +1,19 @@ -import React, { useEffect } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { CinePlayer, useCine, useViewportGrid } from '@ohif/ui'; import { Enums, eventTarget } from '@cornerstonejs/core'; +import { useAppConfig } from '@state'; -function WrappedCinePlayer({ - enabledVPElement, - viewportIndex, - servicesManager, -}) { - const { toolbarService, customizationService } = servicesManager.services; - const [{ isCineEnabled, cines }, cineService] = useCine(); - const [{ activeViewportIndex }] = useViewportGrid(); +function WrappedCinePlayer({ enabledVPElement, viewportId, servicesManager }) { + const { + toolbarService, + customizationService, + displaySetService, + viewportGridService, + cineService, + } = servicesManager.services; + const [{ isCineEnabled, cines }] = useCine(); + const [newStackFrameRate, setNewStackFrameRate] = useState(24); + const [appConfig] = useAppConfig(); const { component: CinePlayerComponent = CinePlayer } = customizationService.get('cinePlayer') ?? {}; @@ -17,12 +21,12 @@ function WrappedCinePlayer({ const handleCineClose = () => { toolbarService.recordInteraction({ groupId: 'MoreTools', - itemId: 'cine', interactionType: 'toggle', commands: [ { commandName: 'toggleCine', commandOptions: {}, + toolName: 'cine', context: 'CORNERSTONE', }, ], @@ -30,11 +34,11 @@ function WrappedCinePlayer({ }; const cineHandler = () => { - if (!cines || !cines[viewportIndex] || !enabledVPElement) { + if (!cines || !cines[viewportId] || !enabledVPElement) { return; } - const cine = cines[viewportIndex]; + const cine = cines[viewportId]; const isPlaying = cine.isPlaying || false; const frameRate = cine.frameRate || 24; @@ -49,53 +53,71 @@ function WrappedCinePlayer({ } }; + const newStackCineHandler = useCallback(() => { + const { viewports } = viewportGridService.getState(); + const { displaySetInstanceUIDs } = viewports.get(viewportId); + + let frameRate = 24; + let isPlaying = cines[viewportId].isPlaying; + displaySetInstanceUIDs.forEach(displaySetInstanceUID => { + const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID); + if (displaySet.FrameRate) { + // displaySet.FrameRate corresponds to DICOM tag (0018,1063) which is defined as the the frame time in milliseconds + // So a bit of math to get the actual frame rate. + frameRate = Math.round(1000 / displaySet.FrameRate); + isPlaying ||= !!appConfig.autoPlayCine; + } + }); + + if (isPlaying) { + cineService.setIsCineEnabled(isPlaying); + } + cineService.setCine({ id: viewportId, isPlaying, frameRate }); + setNewStackFrameRate(frameRate); + }, [cineService, displaySetService, viewportId, viewportGridService, cines]); + useEffect(() => { - eventTarget.addEventListener( - Enums.Events.STACK_VIEWPORT_NEW_STACK, - cineHandler - ); + eventTarget.addEventListener(Enums.Events.STACK_VIEWPORT_NEW_STACK, newStackCineHandler); return () => { - cineService.setCine({ id: viewportIndex, isPlaying: false }); - eventTarget.removeEventListener( - Enums.Events.STACK_VIEWPORT_NEW_STACK, - cineHandler - ); + cineService.setCine({ id: viewportId, isPlaying: false }); + eventTarget.removeEventListener(Enums.Events.STACK_VIEWPORT_NEW_STACK, newStackCineHandler); }; - }, [enabledVPElement]); + }, [enabledVPElement, newStackCineHandler]); useEffect(() => { - if (!cines || !cines[viewportIndex] || !enabledVPElement) { + if (!cines || !cines[viewportId] || !enabledVPElement) { return; } cineHandler(); return () => { - if (enabledVPElement && cines?.[viewportIndex]?.isPlaying) { + if (enabledVPElement && cines?.[viewportId]?.isPlaying) { cineService.stopClip(enabledVPElement); } }; - }, [cines, viewportIndex, cineService, enabledVPElement, cineHandler]); + }, [cines, viewportId, cineService, enabledVPElement, cineHandler]); - const cine = cines[viewportIndex]; + const cine = cines[viewportId]; const isPlaying = (cine && cine.isPlaying) || false; return ( isCineEnabled && ( cineService.setCine({ - id: activeViewportIndex, + id: viewportId, isPlaying, }) } onFrameRateChange={frameRate => cineService.setCine({ - id: activeViewportIndex, + id: viewportId, frameRate, }) } diff --git a/extensions/cornerstone/src/components/DicomUpload/DicomUpload.css b/extensions/cornerstone/src/components/DicomUpload/DicomUpload.css index 55ddf7922e9..d37d2a6a649 100644 --- a/extensions/cornerstone/src/components/DicomUpload/DicomUpload.css +++ b/extensions/cornerstone/src/components/DicomUpload/DicomUpload.css @@ -1,6 +1,23 @@ .dicom-upload-drop-area-border-dash { - background-image: repeating-linear-gradient(to right, #7BB2CE 0%, #7BB2CE 50%, transparent 50%, transparent 100%), repeating-linear-gradient(to right, #7BB2CE 0%, #7BB2CE 50%, transparent 50%, transparent 100%), repeating-linear-gradient(to bottom, #7BB2CE 0%, #7BB2CE 50%, transparent 50%, transparent 100%), repeating-linear-gradient(to bottom, #7BB2CE 0%, #7BB2CE 50%, transparent 50%, transparent 100%); - background-position: left top, left bottom, left top, right top; + background-image: repeating-linear-gradient( + to right, + #7bb2ce 0%, + #7bb2ce 50%, + transparent 50%, + transparent 100% + ), + repeating-linear-gradient(to right, #7bb2ce 0%, #7bb2ce 50%, transparent 50%, transparent 100%), + repeating-linear-gradient(to bottom, #7bb2ce 0%, #7bb2ce 50%, transparent 50%, transparent 100%), + repeating-linear-gradient(to bottom, #7bb2ce 0%, #7bb2ce 50%, transparent 50%, transparent 100%); + background-position: + left top, + left bottom, + left top, + right top; background-repeat: repeat-x, repeat-x, repeat-y, repeat-y; - background-size: 20px 3px, 20px 3px, 3px 20px, 3px 20px; + background-size: + 20px 3px, + 20px 3px, + 3px 20px, + 3px 20px; } diff --git a/extensions/cornerstone/src/components/DicomUpload/DicomUpload.tsx b/extensions/cornerstone/src/components/DicomUpload/DicomUpload.tsx index e90728cb368..8284ee07055 100644 --- a/extensions/cornerstone/src/components/DicomUpload/DicomUpload.tsx +++ b/extensions/cornerstone/src/components/DicomUpload/DicomUpload.tsx @@ -14,19 +14,13 @@ type DicomUploadProps = { onStarted: () => void; }; -function DicomUpload({ - dataSource, - onComplete, - onStarted, -}: DicomUploadProps): ReactElement { +function DicomUpload({ dataSource, onComplete, onStarted }: DicomUploadProps): ReactElement { const baseClassNames = 'min-h-[480px] flex flex-col bg-black select-none'; const [dicomFileUploaderArr, setDicomFileUploaderArr] = useState([]); const onDrop = useCallback(async acceptedFiles => { onStarted(); - setDicomFileUploaderArr( - acceptedFiles.map(file => new DicomFileUploader(file, dataSource)) - ); + setDicomFileUploaderArr(acceptedFiles.map(file => new DicomFileUploader(file, dataSource))); }, []); const getDropZoneComponent = (): ReactElement => { @@ -40,20 +34,29 @@ function DicomUpload({ {({ getRootProps }) => (
- + {({ getRootProps, getInputProps }) => (
-
)}
- + {({ getRootProps, getInputProps }) => (
or drag images or folders here
-
- (DICOM files supported) -
+
(DICOM files supported)
)} @@ -92,9 +93,7 @@ function DicomUpload({ />
) : ( -
- {getDropZoneComponent()} -
+
{getDropZoneComponent()}
)} ); diff --git a/extensions/cornerstone/src/components/DicomUpload/DicomUploadProgress.tsx b/extensions/cornerstone/src/components/DicomUpload/DicomUploadProgress.tsx index 6fd14823abf..c09d9698810 100644 --- a/extensions/cornerstone/src/components/DicomUpload/DicomUploadProgress.tsx +++ b/extensions/cornerstone/src/components/DicomUpload/DicomUploadProgress.tsx @@ -1,10 +1,4 @@ -import React, { - useCallback, - useEffect, - useRef, - useState, - ReactElement, -} from 'react'; +import React, { useCallback, useEffect, useRef, useState, ReactElement } from 'react'; import PropTypes from 'prop-types'; import { Button, Icon, ProgressLoadingBar } from '@ohif/ui'; import DicomFileUploader, { @@ -39,18 +33,14 @@ const BASE_INTERVAL_TIME = 15000; // calculate the upload rate. const UPLOAD_RATE_THRESHOLD = 75; -const NO_WRAP_ELLIPSIS_CLASS_NAMES = - 'text-ellipsis whitespace-nowrap overflow-hidden'; +const NO_WRAP_ELLIPSIS_CLASS_NAMES = 'text-ellipsis whitespace-nowrap overflow-hidden'; function DicomUploadProgress({ dicomFileUploaderArr, onComplete, }: DicomUploadProgressProps): ReactElement { const [totalUploadSize] = useState( - dicomFileUploaderArr.reduce( - (acc, fileUploader) => acc + fileUploader.getFileSize(), - 0 - ) + dicomFileUploaderArr.reduce((acc, fileUploader) => acc + fileUploader.getFileSize(), 0) ); const currentUploadSizeRef = useRef(0); @@ -83,15 +73,13 @@ function DicomUploadProgress({ let intervalStartTime = Date.now(); const setUploadRateRef = () => { - const uploadSizeFromStartOfInterval = - currentUploadSizeRef.current - intervalStartUploadSize; + const uploadSizeFromStartOfInterval = currentUploadSizeRef.current - intervalStartUploadSize; const now = Date.now(); const timeSinceStartOfInterval = now - intervalStartTime; // Calculate and set the upload rate (ref) - uploadRateRef.current = - uploadSizeFromStartOfInterval / timeSinceStartOfInterval; + uploadRateRef.current = uploadSizeFromStartOfInterval / timeSinceStartOfInterval; // Reset the interval starting values. intervalStartUploadSize = currentUploadSizeRef.current; @@ -134,28 +122,19 @@ function DicomUploadProgress({ const updateProgress = (percentComplete: number) => { const previousFileUploadSize = currentFileUploadSize; - currentFileUploadSize = Math.round( - (percentComplete / 100) * fileUploader.getFileSize() - ); + currentFileUploadSize = Math.round((percentComplete / 100) * fileUploader.getFileSize()); currentUploadSizeRef.current = Math.min( totalUploadSize, - currentUploadSizeRef.current - - previousFileUploadSize + - currentFileUploadSize + currentUploadSizeRef.current - previousFileUploadSize + currentFileUploadSize ); - setPercentComplete( - (currentUploadSizeRef.current / totalUploadSize) * 100 - ); + setPercentComplete((currentUploadSizeRef.current / totalUploadSize) * 100); if (uploadRateRef.current !== 0) { - const uploadSizeRemaining = - totalUploadSize - currentUploadSizeRef.current; + const uploadSizeRemaining = totalUploadSize - currentUploadSizeRef.current; - const timeRemaining = Math.round( - uploadSizeRemaining / uploadRateRef.current - ); + const timeRemaining = Math.round(uploadSizeRemaining / uploadRateRef.current); if (currentTimeRemaining === null) { currentTimeRemaining = timeRemaining; @@ -168,9 +147,7 @@ function DicomUploadProgress({ // due to rounding, inaccuracies in the estimate and slight variations // in upload rates over time. if (timeRemaining < ONE_MINUTE) { - const currentSecondsRemaining = Math.ceil( - currentTimeRemaining / ONE_SECOND - ); + const currentSecondsRemaining = Math.ceil(currentTimeRemaining / ONE_SECOND); const secondsRemaining = Math.ceil(timeRemaining / ONE_SECOND); const delta = secondsRemaining - currentSecondsRemaining; if (delta < 0 || delta > 2) { @@ -181,9 +158,7 @@ function DicomUploadProgress({ } if (timeRemaining < ONE_HOUR) { - const currentMinutesRemaining = Math.ceil( - currentTimeRemaining / ONE_MINUTE - ); + const currentMinutesRemaining = Math.ceil(currentTimeRemaining / ONE_MINUTE); const minutesRemaining = Math.ceil(timeRemaining / ONE_MINUTE); const delta = minutesRemaining - currentMinutesRemaining; if (delta < 0 || delta > 2) { @@ -199,9 +174,7 @@ function DicomUploadProgress({ } }; - const progressCallback = ( - progressEvent: DicomFileUploaderProgressEvent - ) => { + const progressCallback = (progressEvent: DicomFileUploaderProgressEvent) => { updateProgress(progressEvent.percentComplete); }; @@ -249,16 +222,12 @@ function DicomUploadProgress({ if (timeRemaining < ONE_MINUTE) { const secondsRemaining = Math.ceil(timeRemaining / ONE_SECOND); - return `${secondsRemaining} ${ - secondsRemaining === 1 ? 'second' : 'seconds' - }`; + return `${secondsRemaining} ${secondsRemaining === 1 ? 'second' : 'seconds'}`; } if (timeRemaining < ONE_HOUR) { const minutesRemaining = Math.ceil(timeRemaining / ONE_MINUTE); - return `${minutesRemaining} ${ - minutesRemaining === 1 ? 'minute' : 'minutes' - }`; + return `${minutesRemaining} ${minutesRemaining === 1 ? 'minute' : 'minutes'}`; } const hoursRemaining = Math.ceil(timeRemaining / ONE_HOUR); @@ -278,9 +247,7 @@ function DicomUploadProgress({ const showInfiniteProgressBar = useCallback((): boolean => { return ( getPercentCompleteRounded() < 1 && - (progressBarContainerRef?.current?.offsetWidth ?? 0) * - (percentComplete / 100) < - 1 + (progressBarContainerRef?.current?.offsetWidth ?? 0) * (percentComplete / 100) < 1 ); }, [getPercentCompleteRounded, percentComplete]); @@ -300,15 +267,17 @@ function DicomUploadProgress({ const getNumCompletedAndTimeRemainingComponent = (): ReactElement => { return ( -
+
{numFilesCompleted === dicomFileUploaderArr.length ? ( <> - {`${ - dicomFileUploaderArr.length - } ${ + {`${dicomFileUploaderArr.length} ${ dicomFileUploaderArr.length > 1 ? 'files' : 'file' } completed.`} - @@ -320,18 +289,14 @@ function DicomUploadProgress({ > {`${numFilesCompleted} of ${dicomFileUploaderArr.length}`}  + {' files completed.'}  - {' files completed.'}  - - - {timeRemaining - ? `Less than ${getFormattedTimeRemaining()} remaining. ` - : ''} + {timeRemaining ? `Less than ${getFormattedTimeRemaining()} remaining. ` : ''} @@ -345,14 +310,13 @@ function DicomUploadProgress({ const getShowFailedOnlyIconComponent = (): ReactElement => { return ( -
+
{numFails > 0 && ( -
- setShowFailedOnly(currentShowFailedOnly => !currentShowFailedOnly) - } - > - +
setShowFailedOnly(currentShowFailedOnly => !currentShowFailedOnly)}> +
)}
@@ -361,31 +325,28 @@ function DicomUploadProgress({ const getPercentCompleteComponent = (): ReactElement => { return ( -
-
+
+
{numFilesCompleted === dicomFileUploaderArr.length ? ( <> -
+
{numFails > 0 - ? `Completed with ${numFails} ${ - numFails > 1 ? 'errors' : 'error' - }!` + ? `Completed with ${numFails} ${numFails > 1 ? 'errors' : 'error'}!` : 'Completed!'}
{getShowFailedOnlyIconComponent()} ) : ( <> -
+
-
+
{`${getPercentCompleteRounded()}%`}
{getShowFailedOnlyIconComponent()}
@@ -397,16 +358,15 @@ function DicomUploadProgress({ }; return ( -
+
{getNumCompletedAndTimeRemainingComponent()} -
+
{getPercentCompleteComponent()} -
+
{dicomFileUploaderArr .filter( dicomFileUploader => - !showFailedOnly || - dicomFileUploader.getStatus() === UploadStatus.Failed + !showFailedOnly || dicomFileUploader.getStatus() === UploadStatus.Failed ) .map(dicomFileUploader => ( { - const [percentComplete, setPercentComplete] = useState( - dicomFileUploader.getPercentComplete() - ); + const [percentComplete, setPercentComplete] = useState(dicomFileUploader.getPercentComplete()); const [failedReason, setFailedReason] = useState(''); const [status, setStatus] = useState(dicomFileUploader.getStatus()); @@ -62,7 +54,10 @@ const DicomUploadProgressItem = memo( switch (dicomFileUploader.getStatus()) { case UploadStatus.Success: return ( - + ); case UploadStatus.InProgress: return ; @@ -76,27 +71,25 @@ const DicomUploadProgressItem = memo( }; return ( -
-
+
+
-
- {getStatusIcon()} -
-
+
{getStatusIcon()}
+
{dicomFileUploader.getFileName()}
{failedReason &&
{failedReason}
}
-
+
{!isComplete() && ( <> {dicomFileUploader.getStatus() === UploadStatus.InProgress && (
{percentComplete}%
)} -
+
diff --git a/extensions/cornerstone/src/getHangingProtocolModule.ts b/extensions/cornerstone/src/getHangingProtocolModule.ts index 15ff51460fe..4f2b3ececf1 100644 --- a/extensions/cornerstone/src/getHangingProtocolModule.ts +++ b/extensions/cornerstone/src/getHangingProtocolModule.ts @@ -1,305 +1,10 @@ -import { Types } from '@ohif/core'; - -const mpr: Types.HangingProtocol.Protocol = { - id: 'mpr', - name: 'Multi-Planar Reconstruction', - locked: true, - hasUpdatedPriorsInformation: false, - createdDate: '2021-02-23', - modifiedDate: '2023-04-03', - availableTo: {}, - editableBy: {}, - // Unknown number of priors referenced - so just match any study - numberOfPriorsReferenced: 0, - protocolMatchingRules: [], - imageLoadStrategy: 'nth', - callbacks: { - // Switches out of MPR mode when the layout change button is used - onLayoutChange: [ - { - commandName: 'toggleHangingProtocol', - commandOptions: { protocolId: 'mpr' }, - context: 'DEFAULT', - }, - ], - // Turns off crosshairs when switching out of MPR mode - onProtocolExit: [ - { - commandName: 'toolbarServiceRecordInteraction', - commandOptions: { - interactionType: 'tool', - commands: [ - { - commandOptions: { - toolName: 'WindowLevel', - }, - context: 'CORNERSTONE', - }, - ], - }, - }, - ], - }, - displaySetSelectors: { - activeDisplaySet: { - seriesMatchingRules: [ - { - weight: 1, - attribute: 'isReconstructable', - constraint: { - equals: { - value: true, - }, - }, - required: true, - }, - ], - }, - }, - stages: [ - { - name: 'MPR 1x3', - viewportStructure: { - layoutType: 'grid', - properties: { - rows: 1, - columns: 3, - layoutOptions: [ - { - x: 0, - y: 0, - width: 1 / 3, - height: 1, - }, - { - x: 1 / 3, - y: 0, - width: 1 / 3, - height: 1, - }, - { - x: 2 / 3, - y: 0, - width: 1 / 3, - height: 1, - }, - ], - }, - }, - viewports: [ - { - viewportOptions: { - toolGroupId: 'mpr', - viewportType: 'volume', - orientation: 'axial', - initialImageOptions: { - preset: 'middle', - }, - syncGroups: [ - { - type: 'voi', - id: 'mpr', - source: true, - target: true, - }, - ], - }, - displaySets: [ - { - id: 'activeDisplaySet', - }, - ], - }, - { - viewportOptions: { - toolGroupId: 'mpr', - viewportType: 'volume', - orientation: 'sagittal', - initialImageOptions: { - preset: 'middle', - }, - syncGroups: [ - { - type: 'voi', - id: 'mpr', - source: true, - target: true, - }, - ], - }, - displaySets: [ - { - id: 'activeDisplaySet', - }, - ], - }, - { - viewportOptions: { - toolGroupId: 'mpr', - viewportType: 'volume', - orientation: 'coronal', - initialImageOptions: { - preset: 'middle', - }, - syncGroups: [ - { - type: 'voi', - id: 'mpr', - source: true, - target: true, - }, - ], - }, - displaySets: [ - { - id: 'activeDisplaySet', - }, - ], - }, - ], - }, - ], -}; - -const mprAnd3DVolumeViewport = { - id: 'mprAnd3DVolumeViewport', - locked: true, - hasUpdatedPriorsInformation: false, - name: 'mpr', - createdDate: '2023-03-15T10:29:44.894Z', - modifiedDate: '2023-03-15T10:29:44.894Z', - availableTo: {}, - editableBy: {}, - protocolMatchingRules: [], - imageLoadStrategy: 'interleaveCenter', - displaySetSelectors: { - mprDisplaySet: { - seriesMatchingRules: [ - { - weight: 1, - attribute: 'isReconstructable', - constraint: { - equals: { - value: true, - }, - }, - required: true, - }, - { - attribute: 'Modality', - constraint: { - equals: { - value: 'CT', - }, - }, - required: true, - }, - ], - }, - }, - stages: [ - { - id: 'mpr3Stage', - name: 'mpr', - viewportStructure: { - layoutType: 'grid', - properties: { - rows: 2, - columns: 2, - }, - }, - viewports: [ - { - viewportOptions: { - toolGroupId: 'mpr', - viewportType: 'volume', - orientation: 'axial', - initialImageOptions: { - preset: 'middle', - }, - syncGroups: [ - { - type: 'voi', - id: 'mpr', - source: true, - target: true, - }, - ], - }, - displaySets: [ - { - id: 'mprDisplaySet', - }, - ], - }, - { - viewportOptions: { - toolGroupId: 'volume3d', - viewportType: 'volume3d', - orientation: 'coronal', - customViewportProps: { - hideOverlays: true, - }, - }, - displaySets: [ - { - id: 'mprDisplaySet', - options: { - displayPreset: 'CT-Bone', - }, - }, - ], - }, - { - viewportOptions: { - toolGroupId: 'mpr', - viewportType: 'volume', - orientation: 'coronal', - initialImageOptions: { - preset: 'middle', - }, - syncGroups: [ - { - type: 'voi', - id: 'mpr', - source: true, - target: true, - }, - ], - }, - displaySets: [ - { - id: 'mprDisplaySet', - }, - ], - }, - { - viewportOptions: { - toolGroupId: 'mpr', - viewportType: 'volume', - orientation: 'sagittal', - initialImageOptions: { - preset: 'middle', - }, - syncGroups: [ - { - type: 'voi', - id: 'mpr', - source: true, - target: true, - }, - ], - }, - displaySets: [ - { - id: 'mprDisplaySet', - }, - ], - }, - ], - }, - ], -}; +import { fourUp } from './hps/fourUp'; +import { main3D } from './hps/main3D'; +import { mpr } from './hps/mpr'; +import { mprAnd3DVolumeViewport } from './hps/mprAnd3DVolumeViewport'; +import { only3D } from './hps/only3D'; +import { primary3D } from './hps/primary3D'; +import { primaryAxial } from './hps/primaryAxial'; function getHangingProtocolModule() { return [ @@ -311,6 +16,26 @@ function getHangingProtocolModule() { name: mprAnd3DVolumeViewport.id, protocol: mprAnd3DVolumeViewport, }, + { + name: fourUp.id, + protocol: fourUp, + }, + { + name: main3D.id, + protocol: main3D, + }, + { + name: primaryAxial.id, + protocol: primaryAxial, + }, + { + name: only3D.id, + protocol: only3D, + }, + { + name: primary3D.id, + protocol: primary3D, + }, ]; } diff --git a/extensions/cornerstone/src/hps/fourUp.ts b/extensions/cornerstone/src/hps/fourUp.ts new file mode 100644 index 00000000000..776d7a29832 --- /dev/null +++ b/extensions/cornerstone/src/hps/fourUp.ts @@ -0,0 +1,130 @@ +export const fourUp = { + id: 'fourUp', + locked: true, + name: 'fourUp', + createdDate: '2023-03-15T10:29:44.894Z', + modifiedDate: '2023-03-15T10:29:44.894Z', + availableTo: {}, + editableBy: {}, + protocolMatchingRules: [], + imageLoadStrategy: 'interleaveCenter', + displaySetSelectors: { + mprDisplaySet: { + seriesMatchingRules: [ + { + weight: 1, + attribute: 'isReconstructable', + constraint: { + equals: { + value: true, + }, + }, + required: true, + }, + ], + }, + }, + stages: [ + { + id: 'fourUpStage', + name: 'fourUp', + viewportStructure: { + layoutType: 'grid', + properties: { + rows: 2, + columns: 2, + }, + }, + viewports: [ + { + viewportOptions: { + toolGroupId: 'mpr', + viewportType: 'volume', + orientation: 'axial', + initialImageOptions: { + preset: 'middle', + }, + syncGroups: [ + { + type: 'voi', + id: 'mpr', + source: true, + target: true, + }, + ], + }, + displaySets: [ + { + id: 'mprDisplaySet', + }, + ], + }, + { + viewportOptions: { + toolGroupId: 'volume3d', + viewportType: 'volume3d', + orientation: 'coronal', + customViewportProps: { + hideOverlays: true, + }, + }, + displaySets: [ + { + id: 'mprDisplaySet', + options: { + // ToDo: choose appropriate preset + displayPreset: 'CT-Bone', + }, + }, + ], + }, + { + viewportOptions: { + toolGroupId: 'mpr', + viewportType: 'volume', + orientation: 'coronal', + initialImageOptions: { + preset: 'middle', + }, + syncGroups: [ + { + type: 'voi', + id: 'mpr', + source: true, + target: true, + }, + ], + }, + displaySets: [ + { + id: 'mprDisplaySet', + }, + ], + }, + { + viewportOptions: { + toolGroupId: 'mpr', + viewportType: 'volume', + orientation: 'sagittal', + initialImageOptions: { + preset: 'middle', + }, + syncGroups: [ + { + type: 'voi', + id: 'mpr', + source: true, + target: true, + }, + ], + }, + displaySets: [ + { + id: 'mprDisplaySet', + }, + ], + }, + ], + }, + ], +}; diff --git a/extensions/cornerstone/src/hps/main3D.ts b/extensions/cornerstone/src/hps/main3D.ts new file mode 100644 index 00000000000..a7b837991b7 --- /dev/null +++ b/extensions/cornerstone/src/hps/main3D.ts @@ -0,0 +1,156 @@ +export const main3D = { + id: 'main3D', + locked: true, + name: 'main3D', + createdDate: '2023-03-15T10:29:44.894Z', + modifiedDate: '2023-03-15T10:29:44.894Z', + availableTo: {}, + editableBy: {}, + protocolMatchingRules: [], + imageLoadStrategy: 'interleaveCenter', + displaySetSelectors: { + mprDisplaySet: { + seriesMatchingRules: [ + { + weight: 1, + attribute: 'isReconstructable', + constraint: { + equals: { + value: true, + }, + }, + required: true, + }, + ], + }, + }, + stages: [ + { + id: 'main3DStage', + name: 'main3D', + viewportStructure: { + layoutType: 'grid', + properties: { + rows: 2, + columns: 3, + layoutOptions: [ + { + x: 0, + y: 0, + width: 1, + height: 1 / 2, + }, + { + x: 0, + y: 1 / 2, + width: 1 / 3, + height: 1 / 2, + }, + { + x: 1 / 3, + y: 1 / 2, + width: 1 / 3, + height: 1 / 2, + }, + { + x: 2 / 3, + y: 1 / 2, + width: 1 / 3, + height: 1 / 2, + }, + ], + }, + }, + viewports: [ + { + viewportOptions: { + toolGroupId: 'volume3d', + viewportType: 'volume3d', + orientation: 'coronal', + customViewportProps: { + hideOverlays: true, + }, + }, + displaySets: [ + { + id: 'mprDisplaySet', + options: { + // ToDo: choose appropriate preset + displayPreset: 'CT-Bone', + }, + }, + ], + }, + { + viewportOptions: { + toolGroupId: 'mpr', + viewportType: 'volume', + orientation: 'axial', + initialImageOptions: { + preset: 'middle', + }, + syncGroups: [ + { + type: 'voi', + id: 'mpr', + source: true, + target: true, + }, + ], + }, + displaySets: [ + { + id: 'mprDisplaySet', + }, + ], + }, + { + viewportOptions: { + toolGroupId: 'mpr', + viewportType: 'volume', + orientation: 'coronal', + initialImageOptions: { + preset: 'middle', + }, + syncGroups: [ + { + type: 'voi', + id: 'mpr', + source: true, + target: true, + }, + ], + }, + displaySets: [ + { + id: 'mprDisplaySet', + }, + ], + }, + { + viewportOptions: { + toolGroupId: 'mpr', + viewportType: 'volume', + orientation: 'sagittal', + initialImageOptions: { + preset: 'middle', + }, + syncGroups: [ + { + type: 'voi', + id: 'mpr', + source: true, + target: true, + }, + ], + }, + displaySets: [ + { + id: 'mprDisplaySet', + }, + ], + }, + ], + }, + ], +}; diff --git a/extensions/cornerstone/src/hps/mpr.ts b/extensions/cornerstone/src/hps/mpr.ts new file mode 100644 index 00000000000..4c625c73172 --- /dev/null +++ b/extensions/cornerstone/src/hps/mpr.ts @@ -0,0 +1,153 @@ +import { Types } from '@ohif/core'; + +export const mpr: Types.HangingProtocol.Protocol = { + id: 'mpr', + name: 'Multi-Planar Reconstruction', + locked: true, + createdDate: '2021-02-23', + modifiedDate: '2023-08-15', + availableTo: {}, + editableBy: {}, + // Unknown number of priors referenced - so just match any study + numberOfPriorsReferenced: 0, + protocolMatchingRules: [], + imageLoadStrategy: 'nth', + callbacks: { + // Switches out of MPR mode when the layout change button is used + onLayoutChange: [ + { + commandName: 'toggleHangingProtocol', + commandOptions: { protocolId: 'mpr' }, + context: 'DEFAULT', + }, + ], + // Turns off crosshairs when switching out of MPR mode + onProtocolExit: [ + { + commandName: 'cleanUpCrosshairs', + }, + ], + }, + displaySetSelectors: { + activeDisplaySet: { + seriesMatchingRules: [ + { + weight: 1, + attribute: 'isReconstructable', + constraint: { + equals: { + value: true, + }, + }, + required: true, + }, + ], + }, + }, + stages: [ + { + name: 'MPR 1x3', + viewportStructure: { + layoutType: 'grid', + properties: { + rows: 1, + columns: 3, + layoutOptions: [ + { + x: 0, + y: 0, + width: 1 / 3, + height: 1, + }, + { + x: 1 / 3, + y: 0, + width: 1 / 3, + height: 1, + }, + { + x: 2 / 3, + y: 0, + width: 1 / 3, + height: 1, + }, + ], + }, + }, + viewports: [ + { + viewportOptions: { + viewportId: 'mpr-axial', + toolGroupId: 'mpr', + viewportType: 'volume', + orientation: 'axial', + initialImageOptions: { + preset: 'middle', + }, + syncGroups: [ + { + type: 'voi', + id: 'mpr', + source: true, + target: true, + }, + ], + }, + displaySets: [ + { + id: 'activeDisplaySet', + }, + ], + }, + { + viewportOptions: { + viewportId: 'mpr-sagittal', + toolGroupId: 'mpr', + viewportType: 'volume', + orientation: 'sagittal', + initialImageOptions: { + preset: 'middle', + }, + syncGroups: [ + { + type: 'voi', + id: 'mpr', + source: true, + target: true, + }, + ], + }, + displaySets: [ + { + id: 'activeDisplaySet', + }, + ], + }, + { + viewportOptions: { + viewportId: 'mpr-coronal', + toolGroupId: 'mpr', + viewportType: 'volume', + orientation: 'coronal', + initialImageOptions: { + preset: 'middle', + }, + syncGroups: [ + { + type: 'voi', + id: 'mpr', + source: true, + target: true, + }, + ], + }, + displaySets: [ + { + id: 'activeDisplaySet', + }, + ], + }, + ], + }, + ], +}; diff --git a/extensions/cornerstone/src/hps/mprAnd3DVolumeViewport.ts b/extensions/cornerstone/src/hps/mprAnd3DVolumeViewport.ts new file mode 100644 index 00000000000..120f6fb7ef7 --- /dev/null +++ b/extensions/cornerstone/src/hps/mprAnd3DVolumeViewport.ts @@ -0,0 +1,138 @@ +export const mprAnd3DVolumeViewport = { + id: 'mprAnd3DVolumeViewport', + locked: true, + name: 'mpr', + createdDate: '2023-03-15T10:29:44.894Z', + modifiedDate: '2023-03-15T10:29:44.894Z', + availableTo: {}, + editableBy: {}, + protocolMatchingRules: [], + imageLoadStrategy: 'interleaveCenter', + displaySetSelectors: { + mprDisplaySet: { + seriesMatchingRules: [ + { + weight: 1, + attribute: 'isReconstructable', + constraint: { + equals: { + value: true, + }, + }, + required: true, + }, + { + attribute: 'Modality', + constraint: { + equals: { + value: 'CT', + }, + }, + required: true, + }, + ], + }, + }, + stages: [ + { + id: 'mpr3Stage', + name: 'mpr', + viewportStructure: { + layoutType: 'grid', + properties: { + rows: 2, + columns: 2, + }, + }, + viewports: [ + { + viewportOptions: { + toolGroupId: 'mpr', + viewportType: 'volume', + orientation: 'axial', + initialImageOptions: { + preset: 'middle', + }, + syncGroups: [ + { + type: 'voi', + id: 'mpr', + source: true, + target: true, + }, + ], + }, + displaySets: [ + { + id: 'mprDisplaySet', + }, + ], + }, + { + viewportOptions: { + toolGroupId: 'volume3d', + viewportType: 'volume3d', + orientation: 'coronal', + customViewportProps: { + hideOverlays: true, + }, + }, + displaySets: [ + { + id: 'mprDisplaySet', + options: { + displayPreset: 'CT-Bone', + }, + }, + ], + }, + { + viewportOptions: { + toolGroupId: 'mpr', + viewportType: 'volume', + orientation: 'coronal', + initialImageOptions: { + preset: 'middle', + }, + syncGroups: [ + { + type: 'voi', + id: 'mpr', + source: true, + target: true, + }, + ], + }, + displaySets: [ + { + id: 'mprDisplaySet', + }, + ], + }, + { + viewportOptions: { + toolGroupId: 'mpr', + viewportType: 'volume', + orientation: 'sagittal', + initialImageOptions: { + preset: 'middle', + }, + syncGroups: [ + { + type: 'voi', + id: 'mpr', + source: true, + target: true, + }, + ], + }, + displaySets: [ + { + id: 'mprDisplaySet', + }, + ], + }, + ], + }, + ], +}; diff --git a/extensions/cornerstone/src/hps/only3D.ts b/extensions/cornerstone/src/hps/only3D.ts new file mode 100644 index 00000000000..d319d1ee4fe --- /dev/null +++ b/extensions/cornerstone/src/hps/only3D.ts @@ -0,0 +1,61 @@ +export const only3D = { + id: 'only3D', + locked: true, + name: 'only3D', + createdDate: '2023-03-15T10:29:44.894Z', + modifiedDate: '2023-03-15T10:29:44.894Z', + availableTo: {}, + editableBy: {}, + protocolMatchingRules: [], + imageLoadStrategy: 'interleaveCenter', + displaySetSelectors: { + mprDisplaySet: { + seriesMatchingRules: [ + { + weight: 1, + attribute: 'isReconstructable', + constraint: { + equals: { + value: true, + }, + }, + required: true, + }, + ], + }, + }, + stages: [ + { + id: 'only3DStage', + name: 'only3D', + viewportStructure: { + layoutType: 'grid', + properties: { + rows: 1, + columns: 1, + }, + }, + viewports: [ + { + viewportOptions: { + toolGroupId: 'volume3d', + viewportType: 'volume3d', + orientation: 'coronal', + customViewportProps: { + hideOverlays: true, + }, + }, + displaySets: [ + { + id: 'mprDisplaySet', + options: { + // ToDo: choose appropriate preset + displayPreset: 'CT-Bone', + }, + }, + ], + }, + ], + }, + ], +}; diff --git a/extensions/cornerstone/src/hps/primary3D.ts b/extensions/cornerstone/src/hps/primary3D.ts new file mode 100644 index 00000000000..12655324885 --- /dev/null +++ b/extensions/cornerstone/src/hps/primary3D.ts @@ -0,0 +1,156 @@ +export const primary3D = { + id: 'primary3D', + locked: true, + name: 'primary3D', + createdDate: '2023-03-15T10:29:44.894Z', + modifiedDate: '2023-03-15T10:29:44.894Z', + availableTo: {}, + editableBy: {}, + protocolMatchingRules: [], + imageLoadStrategy: 'interleaveCenter', + displaySetSelectors: { + mprDisplaySet: { + seriesMatchingRules: [ + { + weight: 1, + attribute: 'isReconstructable', + constraint: { + equals: { + value: true, + }, + }, + required: true, + }, + ], + }, + }, + stages: [ + { + id: 'primary3DStage', + name: 'primary3D', + viewportStructure: { + layoutType: 'grid', + properties: { + rows: 3, + columns: 3, + layoutOptions: [ + { + x: 0, + y: 0, + width: 2 / 3, + height: 1, + }, + { + x: 2 / 3, + y: 0, + width: 1 / 3, + height: 1 / 3, + }, + { + x: 2 / 3, + y: 1 / 3, + width: 1 / 3, + height: 1 / 3, + }, + { + x: 2 / 3, + y: 2 / 3, + width: 1 / 3, + height: 1 / 3, + }, + ], + }, + }, + viewports: [ + { + viewportOptions: { + toolGroupId: 'volume3d', + viewportType: 'volume3d', + orientation: 'coronal', + customViewportProps: { + hideOverlays: true, + }, + }, + displaySets: [ + { + id: 'mprDisplaySet', + options: { + // ToDo: choose appropriate preset + displayPreset: 'CT-Bone', + }, + }, + ], + }, + { + viewportOptions: { + toolGroupId: 'mpr', + viewportType: 'volume', + orientation: 'axial', + initialImageOptions: { + preset: 'middle', + }, + syncGroups: [ + { + type: 'voi', + id: 'mpr', + source: true, + target: true, + }, + ], + }, + displaySets: [ + { + id: 'mprDisplaySet', + }, + ], + }, + { + viewportOptions: { + toolGroupId: 'mpr', + viewportType: 'volume', + orientation: 'coronal', + initialImageOptions: { + preset: 'middle', + }, + syncGroups: [ + { + type: 'voi', + id: 'mpr', + source: true, + target: true, + }, + ], + }, + displaySets: [ + { + id: 'mprDisplaySet', + }, + ], + }, + { + viewportOptions: { + toolGroupId: 'mpr', + viewportType: 'volume', + orientation: 'sagittal', + initialImageOptions: { + preset: 'middle', + }, + syncGroups: [ + { + type: 'voi', + id: 'mpr', + source: true, + target: true, + }, + ], + }, + displaySets: [ + { + id: 'mprDisplaySet', + }, + ], + }, + ], + }, + ], +}; diff --git a/extensions/cornerstone/src/hps/primaryAxial.ts b/extensions/cornerstone/src/hps/primaryAxial.ts new file mode 100644 index 00000000000..2126920b3f0 --- /dev/null +++ b/extensions/cornerstone/src/hps/primaryAxial.ts @@ -0,0 +1,131 @@ +export const primaryAxial = { + id: 'primaryAxial', + locked: true, + name: 'primaryAxial', + createdDate: '2023-03-15T10:29:44.894Z', + modifiedDate: '2023-03-15T10:29:44.894Z', + availableTo: {}, + editableBy: {}, + protocolMatchingRules: [], + imageLoadStrategy: 'interleaveCenter', + displaySetSelectors: { + mprDisplaySet: { + seriesMatchingRules: [ + { + weight: 1, + attribute: 'isReconstructable', + constraint: { + equals: { + value: true, + }, + }, + required: true, + }, + ], + }, + }, + stages: [ + { + id: 'primaryAxialStage', + name: 'primaryAxial', + viewportStructure: { + layoutType: 'grid', + properties: { + rows: 2, + columns: 3, + layoutOptions: [ + { + x: 0, + y: 0, + width: 2 / 3, + height: 1, + }, + { + x: 2 / 3, + y: 0, + width: 1 / 3, + height: 1 / 2, + }, + { + x: 2 / 3, + y: 1 / 2, + width: 1 / 3, + height: 1 / 2, + }, + ], + }, + }, + viewports: [ + { + viewportOptions: { + toolGroupId: 'mpr', + viewportType: 'volume', + orientation: 'axial', + initialImageOptions: { + preset: 'middle', + }, + syncGroups: [ + { + type: 'voi', + id: 'mpr', + source: true, + target: true, + }, + ], + }, + displaySets: [ + { + id: 'mprDisplaySet', + }, + ], + }, + { + viewportOptions: { + toolGroupId: 'mpr', + viewportType: 'volume', + orientation: 'sagittal', + initialImageOptions: { + preset: 'middle', + }, + syncGroups: [ + { + type: 'voi', + id: 'mpr', + source: true, + target: true, + }, + ], + }, + displaySets: [ + { + id: 'mprDisplaySet', + }, + ], + }, + { + viewportOptions: { + toolGroupId: 'mpr', + viewportType: 'volume', + orientation: 'coronal', + initialImageOptions: { + preset: 'middle', + }, + syncGroups: [ + { + type: 'voi', + id: 'mpr', + source: true, + target: true, + }, + ], + }, + displaySets: [ + { + id: 'mprDisplaySet', + }, + ], + }, + ], + }, + ], +}; diff --git a/extensions/cornerstone/src/index.tsx b/extensions/cornerstone/src/index.tsx index 23ced7ee028..ac53c8f0f27 100644 --- a/extensions/cornerstone/src/index.tsx +++ b/extensions/cornerstone/src/index.tsx @@ -29,11 +29,10 @@ import { id } from './id'; import * as csWADOImageLoader from './initWADOImageLoader.js'; import { measurementMappingUtils } from './utils/measurementServiceMappings'; import type { PublicViewportOptions } from './services/ViewportService/Viewport'; +import ImageOverlayViewerTool from './tools/ImageOverlayViewerTool'; const Component = React.lazy(() => { - return import( - /* webpackPrefetch: true */ './Viewport/OHIFCornerstoneViewport' - ); + return import(/* webpackPrefetch: true */ './Viewport/OHIFCornerstoneViewport'); }); const OHIFCornerstoneViewport = props => { @@ -61,7 +60,6 @@ const cornerstoneExtension: Types.Extensions.Extension = { imageRetrievalPoolManager.clearRequestStack(type); }); - csWADOImageLoader.destroy(); enabledElementReset(); }, @@ -70,9 +68,7 @@ const cornerstoneExtension: Types.Extensions.Extension = { * * @param configuration.csToolsConfig - Passed directly to `initCornerstoneTools` */ - preRegistration: function ( - props: Types.Extensions.ExtensionParams - ): Promise { + preRegistration: function (props: Types.Extensions.ExtensionParams): Promise { const { servicesManager } = props; servicesManager.registerService(CornerstoneViewportService.REGISTRATION); servicesManager.registerService(ToolGroupService.REGISTRATION); @@ -140,5 +136,11 @@ const cornerstoneExtension: Types.Extensions.Extension = { }; export type { PublicViewportOptions }; -export { measurementMappingUtils, CornerstoneExtensionTypes, toolNames , getActiveViewportEnabledElement}; +export { + measurementMappingUtils, + CornerstoneExtensionTypes as Types, + toolNames, + getActiveViewportEnabledElement, + ImageOverlayViewerTool, +}; export default cornerstoneExtension; diff --git a/extensions/cornerstone/src/init.tsx b/extensions/cornerstone/src/init.tsx index bda746947e2..8e1ede1266c 100644 --- a/extensions/cornerstone/src/init.tsx +++ b/extensions/cornerstone/src/init.tsx @@ -1,4 +1,4 @@ -import OHIF, { Types } from '@ohif/core'; +import OHIF, { Types, errorHandler } from '@ohif/core'; import React from 'react'; import * as cornerstone from '@cornerstonejs/core'; @@ -10,10 +10,12 @@ import { metaData, volumeLoader, imageLoadPoolManager, + getEnabledElement, Settings, utilities as csUtilities, + Enums as csEnums, } from '@cornerstonejs/core'; -import { Enums, utilities, ReferenceLinesTool } from '@cornerstonejs/tools'; +import { Enums } from '@cornerstonejs/tools'; import { cornerstoneStreamingImageVolumeLoader } from '@cornerstonejs/streaming-image-volume-loader'; import initWADOImageLoader from './initWADOImageLoader'; @@ -27,6 +29,7 @@ import interleaveTopToBottom from './utils/interleaveTopToBottom'; import initContextMenu from './initContextMenu'; import initDoubleClick from './initDoubleClick'; import { CornerstoneServices } from './types'; +import initViewTiming from './utils/initViewTiming'; // TODO: Cypress tests are currently grabbing this from the window? window.cornerstone = cornerstone; @@ -41,32 +44,47 @@ export default async function init({ configuration, appConfig, }: Types.Extensions.ExtensionParams): Promise { - await cs3DInit(); + // Note: this should run first before initializing the cornerstone + // DO NOT CHANGE THE ORDER + const value = appConfig.useSharedArrayBuffer; + let sharedArrayBufferDisabled = false; + + if (value === 'AUTO') { + cornerstone.setUseSharedArrayBuffer(csEnums.SharedArrayBufferModes.AUTO); + } else if (value === 'FALSE' || value === false) { + cornerstone.setUseSharedArrayBuffer(csEnums.SharedArrayBufferModes.FALSE); + sharedArrayBufferDisabled = true; + } else { + cornerstone.setUseSharedArrayBuffer(csEnums.SharedArrayBufferModes.TRUE); + } + + await cs3DInit({ + rendering: { + preferSizeOverAccuracy: Boolean(appConfig.preferSizeOverAccuracy), + useNorm16Texture: Boolean(appConfig.useNorm16Texture), + }, + }); // For debugging e2e tests that are failing on CI cornerstone.setUseCPURendering(Boolean(appConfig.useCPURendering)); + cornerstone.setConfiguration({ ...cornerstone.getConfiguration(), rendering: { ...cornerstone.getConfiguration().rendering, - strictZSpacingForVolumeViewport: - appConfig.strictZSpacingForVolumeViewport, + strictZSpacingForVolumeViewport: appConfig.strictZSpacingForVolumeViewport, }, }); - // For debugging large datasets - const MAX_CACHE_SIZE_1GB = 1073741824; - const maxCacheSize = appConfig.maxCacheSize; - cornerstone.cache.setMaxCacheSize( - maxCacheSize ? maxCacheSize : MAX_CACHE_SIZE_1GB - ); + // For debugging large datasets, otherwise prefer the defaults + const { maxCacheSize } = appConfig; + if (maxCacheSize) { + cornerstone.cache.setMaxCacheSize(maxCacheSize); + } initCornerstoneTools(); - Settings.getRuntimeSettings().set( - 'useCursors', - Boolean(appConfig.useCursors) - ); + Settings.getRuntimeSettings().set('useCursors', Boolean(appConfig.useCursors)); const { userAuthenticationService, @@ -77,8 +95,10 @@ export default async function init({ cornerstoneViewportService, hangingProtocolService, toolGroupService, + toolbarService, viewportGridService, stateSyncService, + syncGroupService, } = servicesManager.services as CornerstoneServices; window.services = servicesManager.services; @@ -87,20 +107,18 @@ export default async function init({ if ( appConfig.showWarningMessageForCrossOrigin && - !window.crossOriginIsolated + !window.crossOriginIsolated && + !sharedArrayBufferDisabled ) { uiNotificationService.show({ title: 'Cross Origin Isolation', message: - 'Cross Origin Isolation is not enabled, volume rendering will not work (e.g., MPR)', + 'Cross Origin Isolation is not enabled, read more about it here: https://docs.ohif.org/faq/', type: 'warning', }); } - if ( - appConfig.showCPUFallbackMessage && - cornerstone.getShouldUseCPURendering() - ) { + if (appConfig.showCPUFallbackMessage && cornerstone.getShouldUseCPURendering()) { _showCPURenderingModal(uiModalService, hangingProtocolService); } @@ -108,6 +126,9 @@ export default async function init({ // an OHIFCornerstoneViewport can be redisplayed with the same LUT stateSyncService.register('lutPresentationStore', { clearOnModeExit: true }); + // Stores synchronizers state to be restored + stateSyncService.register('synchronizersStore', { clearOnModeExit: true }); + // Stores a map from `positionPresentationId` to a Presentation object so that // an OHIFCornerstoneViewport can be redisplayed with the same position stateSyncService.register('positionPresentationStore', { @@ -120,18 +141,14 @@ export default async function init({ clearOnModeExit: true, }); - const labelmapRepresentation = - cornerstoneTools.Enums.SegmentationRepresentations.Labelmap; + const labelmapRepresentation = cornerstoneTools.Enums.SegmentationRepresentations.Labelmap; - cornerstoneTools.segmentation.config.setGlobalRepresentationConfig( - labelmapRepresentation, - { - fillAlpha: 0.3, - fillAlphaInactive: 0.2, - outlineOpacity: 1, - outlineOpacityInactive: 0.65, - } - ); + cornerstoneTools.segmentation.config.setGlobalRepresentationConfig(labelmapRepresentation, { + fillAlpha: 0.3, + fillAlphaInactive: 0.2, + outlineOpacity: 1, + outlineOpacityInactive: 0.65, + }); const metadataProvider = OHIF.classes.MetadataProvider; @@ -140,14 +157,8 @@ export default async function init({ cornerstoneStreamingImageVolumeLoader ); - hangingProtocolService.registerImageLoadStrategy( - 'interleaveCenter', - interleaveCenterLoader - ); - hangingProtocolService.registerImageLoadStrategy( - 'interleaveTopToBottom', - interleaveTopToBottom - ); + hangingProtocolService.registerImageLoadStrategy('interleaveCenter', interleaveCenterLoader); + hangingProtocolService.registerImageLoadStrategy('interleaveTopToBottom', interleaveTopToBottom); hangingProtocolService.registerImageLoadStrategy('nth', nthLoader); // add metadata providers @@ -167,9 +178,7 @@ export default async function init({ initWADOImageLoader(userAuthenticationService, appConfig, extensionManager); /* Measurement Service */ - this.measurementServiceSource = connectToolsToMeasurementService( - servicesManager - ); + this.measurementServiceSource = connectToolsToMeasurementService(servicesManager); initCineService(cineService); @@ -179,35 +188,32 @@ export default async function init({ volumeInputArrayMap => { for (const entry of volumeInputArrayMap.entries()) { const [viewportId, volumeInputArray] = entry; - const viewport = cornerstoneViewportService.getCornerstoneViewport( - viewportId - ); - - const ohifViewport = cornerstoneViewportService.getViewportInfo( - viewportId - ); - - const { - lutPresentationStore, - positionPresentationStore, - } = stateSyncService.getState(); + const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); + + const ohifViewport = cornerstoneViewportService.getViewportInfo(viewportId); + + const { lutPresentationStore, positionPresentationStore } = stateSyncService.getState(); const { presentationIds } = ohifViewport.getViewportOptions(); const presentations = { - positionPresentation: - positionPresentationStore[presentationIds?.positionPresentationId], - lutPresentation: - lutPresentationStore[presentationIds?.lutPresentationId], + positionPresentation: positionPresentationStore[presentationIds?.positionPresentationId], + lutPresentation: lutPresentationStore[presentationIds?.lutPresentationId], }; - cornerstoneViewportService.setVolumesForViewport( - viewport, - volumeInputArray, - presentations - ); + cornerstoneViewportService.setVolumesForViewport(viewport, volumeInputArray, presentations); } } ); + // resize the cornerstone viewport service when the grid size changes + // IMPORTANT: this should happen outside of the OHIFCornerstoneViewport + // since it will trigger a rerender of each viewport and each resizing + // the offscreen canvas which would result in a performance hit, this should + // done only once per grid resize here. Doing it once here, allows us to reduce + // the refreshRage(in ms) to 10 from 50. I tried with even 1 or 5 ms it worked fine + viewportGridService.subscribe(viewportGridService.EVENTS.GRID_SIZE_CHANGED, () => { + cornerstoneViewportService.resize(true); + }); + initContextMenu({ cornerstoneViewportService, customizationService, @@ -219,16 +225,59 @@ export default async function init({ commandsManager, }); - const newStackCallback = evt => { + /** + * When a viewport gets a new display set, this call will go through all the + * active tools in the toolbar, and call any commands registered in the + * toolbar service with a callback to re-enable on displaying the viewport. + */ + const toolbarEventListener = evt => { const { element } = evt.detail; - utilities.stackPrefetch.enable(element); + const activeTools = toolbarService.getActiveTools(); + + activeTools.forEach(tool => { + const toolData = toolbarService.getNestedButton(tool); + const commands = toolData?.listeners?.[evt.type]; + commandsManager.run(commands, { element, evt }); + }); + }; + + /** Listens for active viewport events and fires the toolbar listeners */ + const activeViewportEventListener = evt => { + const { viewportId } = evt; + const toolGroup = toolGroupService.getToolGroupForViewport(viewportId); + + const activeTools = toolbarService.getActiveTools(); + + activeTools.forEach(tool => { + if (!toolGroup?._toolInstances?.[tool]) { + return; + } + + // check if tool is active on the new viewport + const toolEnabled = toolGroup._toolInstances[tool].mode === Enums.ToolModes.Enabled; + + if (!toolEnabled) { + return; + } + + const button = toolbarService.getNestedButton(tool); + const commands = button?.listeners?.[evt.type]; + commandsManager.run(commands, { viewportId, evt }); + }); + }; + + /** + * Runs error handler for failed requests. + * @param event + */ + const imageLoadFailedHandler = ({ detail }) => { + const handler = errorHandler.getHTTPErrorHandler(); + handler(detail.error); }; const resetCrosshairs = evt => { const { element } = evt.detail; - const { viewportId, renderingEngineId } = cornerstone.getEnabledElement( - element - ); + const { viewportId, renderingEngineId } = cornerstone.getEnabledElement(element); const toolGroup = cornerstoneTools.ToolGroupManager.getToolGroupForViewport( viewportId, @@ -250,14 +299,21 @@ export default async function init({ } }; + eventTarget.addEventListener(EVENTS.STACK_VIEWPORT_NEW_STACK, evt => { + const { element } = evt.detail; + cornerstoneTools.utilities.stackContextPrefetch.enable(element); + }); + eventTarget.addEventListener(EVENTS.IMAGE_LOAD_FAILED, imageLoadFailedHandler); + eventTarget.addEventListener(EVENTS.IMAGE_LOAD_ERROR, imageLoadFailedHandler); + function elementEnabledHandler(evt) { const { element } = evt.detail; + element.addEventListener(EVENTS.CAMERA_RESET, resetCrosshairs); - eventTarget.addEventListener( - EVENTS.STACK_VIEWPORT_NEW_STACK, - newStackCallback - ); + eventTarget.addEventListener(EVENTS.STACK_VIEWPORT_NEW_STACK, toolbarEventListener); + + initViewTiming({ element, eventTarget }); } function elementDisabledHandler(evt) { @@ -272,47 +328,13 @@ export default async function init({ // ); } - eventTarget.addEventListener( - EVENTS.ELEMENT_ENABLED, - elementEnabledHandler.bind(null) - ); + eventTarget.addEventListener(EVENTS.ELEMENT_ENABLED, elementEnabledHandler.bind(null)); - eventTarget.addEventListener( - EVENTS.ELEMENT_DISABLED, - elementDisabledHandler.bind(null) - ); + eventTarget.addEventListener(EVENTS.ELEMENT_DISABLED, elementDisabledHandler.bind(null)); viewportGridService.subscribe( - viewportGridService.EVENTS.ACTIVE_VIEWPORT_INDEX_CHANGED, - ({ viewportIndex, viewportId }) => { - viewportId = viewportId || `viewport-${viewportIndex}`; - const toolGroup = toolGroupService.getToolGroupForViewport(viewportId); - - if (!toolGroup || !toolGroup._toolInstances?.['ReferenceLines']) { - return; - } - - // check if reference lines are active - const referenceLinesEnabled = - toolGroup._toolInstances['ReferenceLines'].mode === - Enums.ToolModes.Enabled; - - if (!referenceLinesEnabled) { - return; - } - - toolGroup.setToolConfiguration( - ReferenceLinesTool.toolName, - { - sourceViewportId: viewportId, - }, - true // overwrite - ); - - // make sure to set it to enabled again since we want to recalculate - // the source-target lines - toolGroup.setToolEnabled(ReferenceLinesTool.toolName); - } + viewportGridService.EVENTS.ACTIVE_VIEWPORT_ID_CHANGED, + activeViewportEventListener ); } @@ -320,10 +342,9 @@ function CPUModal() { return (

- Your computer does not have enough GPU power to support the default GPU - rendering mode. OHIF has switched to CPU rendering mode. Please note - that CPU rendering does not support all features such as Volume - Rendering, Multiplanar Reconstruction, and Segmentation Overlays. + Your computer does not have enough GPU power to support the default GPU rendering mode. OHIF + has switched to CPU rendering mode. Please note that CPU rendering does not support all + features such as Volume Rendering, Multiplanar Reconstruction, and Segmentation Overlays.

); diff --git a/extensions/cornerstone/src/initContextMenu.ts b/extensions/cornerstone/src/initContextMenu.ts index bd11dfa719c..488fdf85879 100644 --- a/extensions/cornerstone/src/initContextMenu.ts +++ b/extensions/cornerstone/src/initContextMenu.ts @@ -18,6 +18,7 @@ const DEFAULT_CONTEXT_MENU_CLICKS = { { commandName: 'showCornerstoneContextMenu', commandOptions: { + requireNearbyToolData: true, menuId: 'measurementsContextMenu', }, }, @@ -60,12 +61,22 @@ function initContextMenu({ */ const cornerstoneViewportHandleEvent = (name, evt) => { const customizations = - customizationService.get('cornerstoneViewportClickCommands') || - DEFAULT_CONTEXT_MENU_CLICKS; + customizationService.get('cornerstoneViewportClickCommands') || DEFAULT_CONTEXT_MENU_CLICKS; const toRun = customizations[name]; - console.log('initContextMenu::cornerstoneViewportHandleEvent', name, toRun); + + if (!toRun) { + return; + } + + // only find nearbyToolData if required, for the click (which closes the context menu + // we don't need to find nearbyToolData) + let nearbyToolData = null; + if (toRun.commands.some(command => command.commandOptions?.requireNearbyToolData)) { + nearbyToolData = findNearbyToolData(commandsManager, evt); + } + const options = { - nearbyToolData: findNearbyToolData(commandsManager, evt), + nearbyToolData, event: evt, }; commandsManager.run(toRun, options); @@ -82,34 +93,21 @@ function initContextMenu({ if (!viewportInfo) { return; } - const viewportIndex = viewportInfo.getViewportIndex(); // TODO check update upstream - setEnabledElement(viewportIndex, element); + setEnabledElement(viewportId, element); - element.addEventListener( - cs3DToolsEvents.MOUSE_CLICK, - cornerstoneViewportHandleClick - ); + element.addEventListener(cs3DToolsEvents.MOUSE_CLICK, cornerstoneViewportHandleClick); } function elementDisabledHandler(evt) { const { element } = evt.detail; - element.removeEventListener( - cs3DToolsEvents.MOUSE_CLICK, - cornerstoneViewportHandleClick - ); + element.removeEventListener(cs3DToolsEvents.MOUSE_CLICK, cornerstoneViewportHandleClick); } - eventTarget.addEventListener( - EVENTS.ELEMENT_ENABLED, - elementEnabledHandler.bind(null) - ); + eventTarget.addEventListener(EVENTS.ELEMENT_ENABLED, elementEnabledHandler.bind(null)); - eventTarget.addEventListener( - EVENTS.ELEMENT_DISABLED, - elementDisabledHandler.bind(null) - ); + eventTarget.addEventListener(EVENTS.ELEMENT_DISABLED, elementDisabledHandler.bind(null)); } export default initContextMenu; diff --git a/extensions/cornerstone/src/initCornerstoneTools.js b/extensions/cornerstone/src/initCornerstoneTools.js index 9549b17f911..767b607a772 100644 --- a/extensions/cornerstone/src/initCornerstoneTools.js +++ b/extensions/cornerstone/src/initCornerstoneTools.js @@ -25,9 +25,13 @@ import { annotation, ReferenceLinesTool, TrackballRotateTool, + CircleScissorsTool, + RectangleScissorsTool, + SphereScissorsTool, } from '@cornerstonejs/tools'; import CalibrationLineTool from './tools/CalibrationLineTool'; +import ImageOverlayViewerTool from './tools/ImageOverlayViewerTool'; export default function initCornerstoneTools(configuration = {}) { CrosshairsTool.isAnnotation = false; @@ -58,6 +62,10 @@ export default function initCornerstoneTools(configuration = {}) { addTool(ReferenceLinesTool); addTool(CalibrationLineTool); addTool(TrackballRotateTool); + addTool(CircleScissorsTool); + addTool(RectangleScissorsTool); + addTool(SphereScissorsTool); + addTool(ImageOverlayViewerTool); // Modify annotation tools to use dashed lines on SR const annotationStyle = { @@ -99,6 +107,10 @@ const toolNames = { ReferenceLines: ReferenceLinesTool.toolName, CalibrationLine: CalibrationLineTool.toolName, TrackballRotateTool: TrackballRotateTool.toolName, + CircleScissors: CircleScissorsTool.toolName, + RectangleScissors: RectangleScissorsTool.toolName, + SphereScissors: SphereScissorsTool.toolName, + ImageOverlayViewer: ImageOverlayViewerTool.toolName, }; export { toolNames }; diff --git a/extensions/cornerstone/src/initDoubleClick.ts b/extensions/cornerstone/src/initDoubleClick.ts index 14f80c77525..049d72cbee5 100644 --- a/extensions/cornerstone/src/initDoubleClick.ts +++ b/extensions/cornerstone/src/initDoubleClick.ts @@ -39,10 +39,7 @@ export type initDoubleClickArgs = { commandsManager: CommandsManager; }; -function initDoubleClick({ - customizationService, - commandsManager, -}: initDoubleClickArgs): void { +function initDoubleClick({ customizationService, commandsManager }: initDoubleClickArgs): void { const cornerstoneViewportHandleDoubleClick = (evt: CustomEvent) => { // Do not allow double click on a tool. const nearbyToolData = findNearbyToolData(commandsManager, evt); @@ -54,8 +51,7 @@ function initDoubleClick({ // Allows for the customization of the double click on a viewport. const customizations = - customizationService.get('cornerstoneViewportClickCommands') || - DEFAULT_DOUBLE_CLICK; + customizationService.get('cornerstoneViewportClickCommands') || DEFAULT_DOUBLE_CLICK; const toRun = customizations[eventName]; @@ -84,15 +80,9 @@ function initDoubleClick({ ); } - eventTarget.addEventListener( - EVENTS.ELEMENT_ENABLED, - elementEnabledHandler.bind(null) - ); + eventTarget.addEventListener(EVENTS.ELEMENT_ENABLED, elementEnabledHandler.bind(null)); - eventTarget.addEventListener( - EVENTS.ELEMENT_DISABLED, - elementDisabledHandler.bind(null) - ); + eventTarget.addEventListener(EVENTS.ELEMENT_DISABLED, elementDisabledHandler.bind(null)); } export default initDoubleClick; diff --git a/extensions/cornerstone/src/initMeasurementService.js b/extensions/cornerstone/src/initMeasurementService.js index 3a883cc79da..2ab5cce55be 100644 --- a/extensions/cornerstone/src/initMeasurementService.js +++ b/extensions/cornerstone/src/initMeasurementService.js @@ -127,11 +127,8 @@ const initMeasurementService = ( }; const connectToolsToMeasurementService = servicesManager => { - const { - measurementService, - displaySetService, - cornerstoneViewportService, - } = servicesManager.services; + const { measurementService, displaySetService, cornerstoneViewportService } = + servicesManager.services; const csTools3DVer1MeasurementSource = initMeasurementService( measurementService, displaySetService, @@ -153,10 +150,7 @@ const connectToolsToMeasurementService = servicesManager => { } = annotationAddedEventDetail; const { toolName } = metadata; - if ( - csToolsEvent.type === completedEvt && - toolName === toolNames.CalibrationLine - ) { + if (csToolsEvent.type === completedEvt && toolName === toolNames.CalibrationLine) { // show modal to input the measurement (mm) onCompletedCalibrationLine(servicesManager, csToolsEvent) .then( @@ -212,10 +206,8 @@ const connectToolsToMeasurementService = servicesManager => { try { const annotationSelectionEventDetail = csToolsEvent.detail; - const { - added: addedSelectedAnnotationUIDs, - removed: removedSelectedAnnotationUIDs, - } = annotationSelectionEventDetail; + const { added: addedSelectedAnnotationUIDs, removed: removedSelectedAnnotationUIDs } = + annotationSelectionEventDetail; if (removedSelectedAnnotationUIDs) { removedSelectedAnnotationUIDs.forEach(annotationUID => @@ -283,12 +275,8 @@ const connectMeasurementServiceToTools = ( cornerstoneViewportService, measurementSource ) => { - const { - MEASUREMENT_REMOVED, - MEASUREMENTS_CLEARED, - MEASUREMENT_UPDATED, - RAW_MEASUREMENT_ADDED, - } = measurementService.EVENTS; + const { MEASUREMENT_REMOVED, MEASUREMENTS_CLEARED, MEASUREMENT_UPDATED, RAW_MEASUREMENT_ADDED } = + measurementService.EVENTS; const csTools3DVer1MeasurementSource = measurementService.getSource( CORNERSTONE_3D_TOOLS_SOURCE_NAME, @@ -351,11 +339,7 @@ const connectMeasurementServiceToTools = ( return; } - const { - referenceSeriesUID, - referenceStudyUID, - SOPInstanceUID, - } = measurement; + const { referenceSeriesUID, referenceStudyUID, SOPInstanceUID } = measurement; const instance = DicomMetadataStore.getInstance( referenceStudyUID, @@ -368,9 +352,7 @@ const connectMeasurementServiceToTools = ( if (measurement?.metadata?.referencedImageId) { imageId = measurement.metadata.referencedImageId; - frameNumber = getSOPInstanceAttributes( - measurement.metadata.referencedImageId - ).frameNumber; + frameNumber = getSOPInstanceAttributes(measurement.metadata.referencedImageId).frameNumber; } else { imageId = dataSource.getImageIdsForInstance({ instance }); } diff --git a/extensions/cornerstone/src/initWADOImageLoader.js b/extensions/cornerstone/src/initWADOImageLoader.js index 8b2aec1cc2c..622a4262574 100644 --- a/extensions/cornerstone/src/initWADOImageLoader.js +++ b/extensions/cornerstone/src/initWADOImageLoader.js @@ -1,9 +1,7 @@ import * as cornerstone from '@cornerstonejs/core'; import { volumeLoader } from '@cornerstonejs/core'; import { cornerstoneStreamingImageVolumeLoader } from '@cornerstonejs/streaming-image-volume-loader'; -import dicomImageLoader, { - webWorkerManager, -} from '@cornerstonejs/dicom-image-loader'; +import dicomImageLoader, { webWorkerManager } from '@cornerstonejs/dicom-image-loader'; import dicomParser from 'dicom-parser'; import { errorHandler, utils } from '@ohif/core'; @@ -41,10 +39,7 @@ export default function initWADOImageLoader( dicomImageLoader.external.cornerstone = cornerstone; dicomImageLoader.external.dicomParser = dicomParser; - registerVolumeLoader( - 'cornerstoneStreamingImageVolume', - cornerstoneStreamingImageVolumeLoader - ); + registerVolumeLoader('cornerstoneStreamingImageVolume', cornerstoneStreamingImageVolumeLoader); dicomImageLoader.configure({ decodeConfig: { @@ -54,11 +49,12 @@ export default function initWADOImageLoader( // Until the default is set to true (which is the case for cornerstone3D), // we should set this flag to false. convertFloatPixelDataToInt: false, + use16BitDataType: + Boolean(appConfig.useNorm16Texture) || Boolean(appConfig.preferSizeOverAccuracy), }, - beforeSend: function(xhr) { + beforeSend: function (xhr) { //TODO should be removed in the future and request emitted by DicomWebDataSource - const sourceConfig = - extensionManager.getActiveDataSource()?.[0].getConfig() ?? {}; + const sourceConfig = extensionManager.getActiveDataSource()?.[0].getConfig() ?? {}; const headers = userAuthenticationService.getAuthorizationHeader(); const acceptHeader = utils.generateAcceptHeader( sourceConfig.acceptHeader, diff --git a/extensions/cornerstone/src/services/CornerstoneCacheService/CornerstoneCacheService.ts b/extensions/cornerstone/src/services/CornerstoneCacheService/CornerstoneCacheService.ts index 31ea90cb236..a3db943ab27 100644 --- a/extensions/cornerstone/src/services/CornerstoneCacheService/CornerstoneCacheService.ts +++ b/extensions/cornerstone/src/services/CornerstoneCacheService/CornerstoneCacheService.ts @@ -1,16 +1,8 @@ import { ServicesManager, Types } from '@ohif/core'; -import { - cache as cs3DCache, - Enums, - volumeLoader, - utilities as utils, -} from '@cornerstonejs/core'; +import { cache as cs3DCache, Enums, volumeLoader, utilities as utils } from '@cornerstonejs/core'; import getCornerstoneViewportType from '../../utils/getCornerstoneViewportType'; -import { - StackViewportData, - VolumeViewportData, -} from '../../types/CornerstoneCacheService'; +import { StackViewportData, VolumeViewportData } from '../../types/CornerstoneCacheService'; const VOLUME_LOADER_SCHEME = 'cornerstoneStreamingImageVolume'; @@ -18,9 +10,7 @@ class CornerstoneCacheService { static REGISTRATION = { name: 'cornerstoneCacheService', altName: 'CornerstoneCacheService', - create: ({ - servicesManager, - }: Types.Extensions.ExtensionParams): CornerstoneCacheService => { + create: ({ servicesManager }: Types.Extensions.ExtensionParams): CornerstoneCacheService => { return new CornerstoneCacheService(servicesManager); }, }; @@ -77,11 +67,7 @@ class CornerstoneCacheService { cs3DViewportType === Enums.ViewportType.ORTHOGRAPHIC || cs3DViewportType === Enums.ViewportType.VOLUME_3D ) { - viewportData = await this._getVolumeViewportData( - dataSource, - displaySets, - cs3DViewportType - ); + viewportData = await this._getVolumeViewportData(dataSource, displaySets, cs3DViewportType); } viewportData.viewportType = cs3DViewportType; @@ -134,20 +120,14 @@ class CornerstoneCacheService { // For Stack Viewport we don't have fusion currently const displaySet = displaySets[0]; - let stackImageIds = this.stackImageIds.get( - displaySet.displaySetInstanceUID - ); + let stackImageIds = this.stackImageIds.get(displaySet.displaySetInstanceUID); if (!stackImageIds) { stackImageIds = this._getCornerstoneStackImageIds(displaySet, dataSource); this.stackImageIds.set(displaySet.displaySetInstanceUID, stackImageIds); } - const { - displaySetInstanceUID, - StudyInstanceUID, - isCompositeStack, - } = displaySet; + const { displaySetInstanceUID, StudyInstanceUID, isCompositeStack } = displaySet; const StackViewportData: StackViewportData = { viewportType, @@ -196,31 +176,22 @@ class CornerstoneCacheService { continue; } - const volumeLoaderSchema = - displaySet.volumeLoaderSchema ?? VOLUME_LOADER_SCHEME; + const volumeLoaderSchema = displaySet.volumeLoaderSchema ?? VOLUME_LOADER_SCHEME; const volumeId = `${volumeLoaderSchema}:${displaySet.displaySetInstanceUID}`; - let volumeImageIds = this.volumeImageIds.get( - displaySet.displaySetInstanceUID - ); + let volumeImageIds = this.volumeImageIds.get(displaySet.displaySetInstanceUID); let volume = cs3DCache.getVolume(volumeId); if (!volumeImageIds || !volume) { - volumeImageIds = this._getCornerstoneVolumeImageIds( - displaySet, - dataSource - ); + volumeImageIds = this._getCornerstoneVolumeImageIds(displaySet, dataSource); volume = await volumeLoader.createAndCacheVolume(volumeId, { imageIds: volumeImageIds, }); - this.volumeImageIds.set( - displaySet.displaySetInstanceUID, - volumeImageIds - ); + this.volumeImageIds.set(displaySet.displaySetInstanceUID, volumeImageIds); } volumeData.push({ @@ -239,7 +210,7 @@ class CornerstoneCacheService { } private _shouldRenderSegmentation(displaySets) { - const { segmentationService } = this.servicesManager.services; + const { segmentationService, displaySetService } = this.servicesManager.services; const viewportDisplaySetInstanceUIDs = displaySets.map( ({ displaySetInstanceUID }) => displaySetInstanceUID @@ -251,10 +222,13 @@ class CornerstoneCacheService { for (const segmentation of segmentations) { const segDisplaySetInstanceUID = segmentation.displaySetInstanceUID; + const segDisplaySet = displaySetService.getDisplaySetByUID(segDisplaySetInstanceUID); + + const instance = segDisplaySet.instances?.[0] || segDisplaySet.instance; const shouldDisplaySeg = segmentationService.shouldRenderSegmentation( viewportDisplaySetInstanceUIDs, - segDisplaySetInstanceUID + instance.FrameOfReferenceUID ); if (shouldDisplaySeg) { @@ -268,10 +242,7 @@ class CornerstoneCacheService { } private _getCornerstoneVolumeImageIds(displaySet, dataSource): string[] { - const stackImageIds = this._getCornerstoneStackImageIds( - displaySet, - dataSource - ); + const stackImageIds = this._getCornerstoneStackImageIds(displaySet, dataSource); return stackImageIds; } diff --git a/extensions/cornerstone/src/services/SegmentationService/RTSTRUCT/mapROIContoursToRTStructData.ts b/extensions/cornerstone/src/services/SegmentationService/RTSTRUCT/mapROIContoursToRTStructData.ts index f17ec5c7048..b613074128c 100644 --- a/extensions/cornerstone/src/services/SegmentationService/RTSTRUCT/mapROIContoursToRTStructData.ts +++ b/extensions/cornerstone/src/services/SegmentationService/RTSTRUCT/mapROIContoursToRTStructData.ts @@ -7,32 +7,27 @@ * @returns An array of object that includes data, id, segmentIndex, color * and geometry Id */ -export function mapROIContoursToRTStructData( - structureSet: unknown, - rtDisplaySetUID: unknown -) { - return structureSet.ROIContours.map( - ({ contourPoints, ROINumber, ROIName, colorArray }) => { - const data = contourPoints.map(({ points, ...rest }) => { - const newPoints = points.map(({ x, y, z }) => { - return [x, y, z]; - }); - - return { - ...rest, - points: newPoints, - }; +export function mapROIContoursToRTStructData(structureSet: unknown, rtDisplaySetUID: unknown) { + return structureSet.ROIContours.map(({ contourPoints, ROINumber, ROIName, colorArray }) => { + const data = contourPoints.map(({ points, ...rest }) => { + const newPoints = points.map(({ x, y, z }) => { + return [x, y, z]; }); - const id = ROIName || ROINumber; - return { - data, - id, - segmentIndex: ROINumber, - color: colorArray, - geometryId: `${rtDisplaySetUID}:${id}:segmentIndex-${ROINumber}`, + ...rest, + points: newPoints, }; - } - ); + }); + + const id = ROIName || ROINumber; + + return { + data, + id, + segmentIndex: ROINumber, + color: colorArray, + geometryId: `${rtDisplaySetUID}:${id}:segmentIndex-${ROINumber}`, + }; + }); } diff --git a/extensions/cornerstone/src/services/SegmentationService/SegmentationService.ts b/extensions/cornerstone/src/services/SegmentationService/SegmentationService.ts index eac898ab098..f71dbff5d80 100644 --- a/extensions/cornerstone/src/services/SegmentationService/SegmentationService.ts +++ b/extensions/cornerstone/src/services/SegmentationService/SegmentationService.ts @@ -1,5 +1,3 @@ -import cloneDeep from 'lodash.clonedeep'; - import { Types as OhifTypes, ServicesManager, PubSubService } from '@ohif/core'; import { cache, @@ -7,12 +5,10 @@ import { geometryLoader, eventTarget, getEnabledElementByIds, - metaData, utilities as csUtils, volumeLoader, } from '@cornerstonejs/core'; import { - CONSTANTS as cstConstants, Enums as csToolsEnums, segmentation as cstSegmentation, Types as cstTypes, @@ -21,14 +17,9 @@ import { import isEqual from 'lodash.isequal'; import { Types as ohifTypes } from '@ohif/core'; import { easeInOutBell, reverseEaseInOutBell } from '../../utils/transitions'; -import { - Segment, - Segmentation, - SegmentationConfig, -} from './SegmentationServiceTypes'; +import { Segment, Segmentation, SegmentationConfig } from './SegmentationServiceTypes'; import { mapROIContoursToRTStructData } from './RTSTRUCT/mapROIContoursToRTStructData'; -const { COLOR_LUT } = cstConstants; const LABELMAP = csToolsEnums.SegmentationRepresentations.Labelmap; const CONTOUR = csToolsEnums.SegmentationRepresentations.Contour; @@ -42,11 +33,10 @@ const EVENTS = { // fired when the segmentation is removed SEGMENTATION_REMOVED: 'event::segmentation_removed', // fired when the configuration for the segmentation is changed (e.g., brush size, render fill, outline thickness, etc.) - SEGMENTATION_CONFIGURATION_CHANGED: - 'event::segmentation_configuration_changed', + SEGMENTATION_CONFIGURATION_CHANGED: 'event::segmentation_configuration_changed', // fired when the active segment is loaded in SEG or RTSTRUCT SEGMENT_LOADING_COMPLETE: 'event::segment_loading_complete', - // for all segments + // loading completed for all segments SEGMENTATION_LOADING_COMPLETE: 'event::segmentation_loading_complete', }; @@ -64,9 +54,7 @@ class SegmentationService extends PubSubService { static REGISTRATION = { name: 'segmentationService', altName: 'SegmentationService', - create: ({ - servicesManager, - }: OhifTypes.Extensions.ExtensionParams): SegmentationService => { + create: ({ servicesManager }: OhifTypes.Extensions.ExtensionParams): SegmentationService => { return new SegmentationService({ servicesManager }); }, }; @@ -106,42 +94,50 @@ class SegmentationService extends PubSubService { }; /** - * It adds a segment to a segmentation, basically just setting the properties for - * the segment. - * @param segmentationId - The ID of the segmentation you want to add a - * segment to. - * @param segmentIndex - The index of the segment to add. - * @param properties - The properties of the segment to add including - * -- label: the label of the segment - * -- color: the color of the segment - * -- opacity: the opacity of the segment - * -- visibility: the visibility of the segment (boolean) - * -- isLocked: whether the segment is locked for editing - * -- active: whether the segment is currently the active segment to be edited + * Adds a new segment to the specified segmentation. + * @param segmentationId - The ID of the segmentation to add the segment to. + * @param config - An object containing the configuration options for the new segment. + * - segmentIndex: (optional) The index of the segment to add. If not provided, the next available index will be used. + * - toolGroupId: (optional) The ID of the tool group to associate the new segment with. If not provided, the first available tool group will be used. + * - properties: (optional) An object containing the properties of the new segment. + * - label: (optional) The label of the new segment. If not provided, a default label will be used. + * - color: (optional) The color of the new segment in RGB format. If not provided, a default color will be used. + * - opacity: (optional) The opacity of the new segment. If not provided, a default opacity will be used. + * - visibility: (optional) Whether the new segment should be visible. If not provided, the segment will be visible by default. + * - isLocked: (optional) Whether the new segment should be locked for editing. If not provided, the segment will not be locked by default. + * - active: (optional) Whether the new segment should be the active segment to be edited. If not provided, the segment will not be active by default. */ public addSegment( segmentationId: string, - segmentIndex: number, - toolGroupId?: string, - properties?: { - label?: string; - color?: ohifTypes.RGB; - opacity?: number; - visibility?: boolean; - isLocked?: boolean; - active?: boolean; - } + config: { + segmentIndex?: number; + toolGroupId?: string; + properties?: { + label?: string; + color?: ohifTypes.RGB; + opacity?: number; + visibility?: boolean; + isLocked?: boolean; + active?: boolean; + }; + } = {} ): void { - if (segmentIndex === 0) { + if (config?.segmentIndex === 0) { throw new Error('Segment index 0 is reserved for "no label"'); } - toolGroupId = toolGroupId ?? this._getFirstToolGroupId(); + const toolGroupId = config.toolGroupId ?? this._getApplicableToolGroupId(); - const { - segmentationRepresentationUID, - segmentation, - } = this._getSegmentationInfo(segmentationId, toolGroupId); + const { segmentationRepresentationUID, segmentation } = this._getSegmentationInfo( + segmentationId, + toolGroupId + ); + + let segmentIndex = config.segmentIndex; + if (!segmentIndex) { + // grab the next available segment index + segmentIndex = segmentation.segments.length === 0 ? 1 : segmentation.segments.length; + } if (this._getSegmentInfo(segmentation, segmentIndex)) { throw new Error(`Segment ${segmentIndex} already exists`); @@ -154,7 +150,7 @@ class SegmentationService extends PubSubService { ); segmentation.segments[segmentIndex] = { - label: properties.label, + label: config.properties?.label ?? `Segment ${segmentIndex}`, segmentIndex: segmentIndex, color: [rgbaColor[0], rgbaColor[1], rgbaColor[2]], opacity: rgbaColor[3], @@ -164,34 +160,19 @@ class SegmentationService extends PubSubService { segmentation.segmentCount++; + // make the newly added segment the active segment + this._setActiveSegment(segmentationId, segmentIndex); + const suppressEvents = true; - if (properties !== undefined) { - const { - color: newColor, - opacity, - isLocked, - visibility, - active, - } = properties; + if (config.properties !== undefined) { + const { color: newColor, opacity, isLocked, visibility, active } = config.properties; if (newColor !== undefined) { - this._setSegmentColor( - segmentationId, - segmentIndex, - newColor, - toolGroupId, - suppressEvents - ); + this._setSegmentColor(segmentationId, segmentIndex, newColor, toolGroupId, suppressEvents); } if (opacity !== undefined) { - this._setSegmentOpacity( - segmentationId, - segmentIndex, - opacity, - toolGroupId, - suppressEvents - ); + this._setSegmentOpacity(segmentationId, segmentIndex, opacity, toolGroupId, suppressEvents); } if (visibility !== undefined) { @@ -204,17 +185,12 @@ class SegmentationService extends PubSubService { ); } - if (active !== undefined) { + if (active === true) { this._setActiveSegment(segmentationId, segmentIndex, suppressEvents); } if (isLocked !== undefined) { - this._setSegmentLocked( - segmentationId, - segmentIndex, - isLocked, - suppressEvents - ); + this._setSegmentLocked(segmentationId, segmentIndex, isLocked, suppressEvents); } } @@ -285,9 +261,7 @@ class SegmentationService extends PubSubService { if (segmentation.activeSegmentIndex === segmentIndex) { const segmentIndices = Object.keys(segmentation.segments); - const newActiveSegmentIndex = segmentIndices.length - ? Number(segmentIndices[0]) - : 1; + const newActiveSegmentIndex = segmentIndices.length ? Number(segmentIndices[0]) : 1; this._setActiveSegment(segmentationId, newActiveSegmentIndex, true); } @@ -313,26 +287,21 @@ class SegmentationService extends PubSubService { ); } - public setSegmentLockedForSegmentation( - segmentationId: string, - segmentIndex: number, - isLocked: boolean - ): void { + public setSegmentLocked(segmentationId: string, segmentIndex: number, isLocked: boolean): void { const suppressEvents = false; - this._setSegmentLocked( - segmentationId, - segmentIndex, - isLocked, - suppressEvents - ); + this._setSegmentLocked(segmentationId, segmentIndex, isLocked, suppressEvents); } - public setSegmentLabel( - segmentationId: string, - segmentIndex: number, - segmentLabel: string - ): void { - this._setSegmentLabel(segmentationId, segmentIndex, segmentLabel); + /** + * Toggles the locked state of a segment in a segmentation. + * @param segmentationId - The ID of the segmentation. + * @param segmentIndex - The index of the segment to toggle. + */ + public toggleSegmentLocked(segmentationId: string, segmentIndex: number): void { + const segmentation = this.getSegmentation(segmentationId); + const segment = this._getSegmentInfo(segmentation, segmentIndex); + const isLocked = !segment.isLocked; + this._setSegmentLocked(segmentationId, segmentIndex, isLocked); } public setSegmentColor( @@ -387,24 +356,14 @@ class SegmentationService extends PubSubService { this._setSegmentOpacity(segmentationId, segmentIndex, opacity, toolGroupId); } - public setActiveSegmentationForToolGroup( - segmentationId: string, - toolGroupId?: string - ): void { - toolGroupId = toolGroupId ?? this._getFirstToolGroupId(); + public setActiveSegmentationForToolGroup(segmentationId: string, toolGroupId?: string): void { + toolGroupId = toolGroupId ?? this._getApplicableToolGroupId(); const suppressEvents = false; - this._setActiveSegmentationForToolGroup( - segmentationId, - toolGroupId, - suppressEvents - ); + this._setActiveSegmentationForToolGroup(segmentationId, toolGroupId, suppressEvents); } - public setActiveSegmentForSegmentation( - segmentationId: string, - segmentIndex: number - ): void { + public setActiveSegment(segmentationId: string, segmentIndex: number): void { this._setActiveSegment(segmentationId, segmentIndex, false); } @@ -420,9 +379,7 @@ class SegmentationService extends PubSubService { * @return Array of segmentations */ - public getSegmentations( - filterNonHydratedSegmentations = true - ): Segmentation[] { + public getSegmentations(filterNonHydratedSegmentations = true): Segmentation[] { const segmentations = this._getSegmentations(); return ( @@ -435,10 +392,24 @@ class SegmentationService extends PubSubService { private _getSegmentations(): Segmentation[] { const segmentations = this.arrayOfObjects(this.segmentations); - return ( - segmentations && - segmentations.map(m => this.segmentations[Object.keys(m)[0]]) - ); + return segmentations && segmentations.map(m => this.segmentations[Object.keys(m)[0]]); + } + + public getActiveSegmentation(): Segmentation { + const segmentations = this.getSegmentations(); + + return segmentations.find(segmentation => segmentation.isActive); + } + + public getActiveSegment() { + const activeSegmentation = this.getActiveSegmentation(); + const { activeSegmentIndex, segments } = activeSegmentation; + + if (activeSegmentIndex === null) { + return; + } + + return segments[activeSegmentIndex]; } /** @@ -477,8 +448,7 @@ class SegmentationService extends PubSubService { } const representationType = segmentation.type; - const representationData = - segmentation.representationData[representationType]; + const representationData = segmentation.representationData[representationType]; cstSegmentation.addSegmentations([ { segmentationId, @@ -491,13 +461,6 @@ class SegmentationService extends PubSubService { }, ]); - // Define a new color LUT and associate it with this segmentation. - // Todo: need to be generalized to accept custom color LUTs - const newColorLUT = this.generateNewColorLUT(); - const newColorLUTIndex = this.getNextColorLUTIndex(); - - cstSegmentation.config.color.addColorLUT(newColorLUT, newColorLUTIndex); - this.segmentations[segmentationId] = { ...segmentation, label: segmentation.label || '', @@ -505,7 +468,6 @@ class SegmentationService extends PubSubService { activeSegmentIndex: segmentation.activeSegmentIndex ?? null, segmentCount: segmentation.segmentCount ?? 0, isActive: false, - colorLUTIndex: newColorLUTIndex, isVisible: true, }; @@ -566,9 +528,7 @@ class SegmentationService extends PubSubService { const { labelmapBufferArray, referencedVolumeId } = segDisplaySet; if (!labelmapBufferArray || !referencedVolumeId) { - throw new Error( - 'No labelmapBufferArray or referencedVolumeId found for the SEG displaySet' - ); + throw new Error('No labelmapBufferArray or referencedVolumeId found for the SEG displaySet'); } // if the labelmap doesn't exist, we need to create it first from the @@ -576,23 +536,18 @@ class SegmentationService extends PubSubService { const referencedVolume = cache.getVolume(referencedVolumeId); if (!referencedVolume) { - throw new Error( - `No volume found for referencedVolumeId: ${referencedVolumeId}` - ); + throw new Error(`No volume found for referencedVolumeId: ${referencedVolumeId}`); } // Force use of a Uint8Array SharedArrayBuffer for the segmentation to save space and so // it is easily compressible in worker thread. - const derivedVolume = await volumeLoader.createAndCacheDerivedVolume( - referencedVolumeId, - { - volumeId: segmentationId, - targetBuffer: { - type: 'Uint8Array', - sharedArrayBuffer: true, - }, - } - ); + const derivedVolume = await volumeLoader.createAndCacheDerivedVolume(referencedVolumeId, { + volumeId: segmentationId, + targetBuffer: { + type: 'Uint8Array', + sharedArrayBuffer: window.SharedArrayBuffer, + }, + }); const derivedVolumeScalarData = derivedVolume.getScalarData(); const segmentsInfo = segDisplaySet.segMetadata.data; @@ -680,10 +635,7 @@ class SegmentationService extends PubSubService { const defaultScheme = this._getDefaultSegmentationScheme(); const rtDisplaySetUID = rtDisplaySet.displaySetInstanceUID; - const allRTStructData = mapROIContoursToRTStructData( - structureSet, - rtDisplaySetUID - ); + const allRTStructData = mapROIContoursToRTStructData(structureSet, rtDisplaySetUID); // sort by segmentIndex allRTStructData.sort((a, b) => a.segmentIndex - b.segmentIndex); @@ -726,19 +678,16 @@ class SegmentationService extends PubSubService { // catch error instead of failing to allow loading to continue try { - const geometry = await geometryLoader.createAndCacheGeometry( - geometryId, - { - geometryData: { - data, - id, - color, - frameOfReferenceUID: structureSet.frameOfReferenceUID, - segmentIndex, - }, - type: csEnums.GeometryType.CONTOUR, - } - ); + const geometry = await geometryLoader.createAndCacheGeometry(geometryId, { + geometryData: { + data, + id, + color, + frameOfReferenceUID: structureSet.frameOfReferenceUID, + segmentIndex, + }, + type: csEnums.GeometryType.CONTOUR, + }); const contourSet = geometry.data; const centroid = contourSet.getCentroid(); @@ -758,9 +707,7 @@ class SegmentationService extends PubSubService { const numInitialized = Object.keys(segmentsCachedStats).length; // Calculate percentage completed - const percentComplete = Math.round( - (numInitialized / allRTStructData.length) * 100 - ); + const percentComplete = Math.round((numInitialized / allRTStructData.length) * 100); this._broadcastEvent(EVENTS.SEGMENT_LOADING_COMPLETE, { percentComplete, @@ -808,6 +755,97 @@ class SegmentationService extends PubSubService { return this.addOrUpdateSegmentation(segmentation, suppressEvents); } + // Todo: this should not run on the main thread + public calculateCentroids = ( + segmentationId: string, + segmentIndex?: number + ): Map => { + const segmentation = this.getSegmentation(segmentationId); + const volume = this.getLabelmapVolume(segmentationId); + const { dimensions, imageData } = volume; + const scalarData = volume.getScalarData(); + const [dimX, dimY, numFrames] = dimensions; + const frameLength = dimX * dimY; + + const segmentIndices = segmentIndex + ? [segmentIndex] + : segmentation.segments + .filter(segment => segment?.segmentIndex) + .map(segment => segment.segmentIndex); + + const segmentIndicesSet = new Set(segmentIndices); + + const centroids = new Map(); + for (const index of segmentIndicesSet) { + centroids.set(index, { x: 0, y: 0, z: 0, count: 0 }); + } + + let voxelIndex = 0; + for (let frame = 0; frame < numFrames; frame++) { + for (let p = 0; p < frameLength; p++) { + const segmentIndex = scalarData[voxelIndex++]; + if (segmentIndicesSet.has(segmentIndex)) { + const centroid = centroids.get(segmentIndex); + centroid.x += p % dimX; + centroid.y += (p / dimX) | 0; + centroid.z += frame; + centroid.count++; + } + } + } + + const result = new Map(); + for (const [index, centroid] of centroids) { + const count = centroid.count; + const normalizedCentroid = { + x: centroid.x / count, + y: centroid.y / count, + z: centroid.z / count, + }; + normalizedCentroid.world = imageData.indexToWorld([ + normalizedCentroid.x, + normalizedCentroid.y, + normalizedCentroid.z, + ]); + result.set(index, normalizedCentroid); + } + + this.setCentroids(segmentationId, result); + return result; + }; + + private setCentroids = ( + segmentationId: string, + centroids: Map + ): void => { + const segmentation = this.getSegmentation(segmentationId); + const imageData = this.getLabelmapVolume(segmentationId).imageData; // Assuming this method returns imageData + + if (!segmentation.cachedStats) { + segmentation.cachedStats = { segmentCenter: {} }; + } else if (!segmentation.cachedStats.segmentCenter) { + segmentation.cachedStats.segmentCenter = {}; + } + + for (const [segmentIndex, centroid] of centroids) { + let world = centroid.world; + + // If world coordinates are not provided, calculate them + if (!world || world.length === 0) { + world = imageData.indexToWorld(centroid.image); + } + + segmentation.cachedStats.segmentCenter[segmentIndex] = { + center: { + image: centroid.image, + world: world, + }, + }; + } + + this.addOrUpdateSegmentation(segmentation, true, true); + }; + public jumpToSegmentCenter( segmentationId: string, segmentIndex: number, @@ -821,11 +859,14 @@ class SegmentationService extends PubSubService { const { toolGroupService } = this.servicesManager.services; const center = this._getSegmentCenter(segmentationId, segmentIndex); + if (!center?.world) { + return; + } + const { world } = center; // todo: generalize - toolGroupId = - toolGroupId || this._getToolGroupIdsWithSegmentation(segmentationId); + toolGroupId = toolGroupId || this._getToolGroupIdsWithSegmentation(segmentationId); const toolGroups = []; @@ -842,10 +883,7 @@ class SegmentationService extends PubSubService { // @ts-ignore for (const { viewportId, renderingEngineId } of viewportsInfo) { - const { viewport } = getEnabledElementByIds( - viewportId, - renderingEngineId - ); + const { viewport } = getEnabledElementByIds(viewportId, renderingEngineId); cstUtils.viewport.jumpToWorld(viewport, world); } @@ -877,7 +915,7 @@ class SegmentationService extends PubSubService { } const segmentation = this.getSegmentation(segmentationId); - toolGroupId = toolGroupId ?? this._getFirstToolGroupId(); + toolGroupId = toolGroupId ?? this._getApplicableToolGroupId(); const segmentationRepresentation = this._getSegmentationRepresentation( segmentationId, @@ -888,9 +926,7 @@ class SegmentationService extends PubSubService { const { segments } = segmentation; const highlightFn = - type === LABELMAP - ? this._highlightLabelmap.bind(this) - : this._highlightContour.bind(this); + type === LABELMAP ? this._highlightLabelmap.bind(this) : this._highlightContour.bind(this); const adjustedAlpha = type === LABELMAP ? alpha : 1 - alpha; @@ -909,14 +945,13 @@ class SegmentationService extends PubSubService { displaySetInstanceUID: string, options?: { segmentationId: string; + FrameOfReferenceUID: string; label: string; } ): Promise => { const { displaySetService } = this.servicesManager.services; - const displaySet = displaySetService.getDisplaySetByUID( - displaySetInstanceUID - ); + const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID); // Todo: we currently only support labelmap for segmentation for a displaySet const representationType = LABELMAP; @@ -931,7 +966,7 @@ class SegmentationService extends PubSubService { volumeId: segmentationId, targetBuffer: { type: 'Uint8Array', - sharedArrayBuffer: true, + sharedArrayBuffer: window.SharedArrayBuffer, }, }); @@ -945,6 +980,8 @@ class SegmentationService extends PubSubService { // We should set it as active by default, as it created for display isActive: true, type: representationType, + FrameOfReferenceUID: + options?.FrameOfReferenceUID || displaySet.instances?.[0]?.FrameOfReferenceUID, representationData: { LABELMAP: { volumeId: segmentationId, @@ -972,14 +1009,13 @@ class SegmentationService extends PubSubService { toolGroupId: string, segmentationId: string, hydrateSegmentation = false, - representationType = csToolsEnums.SegmentationRepresentations.Labelmap + representationType = csToolsEnums.SegmentationRepresentations.Labelmap, + suppressEvents = false ): Promise => { const segmentation = this.getSegmentation(segmentationId); if (!segmentation) { - throw new Error( - `Segmentation with segmentationId ${segmentationId} not found.` - ); + throw new Error(`Segmentation with segmentationId ${segmentationId} not found.`); } if (hydrateSegmentation) { @@ -987,8 +1023,6 @@ class SegmentationService extends PubSubService { segmentation.hydrated = true; } - const { colorLUTIndex } = segmentation; - // Based on the segmentationId, set the colorLUTIndex. const segmentationRepresentationUIDs = await cstSegmentation.addSegmentationRepresentations( toolGroupId, @@ -1007,46 +1041,22 @@ class SegmentationService extends PubSubService { segmentationRepresentationUIDs[0] ); - cstSegmentation.config.color.setColorLUT( - toolGroupId, - segmentationRepresentationUIDs[0], - colorLUTIndex - ); - // add the segmentation segments properly for (const segment of segmentation.segments) { if (segment === null || segment === undefined) { continue; } - const { - segmentIndex, - color, - isLocked, - isVisible: visibility, - opacity, - } = segment; + const { segmentIndex, color, isLocked, isVisible: visibility, opacity } = segment; const suppressEvents = true; if (color !== undefined) { - this._setSegmentColor( - segmentationId, - segmentIndex, - color, - toolGroupId, - suppressEvents - ); + this._setSegmentColor(segmentationId, segmentIndex, color, toolGroupId, suppressEvents); } if (opacity !== undefined) { - this._setSegmentOpacity( - segmentationId, - segmentIndex, - opacity, - toolGroupId, - suppressEvents - ); + this._setSegmentOpacity(segmentationId, segmentIndex, opacity, toolGroupId, suppressEvents); } if (visibility !== undefined) { @@ -1059,18 +1069,19 @@ class SegmentationService extends PubSubService { ); } - if (isLocked !== undefined) { - this._setSegmentLocked( - segmentationId, - segmentIndex, - isLocked, - suppressEvents - ); + if (isLocked) { + this._setSegmentLocked(segmentationId, segmentIndex, isLocked, suppressEvents); } } + + if (!suppressEvents) { + this._broadcastEvent(this.EVENTS.SEGMENTATION_UPDATED, { + segmentation, + }); + } }; - public setSegmentRGBAColorForSegmentation = ( + public setSegmentRGBAColor = ( segmentationId: string, segmentIndex: number, rgbaColor, @@ -1102,29 +1113,22 @@ class SegmentationService extends PubSubService { }); }; - public getToolGroupIdsWithSegmentation = ( - segmentationId: string - ): string[] => { - const toolGroupIds = cstSegmentation.state.getToolGroupIdsWithSegmentation( - segmentationId - ); + public getToolGroupIdsWithSegmentation = (segmentationId: string): string[] => { + const toolGroupIds = cstSegmentation.state.getToolGroupIdsWithSegmentation(segmentationId); return toolGroupIds; }; - public hydrateSegmentation = ( - segmentationId: string, - suppressEvents = false - ): void => { + public hydrateSegmentation = (segmentationId: string, suppressEvents = false): void => { const segmentation = this.getSegmentation(segmentationId); if (!segmentation) { - throw new Error( - `Segmentation with segmentationId ${segmentationId} not found.` - ); + throw new Error(`Segmentation with segmentationId ${segmentationId} not found.`); } - segmentation.hydrated = true; + // Not all segmentations have dipslaysets, some of them are derived in the client + this._setDisplaySetIsHydrated(segmentationId, true); + if (!suppressEvents) { this._broadcastEvent(this.EVENTS.SEGMENTATION_UPDATED, { segmentation, @@ -1132,6 +1136,18 @@ class SegmentationService extends PubSubService { } }; + private _setDisplaySetIsHydrated(displaySetUID: string, isHydrated: boolean): void { + const { displaySetService } = this.servicesManager.services; + const displaySet = displaySetService.getDisplaySetByUID(displaySetUID); + + if (!displaySet) { + return; + } + + displaySet.isHydrated = isHydrated; + displaySetService.setDisplaySetMetadataInvalidated(displaySetUID, false); + } + private _highlightLabelmap( segmentIndex: number, alpha: number, @@ -1245,17 +1261,13 @@ class SegmentationService extends PubSubService { ): void { const uids = segmentationRepresentationUIDsIds || []; if (!uids.length) { - const representations = cstSegmentation.state.getSegmentationRepresentations( - toolGroupId - ); + const representations = cstSegmentation.state.getSegmentationRepresentations(toolGroupId); if (!representations || !representations.length) { return; } - uids.push( - ...representations.map(rep => rep.segmentationRepresentationUID) - ); + uids.push(...representations.map(rep => rep.segmentationRepresentationUID)); } cstSegmentation.removeSegmentationsFromToolGroup(toolGroupId, uids); @@ -1271,14 +1283,11 @@ class SegmentationService extends PubSubService { const wasActive = segmentation.isActive; if (!segmentationId || !segmentation) { - console.warn( - `No segmentationId provided, or unable to find segmentation by id.` - ); + console.warn(`No segmentationId provided, or unable to find segmentation by id.`); return; } const { colorLUTIndex } = segmentation; - this._removeSegmentationFromCornerstone(segmentationId); // Delete associated colormap @@ -1292,24 +1301,26 @@ class SegmentationService extends PubSubService { if (wasActive) { const remainingSegmentations = this._getSegmentations(); - if (remainingSegmentations.length) { - const { id } = remainingSegmentations[0]; + const remainingHydratedSegmentations = remainingSegmentations.filter( + segmentation => segmentation.hydrated + ); - this._setActiveSegmentationForToolGroup( - id, - this._getFirstToolGroupId(), - false - ); + if (remainingHydratedSegmentations.length) { + const { id } = remainingHydratedSegmentations[0]; + + this._setActiveSegmentationForToolGroup(id, this._getApplicableToolGroupId(), false); } } + this._setDisplaySetIsHydrated(segmentationId, false); + this._broadcastEvent(this.EVENTS.SEGMENTATION_REMOVED, { segmentationId, }); } public getConfiguration = (toolGroupId?: string): SegmentationConfig => { - toolGroupId = toolGroupId ?? this._getFirstToolGroupId(); + toolGroupId = toolGroupId ?? this._getApplicableToolGroupId(); const brushSize = 1; // const brushSize = cstUtils.segmentation.getBrushSizeForToolGroup( @@ -1321,9 +1332,8 @@ class SegmentationService extends PubSubService { // toolGroupId // ); - const segmentationRepresentations = this.getSegmentationRepresentationsForToolGroup( - toolGroupId - ); + const segmentationRepresentations = + this.getSegmentationRepresentationsForToolGroup(toolGroupId); const typeToUse = segmentationRepresentations?.[0]?.type || LABELMAP; @@ -1381,11 +1391,7 @@ class SegmentationService extends PubSubService { setConfigValueIfDefined('outlineOpacity', outlineOpacity, v => v / 100); setConfigValueIfDefined('fillAlpha', fillAlpha, v => v / 100); setConfigValueIfDefined('renderFill', renderFill); - setConfigValueIfDefined( - 'fillAlphaInactive', - fillAlphaInactive, - v => v / 100 - ); + setConfigValueIfDefined('fillAlphaInactive', fillAlphaInactive, v => v / 100); setConfigValueIfDefined('outlineOpacityInactive', fillAlphaInactive, v => Math.max(0.75, v / 100) ); @@ -1419,10 +1425,7 @@ class SegmentationService extends PubSubService { // }); // } - this._broadcastEvent( - this.EVENTS.SEGMENTATION_CONFIGURATION_CHANGED, - this.getConfiguration() - ); + this._broadcastEvent(this.EVENTS.SEGMENTATION_CONFIGURATION_CHANGED, this.getConfiguration()); }; public getLabelmapVolume = (segmentationId: string) => { @@ -1433,15 +1436,11 @@ class SegmentationService extends PubSubService { return cstSegmentation.state.getSegmentationRepresentations(toolGroupId); }; - public setSegmentLabelForSegmentation( - segmentationId: string, - segmentIndex: number, - label: string - ) { - this._setSegmentLabelForSegmentation(segmentationId, segmentIndex, label); + public setSegmentLabel(segmentationId: string, segmentIndex: number, label: string) { + this._setSegmentLabel(segmentationId, segmentIndex, label); } - private _setSegmentLabelForSegmentation( + private _setSegmentLabel( segmentationId: string, segmentIndex: number, label: string, @@ -1456,9 +1455,7 @@ class SegmentationService extends PubSubService { const segmentInfo = segmentation.segments[segmentIndex]; if (segmentInfo === undefined) { - throw new Error( - `Segment ${segmentIndex} not yet added to segmentation: ${segmentationId}` - ); + throw new Error(`Segment ${segmentIndex} not yet added to segmentation: ${segmentationId}`); } segmentInfo.label = label; @@ -1471,14 +1468,8 @@ class SegmentationService extends PubSubService { } } - public shouldRenderSegmentation( - viewportDisplaySetInstanceUIDs, - segDisplaySetInstanceUID - ) { - if ( - !viewportDisplaySetInstanceUIDs || - !viewportDisplaySetInstanceUIDs.length - ) { + public shouldRenderSegmentation(viewportDisplaySetInstanceUIDs, segmentationFrameOfReferenceUID) { + if (!viewportDisplaySetInstanceUIDs?.length) { return false; } @@ -1486,26 +1477,16 @@ class SegmentationService extends PubSubService { let shouldDisplaySeg = false; - const segDisplaySet = displaySetService.getDisplaySetByUID( - segDisplaySetInstanceUID - ); - - const segFrameOfReferenceUID = this._getFrameOfReferenceUIDForSeg( - segDisplaySet - ); - // check if the displaySet is sharing the same frameOfReferenceUID // with the new segmentation for (const displaySetInstanceUID of viewportDisplaySetInstanceUIDs) { - const displaySet = displaySetService.getDisplaySetByUID( - displaySetInstanceUID - ); + const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID); // Todo: this might not be ideal for use cases such as 4D, since we // don't want to show the segmentation for all the frames if ( displaySet.isReconstructable && - displaySet?.images?.[0]?.FrameOfReferenceUID === segFrameOfReferenceUID + displaySet?.images?.[0]?.FrameOfReferenceUID === segmentationFrameOfReferenceUID ) { shouldDisplaySeg = true; break; @@ -1527,7 +1508,6 @@ class SegmentationService extends PubSubService { segments: [], isVisible: true, isActive: false, - colorLUTIndex: 0, }; } @@ -1547,10 +1527,7 @@ class SegmentationService extends PubSubService { segmentation.isActive = segmentation.id === segmentationId; }); - const representation = this._getSegmentationRepresentation( - segmentationId, - toolGroupId - ); + const representation = this._getSegmentationRepresentation(segmentationId, toolGroupId); cstSegmentation.activeSegmentation.setActiveSegmentationRepresentation( toolGroupId, @@ -1564,16 +1541,11 @@ class SegmentationService extends PubSubService { } } - private _toggleSegmentationVisibility = ( - segmentationId: string, - suppressEvents = false - ) => { + private _toggleSegmentationVisibility = (segmentationId: string, suppressEvents = false) => { const segmentation = this.segmentations[segmentationId]; if (!segmentation) { - throw new Error( - `Segmentation with segmentationId ${segmentationId} not found.` - ); + throw new Error(`Segmentation with segmentationId ${segmentationId} not found.`); } segmentation.isVisible = !segmentation.isVisible; @@ -1587,21 +1559,14 @@ class SegmentationService extends PubSubService { } }; - private _setActiveSegment( - segmentationId: string, - segmentIndex: number, - suppressEvents = false - ) { + private _setActiveSegment(segmentationId: string, segmentIndex: number, suppressEvents = false) { const segmentation = this.getSegmentation(segmentationId); if (segmentation === undefined) { throw new Error(`no segmentation for segmentationId: ${segmentationId}`); } - cstSegmentation.segmentIndex.setActiveSegmentIndex( - segmentationId, - segmentIndex - ); + cstSegmentation.segmentIndex.setActiveSegmentIndex(segmentationId, segmentIndex); segmentation.activeSegmentIndex = segmentIndex; @@ -1625,8 +1590,7 @@ class SegmentationService extends PubSubService { } private _getVolumeIdForDisplaySet(displaySet) { - const volumeLoaderSchema = - displaySet.volumeLoaderSchema ?? VOLUME_LOADER_SCHEME; + const volumeLoaderSchema = displaySet.volumeLoaderSchema ?? VOLUME_LOADER_SCHEME; return `${volumeLoaderSchema}:${displaySet.displaySetInstanceUID}`; } @@ -1647,12 +1611,10 @@ class SegmentationService extends PubSubService { const segmentInfo = this._getSegmentInfo(segmentation, segmentIndex); if (segmentInfo === undefined) { - throw new Error( - `Segment ${segmentIndex} not yet added to segmentation: ${segmentationId}` - ); + throw new Error(`Segment ${segmentIndex} not yet added to segmentation: ${segmentationId}`); } - toolGroupId = toolGroupId ?? this._getFirstToolGroupId(); + toolGroupId = toolGroupId ?? this._getApplicableToolGroupId(); const segmentationRepresentation = this._getSegmentationRepresentation( segmentationId, @@ -1660,9 +1622,7 @@ class SegmentationService extends PubSubService { ); if (!segmentationRepresentation) { - throw new Error( - 'Must add representation to toolgroup before setting segments' - ); + throw new Error('Must add representation to toolgroup before setting segments'); } const { segmentationRepresentationUID } = segmentationRepresentation; @@ -1727,18 +1687,12 @@ class SegmentationService extends PubSubService { const segmentInfo = this._getSegmentInfo(segmentation, segmentIndex); if (segmentInfo === undefined) { - throw new Error( - `Segment ${segmentIndex} not yet added to segmentation: ${segmentationId}` - ); + throw new Error(`Segment ${segmentIndex} not yet added to segmentation: ${segmentationId}`); } segmentInfo.isLocked = isLocked; - cstSegmentation.segmentLocking.setSegmentIndexLocked( - segmentationId, - segmentIndex, - isLocked - ); + cstSegmentation.segmentLocking.setSegmentIndexLocked(segmentationId, segmentIndex, isLocked); if (suppressEvents === false) { this._broadcastEvent(this.EVENTS.SEGMENTATION_UPDATED, { @@ -1754,12 +1708,12 @@ class SegmentationService extends PubSubService { toolGroupId?: string, suppressEvents = false ) { - toolGroupId = toolGroupId ?? this._getFirstToolGroupId(); + toolGroupId = toolGroupId ?? this._getApplicableToolGroupId(); - const { - segmentationRepresentationUID, - segmentation, - } = this._getSegmentationInfo(segmentationId, toolGroupId); + const { segmentationRepresentationUID, segmentation } = this._getSegmentationInfo( + segmentationId, + toolGroupId + ); if (segmentation === undefined) { throw new Error(`no segmentation for segmentationId: ${segmentationId}`); @@ -1768,9 +1722,7 @@ class SegmentationService extends PubSubService { const segmentInfo = this._getSegmentInfo(segmentation, segmentIndex); if (segmentInfo === undefined) { - throw new Error( - `Segment ${segmentIndex} not yet added to segmentation: ${segmentationId}` - ); + throw new Error(`Segment ${segmentIndex} not yet added to segmentation: ${segmentationId}`); } segmentInfo.isVisible = isVisible; @@ -1812,12 +1764,10 @@ class SegmentationService extends PubSubService { const segmentInfo = this._getSegmentInfo(segmentation, segmentIndex); if (segmentInfo === undefined) { - throw new Error( - `Segment ${segmentIndex} not yet added to segmentation: ${segmentationId}` - ); + throw new Error(`Segment ${segmentIndex} not yet added to segmentation: ${segmentationId}`); } - toolGroupId = toolGroupId ?? this._getFirstToolGroupId(); + toolGroupId = toolGroupId ?? this._getApplicableToolGroupId(); const segmentationRepresentation = this._getSegmentationRepresentation( segmentationId, @@ -1825,9 +1775,7 @@ class SegmentationService extends PubSubService { ); if (!segmentationRepresentation) { - throw new Error( - 'Must add representation to toolgroup before setting segments' - ); + throw new Error('Must add representation to toolgroup before setting segments'); } const { segmentationRepresentationUID } = segmentationRepresentation; @@ -1868,9 +1816,7 @@ class SegmentationService extends PubSubService { const segmentInfo = this._getSegmentInfo(segmentation, segmentIndex); if (segmentInfo === undefined) { - throw new Error( - `Segment ${segmentIndex} not yet added to segmentation: ${segmentationId}` - ); + throw new Error(`Segment ${segmentIndex} not yet added to segmentation: ${segmentationId}`); } segmentInfo.label = segmentLabel; @@ -1883,11 +1829,10 @@ class SegmentationService extends PubSubService { } private _getSegmentationRepresentation(segmentationId, toolGroupId) { - const segmentationRepresentations = this.getSegmentationRepresentationsForToolGroup( - toolGroupId - ); + const segmentationRepresentations = + this.getSegmentationRepresentationsForToolGroup(toolGroupId); - if (segmentationRepresentations.length === 0) { + if (!segmentationRepresentations?.length) { return; } @@ -1958,21 +1903,13 @@ class SegmentationService extends PubSubService { return; } - const segmentationState = cstSegmentation.state.getSegmentation( - segmentationId - ); + const segmentationState = cstSegmentation.state.getSegmentation(segmentationId); if (!segmentationState) { return; } - const { - activeSegmentIndex, - cachedStats, - segmentsLocked, - label, - type, - } = segmentationState; + const { activeSegmentIndex, cachedStats, segmentsLocked, label, type } = segmentationState; if (![LABELMAP, CONTOUR].includes(type)) { throw new Error( @@ -2002,10 +1939,7 @@ class SegmentationService extends PubSubService { try { this.addOrUpdateSegmentation(segmentationSchema); } catch (error) { - console.warn( - `Failed to add/update segmentation ${segmentationId}`, - error - ); + console.warn(`Failed to add/update segmentation ${segmentationId}`, error); } }; @@ -2021,9 +1955,7 @@ class SegmentationService extends PubSubService { ); if (!segmentationRepresentation) { - throw new Error( - 'Must add representation to toolgroup before setting segments' - ); + throw new Error('Must add representation to toolgroup before setting segments'); } const { segmentationRepresentationUID } = segmentationRepresentation; @@ -2041,14 +1973,11 @@ class SegmentationService extends PubSubService { return; } - const toolGroupIds = segmentationState.getToolGroupIdsWithSegmentation( - segmentationId - ); + const toolGroupIds = segmentationState.getToolGroupIdsWithSegmentation(segmentationId); toolGroupIds.forEach(toolGroupId => { - const segmentationRepresentations = segmentationState.getSegmentationRepresentations( - toolGroupId - ); + const segmentationRepresentations = + segmentationState.getSegmentationRepresentations(toolGroupId); const UIDsToRemove = []; segmentationRepresentations.forEach(representation => { @@ -2073,17 +2002,12 @@ class SegmentationService extends PubSubService { } } - private _updateCornerstoneSegmentations({ - segmentationId, - notYetUpdatedAtSource, - }) { + private _updateCornerstoneSegmentations({ segmentationId, notYetUpdatedAtSource }) { if (notYetUpdatedAtSource === false) { return; } const segmentationState = cstSegmentation.state; - const sourceSegmentation = segmentationState.getSegmentation( - segmentationId - ); + const sourceSegmentation = segmentationState.getSegmentation(segmentationId); const segmentation = this.segmentations[segmentationId]; const { label, cachedStats } = segmentation; @@ -2099,14 +2023,11 @@ class SegmentationService extends PubSubService { private _updateCornerstoneSegmentationVisibility = segmentationId => { const segmentationState = cstSegmentation.state; - const toolGroupIds = segmentationState.getToolGroupIdsWithSegmentation( - segmentationId - ); + const toolGroupIds = segmentationState.getToolGroupIdsWithSegmentation(segmentationId); toolGroupIds.forEach(toolGroupId => { - const segmentationRepresentations = cstSegmentation.state.getSegmentationRepresentations( - toolGroupId - ); + const segmentationRepresentations = + cstSegmentation.state.getSegmentationRepresentations(toolGroupId); if (segmentationRepresentations.length === 0) { return; @@ -2130,10 +2051,7 @@ class SegmentationService extends PubSubService { ); // update segments visibility - const { segmentation } = this._getSegmentationInfo( - segmentationId, - toolGroupId - ); + const { segmentation } = this._getSegmentationInfo(segmentationId, toolGroupId); const segments = segmentation.segments.filter(Boolean); @@ -2145,9 +2063,7 @@ class SegmentationService extends PubSubService { private _getToolGroupIdsWithSegmentation(segmentationId: string) { const segmentationState = cstSegmentation.state; - const toolGroupIds = segmentationState.getToolGroupIdsWithSegmentation( - segmentationId - ); + const toolGroupIds = segmentationState.getToolGroupIdsWithSegmentation(segmentationId); return toolGroupIds; } @@ -2168,29 +2084,22 @@ class SegmentationService extends PubSubService { } } - private _getFirstToolGroupId = () => { - const { toolGroupService } = this.servicesManager.services; - const toolGroupIds = toolGroupService.getToolGroupIds(); + private _getApplicableToolGroupId = () => { + const { toolGroupService, viewportGridService, cornerstoneViewportService } = + this.servicesManager.services; - return toolGroupIds[0]; - }; + const viewportInfo = cornerstoneViewportService.getViewportInfo( + viewportGridService.getActiveViewportId() + ); - private getNextColorLUTIndex = (): number => { - let i = 0; - while (true) { - if (cstSegmentation.state.getColorLUT(i) === undefined) { - return i; - } + if (!viewportInfo) { + const toolGroupIds = toolGroupService.getToolGroupIds(); - i++; + return toolGroupIds[0]; } - }; - - private generateNewColorLUT() { - const newColorLUT = cloneDeep(COLOR_LUT); - return newColorLUT; - } + return viewportInfo.getToolGroupId(); + }; /** * Converts object of objects to array. diff --git a/extensions/cornerstone/src/services/SegmentationService/SegmentationServiceTypes.ts b/extensions/cornerstone/src/services/SegmentationService/SegmentationServiceTypes.ts index e170197c36f..7262b0e6d6f 100644 --- a/extensions/cornerstone/src/services/SegmentationService/SegmentationServiceTypes.ts +++ b/extensions/cornerstone/src/services/SegmentationService/SegmentationServiceTypes.ts @@ -39,6 +39,8 @@ type Segmentation = { isActive: boolean; // if the segmentation is visible in the viewer isVisible: boolean; + // the frame of reference UID of the segmentation + FrameOfReferenceUID: string; // the label of the segmentation label: string; // the number of segments in the segmentation @@ -65,4 +67,4 @@ type SegmentationRepresentationData = { LABELMAP?: LabelmapSegmentationData; }; -export { SegmentationConfig, Segment, Segmentation }; +export type { SegmentationConfig, Segment, Segmentation }; diff --git a/extensions/cornerstone/src/services/SyncGroupService/SyncGroupService.ts b/extensions/cornerstone/src/services/SyncGroupService/SyncGroupService.ts index 56a760d5fcb..f93075e1cf6 100644 --- a/extensions/cornerstone/src/services/SyncGroupService/SyncGroupService.ts +++ b/extensions/cornerstone/src/services/SyncGroupService/SyncGroupService.ts @@ -1,8 +1,4 @@ -import { - synchronizers, - SynchronizerManager, - Synchronizer, -} from '@cornerstonejs/tools'; +import { synchronizers, SynchronizerManager, Synchronizer } from '@cornerstonejs/tools'; import { pubSubServiceInterface, Types, ServicesManager } from '@ohif/core'; @@ -14,10 +10,7 @@ const EVENTS = { * @params options - are an optional set of options associated with the first * sync group declared. */ -export type SyncCreator = ( - id: string, - options?: Record -) => Synchronizer; +export type SyncCreator = (id: string, options?: Record) => Synchronizer; export type SyncGroup = { type: string; @@ -40,9 +33,7 @@ export default class SyncGroupService { static REGISTRATION = { name: 'syncGroupService', altName: 'SyncGroupService', - create: ({ - servicesManager, - }: Types.Extensions.ExtensionParams): SyncGroupService => { + create: ({ servicesManager }: Types.Extensions.ExtensionParams): SyncGroupService => { return new SyncGroupService(servicesManager); }, }; @@ -54,7 +45,7 @@ export default class SyncGroupService { [POSITION]: synchronizers.createCameraPositionSynchronizer, [VOI]: synchronizers.createVOISynchronizer, [ZOOMPAN]: synchronizers.createZoomPanSynchronizer, - [STACKIMAGE]: synchronizers.createStackImageSynchronizer, + [STACKIMAGE]: synchronizers.createImageSliceSynchronizer, }; constructor(serviceManager: ServicesManager) { @@ -65,11 +56,7 @@ export default class SyncGroupService { Object.assign(this, pubSubServiceInterface); } - private _createSynchronizer( - type: string, - id: string, - options - ): Synchronizer | undefined { + private _createSynchronizer(type: string, id: string, options): Synchronizer | undefined { const syncCreator = this.synchronizerCreators[type.toLowerCase()]; if (syncCreator) { return syncCreator(id, options); @@ -87,6 +74,10 @@ export default class SyncGroupService { this.synchronizerCreators[type.toLowerCase()] = creator; } + public getSynchronizer(id: string): Synchronizer | void { + return SynchronizerManager.getSynchronizer(id); + } + protected _getOrCreateSynchronizer( type: string, id: string, @@ -109,19 +100,11 @@ export default class SyncGroupService { return; } - const syncGroupsArray = Array.isArray(syncGroups) - ? syncGroups - : [syncGroups]; + const syncGroupsArray = Array.isArray(syncGroups) ? syncGroups : [syncGroups]; syncGroupsArray.forEach(syncGroup => { const syncGroupObj = asSyncGroup(syncGroup); - const { - type, - target = true, - source = true, - options = {}, - id = type, - } = syncGroupObj; + const { type, target = true, source = true, options = {}, id = type } = syncGroupObj; const synchronizer = this._getOrCreateSynchronizer(type, id, options); synchronizer.setOptions(viewportId, options); @@ -142,6 +125,17 @@ export default class SyncGroupService { SynchronizerManager.destroy(); } + public getSynchronizersForViewport( + viewportId: string, + renderingEngineId: string + ): Synchronizer[] { + return SynchronizerManager.getAllSynchronizers().filter( + s => + s.hasSourceViewport(renderingEngineId, viewportId) || + s.hasTargetViewport(renderingEngineId, viewportId) + ); + } + public removeViewportFromSyncGroup( viewportId: string, renderingEngineId: string, diff --git a/extensions/cornerstone/src/services/ToolGroupService/ToolGroupService.ts b/extensions/cornerstone/src/services/ToolGroupService/ToolGroupService.ts index b8f6c970985..3a85342b381 100644 --- a/extensions/cornerstone/src/services/ToolGroupService/ToolGroupService.ts +++ b/extensions/cornerstone/src/services/ToolGroupService/ToolGroupService.ts @@ -24,9 +24,7 @@ export default class ToolGroupService { public static REGISTRATION = { name: 'toolGroupService', altName: 'ToolGroupService', - create: ({ - servicesManager, - }: OhifTypes.Extensions.ExtensionParams): ToolGroupService => { + create: ({ servicesManager }: OhifTypes.Extensions.ExtensionParams): ToolGroupService => { return new ToolGroupService(servicesManager); }, }; @@ -40,10 +38,7 @@ export default class ToolGroupService { EVENTS: { [key: string]: string }; constructor(serviceManager) { - const { - cornerstoneViewportService, - viewportGridService, - } = serviceManager.services; + const { cornerstoneViewportService, viewportGridService } = serviceManager.services; this.cornerstoneViewportService = cornerstoneViewportService; this.viewportGridService = viewportGridService; this.listeners = {}; @@ -66,19 +61,14 @@ export default class ToolGroupService { if (!toolGroupIdToUse) { // Use the active viewport's tool group if no tool group id is provided - const enabledElement = getActiveViewportEnabledElement( - this.viewportGridService - ); + const enabledElement = getActiveViewportEnabledElement(this.viewportGridService); if (!enabledElement) { return; } const { renderingEngineId, viewportId } = enabledElement; - const toolGroup = ToolGroupManager.getToolGroupForViewport( - viewportId, - renderingEngineId - ); + const toolGroup = ToolGroupManager.getToolGroupForViewport(viewportId, renderingEngineId); if (!toolGroup) { console.warn( @@ -103,16 +93,13 @@ export default class ToolGroupService { public getToolGroupForViewport(viewportId: string): Types.IToolGroup | void { const renderingEngine = this.cornerstoneViewportService.getRenderingEngine(); - return ToolGroupManager.getToolGroupForViewport( - viewportId, - renderingEngine.id - ); + return ToolGroupManager.getToolGroupForViewport(viewportId, renderingEngine.id); } public getActiveToolForViewport(viewportId: string): string { - const toolGroup = ToolGroupManager.getToolGroupForViewport(viewportId); + const toolGroup = this.getToolGroupForViewport(viewportId); if (!toolGroup) { - return null; + return; } return toolGroup.getActivePrimaryMouseButtonTool(); @@ -133,10 +120,7 @@ export default class ToolGroupService { renderingEngineId: string, deleteToolGroupIfEmpty?: boolean ): void { - const toolGroup = ToolGroupManager.getToolGroupForViewport( - viewportId, - renderingEngineId - ); + const toolGroup = ToolGroupManager.getToolGroupForViewport(viewportId, renderingEngineId); if (!toolGroup) { return; @@ -193,24 +177,16 @@ export default class ToolGroupService { return toolGroup; } - public addToolsToToolGroup( - toolGroupId: string, - tools: Array, - configs: any = {} - ): void { + public addToolsToToolGroup(toolGroupId: string, tools: Array, configs: any = {}): void { const toolGroup = ToolGroupManager.getToolGroup(toolGroupId); // this.changeConfigurationIfNecessary(toolGroup, volumeId); this._addTools(toolGroup, tools, configs); this._setToolsMode(toolGroup, tools); } - public createToolGroupAndAddTools( - toolGroupId: string, - tools: Array, - configs: any = {} - ): Types.IToolGroup { + public createToolGroupAndAddTools(toolGroupId: string, tools: Array): Types.IToolGroup { const toolGroup = this.createToolGroup(toolGroupId); - this.addToolsToToolGroup(toolGroupId, tools, configs); + this.addToolsToToolGroup(toolGroupId, tools); return toolGroup; } @@ -259,34 +235,6 @@ export default class ToolGroupService { toolInstance.configuration = config; } - private _getToolNames(toolGroupTools: Tools): string[] { - const toolNames = []; - if (toolGroupTools.active) { - toolGroupTools.active.forEach(tool => { - toolNames.push(tool.toolName); - }); - } - if (toolGroupTools.passive) { - toolGroupTools.passive.forEach(tool => { - toolNames.push(tool.toolName); - }); - } - - if (toolGroupTools.enabled) { - toolGroupTools.enabled.forEach(tool => { - toolNames.push(tool.toolName); - }); - } - - if (toolGroupTools.disabled) { - toolGroupTools.disabled.forEach(tool => { - toolNames.push(tool.toolName); - }); - } - - return toolNames; - } - private _setToolsMode(toolGroup, tools) { const { active, passive, enabled, disabled } = tools; @@ -315,17 +263,33 @@ export default class ToolGroupService { } } - private _addTools(toolGroup, tools, configs) { - const toolNames = this._getToolNames(tools); - toolNames.forEach(toolName => { - // Initialize the toolConfig if no configuration is provided - const toolConfig = configs[toolName] ?? {}; + private _addTools(toolGroup, tools) { + const addTools = tools => { + tools.forEach(({ toolName, parentTool, configuration }) => { + if (parentTool) { + toolGroup.addToolInstance(toolName, parentTool, { + ...configuration, + }); + } else { + toolGroup.addTool(toolName, { ...configuration }); + } + }); + }; - // if (volumeUID) { - // toolConfig.volumeUID = volumeUID; - // } + if (tools.active) { + addTools(tools.active); + } - toolGroup.addTool(toolName, { ...toolConfig }); - }); + if (tools.passive) { + addTools(tools.passive); + } + + if (tools.enabled) { + addTools(tools.enabled); + } + + if (tools.disabled) { + addTools(tools.disabled); + } } } diff --git a/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts b/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts index 56ed36197e7..96050311c73 100644 --- a/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts +++ b/extensions/cornerstone/src/services/ViewportService/CornerstoneViewportService.ts @@ -9,41 +9,28 @@ import { VolumeViewport, VolumeViewport3D, cache, - utilities, - CONSTANTS, Enums as csEnums, + BaseVolumeViewport, } from '@cornerstonejs/core'; -import { - utilities as csToolsUtils, - Enums as csToolsEnums, -} from '@cornerstonejs/tools'; +import { utilities as csToolsUtils, Enums as csToolsEnums } from '@cornerstonejs/tools'; import { IViewportService } from './IViewportService'; import { RENDERING_ENGINE_ID } from './constants'; -import ViewportInfo, { - ViewportOptions, - DisplaySetOptions, - PublicViewportOptions, -} from './Viewport'; -import { - StackViewportData, - VolumeViewportData, -} from '../../types/CornerstoneCacheService'; -import { Presentation, Presentations } from '../../types/Presentation'; +import ViewportInfo, { DisplaySetOptions, PublicViewportOptions } from './Viewport'; +import { StackViewportData, VolumeViewportData } from '../../types/CornerstoneCacheService'; +import { LutPresentation, PositionPresentation, Presentations } from '../../types/Presentation'; import JumpPresets from '../../utils/JumpPresets'; const EVENTS = { - VIEWPORT_DATA_CHANGED: - 'event::cornerstoneViewportService:viewportDataChanged', + VIEWPORT_DATA_CHANGED: 'event::cornerstoneViewportService:viewportDataChanged', }; /** * Handles cornerstone viewport logic including enabling, disabling, and * updating the viewport. */ -class CornerstoneViewportService extends PubSubService - implements IViewportService { +class CornerstoneViewportService extends PubSubService implements IViewportService { static REGISTRATION = { name: 'cornerstoneViewportService', altName: 'CornerstoneViewportService', @@ -55,10 +42,10 @@ class CornerstoneViewportService extends PubSubService }; renderingEngine: Types.IRenderingEngine | null; - viewportsInfo: Map = new Map(); viewportsById: Map = new Map(); viewportGridResizeObserver: ResizeObserver | null; viewportsDisplaySets: Map = new Map(); + beforeResizePositionPresentations: Map = new Map(); // Some configs enableResizeDetector: true; @@ -66,6 +53,11 @@ class CornerstoneViewportService extends PubSubService resizeRefreshMode: 'debounce'; servicesManager = null; + resizeQueue = []; + viewportResizeTimer = null; + gridResizeDelay = 50; + gridResizeTimeOut = null; + constructor(servicesManager: ServicesManager) { super(EVENTS); this.renderingEngine = null; @@ -75,45 +67,17 @@ class CornerstoneViewportService extends PubSubService /** * Adds the HTML element to the viewportService - * @param {*} viewportIndex + * @param {*} viewportId * @param {*} elementRef */ - public enableViewport( - viewportIndex: number, - viewportOptions: PublicViewportOptions, - elementRef: HTMLDivElement - ): void { - // Use the provided viewportId - // Not providing a viewportId is frowned upon because it does weird things - // on moving them around, but it does mostly work. - if (!viewportOptions.viewportId) { - console.warn('Should provide viewport id externally', viewportOptions); - viewportOptions.viewportId = - this.getViewportId(viewportIndex) || `viewport-${viewportIndex}`; - } - const { viewportId } = viewportOptions; - const viewportInfo = new ViewportInfo(viewportIndex, viewportId); - if (!viewportInfo.viewportId) { - throw new Error('Should have viewport ID afterwards'); - } - + public enableViewport(viewportId: string, elementRef: HTMLDivElement): void { + const viewportInfo = new ViewportInfo(viewportId); viewportInfo.setElement(elementRef); - this.viewportsInfo.set(viewportIndex, viewportInfo); this.viewportsById.set(viewportId, viewportInfo); } public getViewportIds(): string[] { - const viewportIds = []; - - this.viewportsInfo.forEach(viewportInfo => { - viewportIds.push(viewportInfo.getViewportId()); - }); - - return viewportIds; - } - - public getViewportId(viewportIndex: number): string { - return this.viewportsInfo[viewportIndex]?.viewportId; + return Array.from(this.viewportsById.keys()); } /** @@ -137,14 +101,28 @@ class CornerstoneViewportService extends PubSubService } /** - * It triggers the resize on the rendering engine. + * It triggers the resize on the rendering engine, and renders the viewports + * + * @param isGridResize - if the resize is triggered by a grid resize + * this is used to avoid double resize of the viewports since if the + * grid is resized, all viewports will be resized so there is no need + * to resize them individually which will get triggered by their + * individual resize observers */ - public resize() { - const immediate = true; - const keepCamera = true; - - this.renderingEngine.resize(immediate, keepCamera); - this.renderingEngine.render(); + public resize(isGridResize = false) { + // if there is a grid resize happening, it means the viewport grid + // has been manipulated (e.g., panels closed, added, etc.) and we need + // to resize all viewports, so we will add a timeout here to make sure + // we don't double resize the viewports when viewports in the grid are + // resized individually + if (isGridResize) { + this.performResize(); + this.resetGridResizeTimeout(); + this.resizeQueue = []; + clearTimeout(this.viewportResizeTimer); + } else { + this.enqueueViewportResizeRequest(); + } } /** @@ -171,117 +149,293 @@ class CornerstoneViewportService extends PubSubService * created for every new viewport, this will be called whenever the set of * viewports is changed, but NOT when the viewport position changes only. * - * @param viewportIndex + * @param viewportId - The viewportId to disable */ - public disableElement(viewportIndex: number): void { - const viewportInfo = this.viewportsInfo.get(viewportIndex); - if (!viewportInfo) { + public disableElement(viewportId: string): void { + this.renderingEngine?.disableElement(viewportId); + + // clean up + this.viewportsById.delete(viewportId); + this.viewportsDisplaySets.delete(viewportId); + } + + /** + * Sets the presentations for a given viewport. Presentations is an object + * that can define the lut or position for a viewport. + * + * @param viewportId - The ID of the viewport. + * @param presentations - The presentations to apply to the viewport. + */ + public setPresentations(viewportId: string, presentations?: Presentations): void { + const viewport = this.getCornerstoneViewport(viewportId) as + | Types.IStackViewport + | Types.IVolumeViewport; + + if (!viewport) { return; } - const viewportId = viewportInfo.getViewportId(); + if (!presentations) { + return; + } - this.renderingEngine && this.renderingEngine.disableElement(viewportId); + const { lutPresentation, positionPresentation } = presentations; + if (lutPresentation) { + const { presentation } = lutPresentation; - this.viewportsInfo.get(viewportIndex).destroy(); - this.viewportsInfo.delete(viewportIndex); - this.viewportsById.delete(viewportId); + if (viewport instanceof BaseVolumeViewport) { + Object.entries(presentation).forEach( + ([volumeId, properties]: [string, Types.ViewportProperties]) => { + viewport.setProperties(properties, volumeId); + } + ); + } else { + viewport.setProperties(presentation); + } + } + + if (positionPresentation) { + const { viewPlaneNormal, viewUp, zoom, pan } = positionPresentation.presentation; + viewport.setCamera({ viewPlaneNormal, viewUp }); + + if (zoom !== undefined) { + viewport.setZoom(zoom); + } + + if (pan !== undefined) { + viewport.setPan(pan); + } + } } - public setPresentations(viewport, presentations?: Presentations): void { - const properties = presentations?.lutPresentation?.properties; - if (properties) { - viewport.setProperties(properties); + /** + * Retrieves the position presentation information for a given viewport. + * @param viewportId The ID of the viewport. + * @returns The position presentation object containing various properties + * such as ID, viewport type, initial image index, view plane normal, view up, zoom, and pan. + */ + public getPositionPresentation(viewportId: string): PositionPresentation { + const viewportInfo = this.viewportsById.get(viewportId); + if (!viewportInfo) { + return; + } + + const presentationIds = viewportInfo.getPresentationIds(); + + if (!presentationIds) { + return; } - const camera = presentations?.positionPresentation?.camera; - if (camera) { - viewport.setCamera(camera); + + const { positionPresentationId } = presentationIds; + + const csViewport = this.getCornerstoneViewport(viewportId); + if (!csViewport) { + return; } + + const { viewPlaneNormal, viewUp } = csViewport.getCamera(); + const initialImageIndex = csViewport.getCurrentImageIdIndex(); + const zoom = csViewport.getZoom(); + const pan = csViewport.getPan(); + + return { + id: positionPresentationId, + viewportType: viewportInfo.getViewportType(), + presentation: { + initialImageIndex, + viewUp, + viewPlaneNormal, + zoom, + pan, + }, + }; } - public getPresentation(viewportIndex: number): Presentation { - const viewportInfo = this.viewportsInfo.get(viewportIndex); + /** + * Retrieves the LUT (Lookup Table) presentation for a given viewport. + * @param viewportId The ID of the viewport. + * @returns The LUT presentation object, or undefined if the viewport does not exist. + */ + public getLutPresentation(viewportId: string): LutPresentation { + const viewportInfo = this.viewportsById.get(viewportId); if (!viewportInfo) { return; } - const { viewportType, presentationIds } = viewportInfo.getViewportOptions(); - const csViewport = this.getCornerstoneViewportByIndex(viewportIndex); + const presentationIds = viewportInfo.getPresentationIds(); + + if (!presentationIds) { + return; + } + + const { lutPresentationId } = presentationIds; + + const csViewport = this.getCornerstoneViewport(viewportId) as + | Types.IStackViewport + | Types.IVolumeViewport; + if (!csViewport) { return; } - const properties = csViewport.getProperties(); - if (properties.isComputedVOI) { - delete properties.voiRange; - delete properties.VOILUTFunction; + const cleanProperties = properties => { + if (properties.isComputedVOI) { + delete properties.voiRange; + delete properties.VOILUTFunction; + } + return properties; + }; + + const presentation = + csViewport instanceof BaseVolumeViewport + ? new Map() + : cleanProperties(csViewport.getProperties()); + + if (presentation instanceof Map) { + csViewport.getActors().forEach(({ uid: volumeId }) => { + const properties = cleanProperties(csViewport.getProperties(volumeId)); + presentation.set(volumeId, properties); + }); } - const initialImageIndex = csViewport.getCurrentImageIdIndex(); - const camera = csViewport.getCamera(); + + return { + id: lutPresentationId, + viewportType: viewportInfo.getViewportType(), + presentation, + }; + } + + /** + * Retrieves the presentations for a given viewport. + * @param viewportId - The ID of the viewport. + * @returns The presentations for the viewport. + */ + public getPresentations(viewportId: string): Presentations { + const viewportInfo = this.viewportsById.get(viewportId); + if (!viewportInfo) { + return; + } + + const positionPresentation = this.getPositionPresentation(viewportId); + const lutPresentation = this.getLutPresentation(viewportId); + return { - presentationIds, - viewportType: - !viewportType || viewportType === 'stack' ? 'stack' : 'volume', - properties, - initialImageIndex, - camera, + positionPresentation, + lutPresentation, }; } /** - * Uses the renderingEngine to enable the element for the given viewport index - * and sets the displaySet data to the viewport - * @param {*} viewportIndex - * @param {*} displaySet - * @param {*} dataSource - * @returns + * Stores the presentation state for a given viewport inside the + * stateSyncService. This is used to persist the presentation state + * across different scenarios e.g., when the viewport is changing the + * display set, or when the viewport is moving to a different layout. + * + * @param viewportId The ID of the viewport. + */ + public storePresentation({ viewportId }) { + let presentations = null as Presentations; + try { + presentations = this.getPresentations(viewportId); + if (!presentations?.positionPresentation && !presentations?.lutPresentation) { + return; + } + } catch (error) { + console.warn(error); + return; + } + + const { stateSyncService, syncGroupService } = this.servicesManager.services; + + const synchronizers = syncGroupService.getSynchronizersForViewport( + viewportId, + this.renderingEngine.id + ); + + const { positionPresentationStore, synchronizersStore, lutPresentationStore } = + stateSyncService.getState(); + + const { lutPresentation, positionPresentation } = presentations; + const { id: positionPresentationId } = positionPresentation; + const { id: lutPresentationId } = lutPresentation; + + const updateStore = (store, id, value) => ({ ...store, [id]: value }); + + const newState = {} as { [key: string]: any }; + + if (lutPresentationId) { + newState.lutPresentationStore = updateStore( + lutPresentationStore, + lutPresentationId, + lutPresentation + ); + } + + if (positionPresentationId) { + newState.positionPresentationStore = updateStore( + positionPresentationStore, + positionPresentationId, + positionPresentation + ); + } + + if (synchronizers?.length) { + newState.synchronizersStore = updateStore( + synchronizersStore, + viewportId, + synchronizers.map(synchronizer => ({ + id: synchronizer.id, + sourceViewports: [...synchronizer.getSourceViewports()], + targetViewports: [...synchronizer.getTargetViewports()], + })) + ); + } + + stateSyncService.store(newState); + } + + /** + * Sets the viewport data for a viewport. + * @param viewportId - The ID of the viewport to set the data for. + * @param viewportData - The viewport data to set. + * @param publicViewportOptions - The public viewport options. + * @param publicDisplaySetOptions - The public display set options. + * @param presentations - The presentations to set. */ public setViewportData( - viewportIndex: number, + viewportId: string, viewportData: StackViewportData | VolumeViewportData, publicViewportOptions: PublicViewportOptions, publicDisplaySetOptions: DisplaySetOptions[], presentations?: Presentations ): void { const renderingEngine = this.getRenderingEngine(); - const viewportId = - publicViewportOptions.viewportId || this.getViewportId(viewportIndex); - if (!viewportId) { - throw new Error('Must define viewportId externally'); - } + // This is the old viewportInfo, which may have old options but we might be + // using its viewport (same viewportId as the new viewportInfo) const viewportInfo = this.viewportsById.get(viewportId); - if (!viewportInfo) { - throw new Error('Viewport info not defined'); - } + // We should store the presentation for the current viewport since we can't only + // rely to store it WHEN the viewport is disabled since we might keep around the + // same viewport/element and just change the viewportData for it (drag and drop etc.) + // the disableElement storePresentation handle would not be called in this case + // and we would lose the presentation. + this.storePresentation({ viewportId: viewportInfo.getViewportId() }); - // If the viewport has moved index, then record the new index - if (viewportInfo.viewportIndex !== viewportIndex) { - this.viewportsInfo.delete(viewportInfo.viewportIndex); - this.viewportsInfo.set(viewportIndex, viewportInfo); - viewportInfo.viewportIndex = viewportIndex; + if (!viewportInfo) { + throw new Error('element is not enabled for the given viewportId'); } - viewportInfo.setRenderingEngineId(renderingEngine.id); - - const { - viewportOptions, - displaySetOptions, - } = this._getViewportAndDisplaySetOptions( - publicViewportOptions, - publicDisplaySetOptions, - viewportInfo - ); - - viewportInfo.setViewportOptions(viewportOptions); - viewportInfo.setDisplaySetOptions(displaySetOptions); - viewportInfo.setViewportData(viewportData); + // override the viewportOptions and displaySetOptions with the public ones + // since those are the newly set ones, we set them here so that it handles defaults + const displaySetOptions = viewportInfo.setPublicDisplaySetOptions(publicDisplaySetOptions); + const viewportOptions = viewportInfo.setPublicViewportOptions(publicViewportOptions); const element = viewportInfo.getElement(); const type = viewportInfo.getViewportType(); const background = viewportInfo.getBackground(); const orientation = viewportInfo.getOrientation(); + const displayArea = viewportInfo.getDisplayArea(); const viewportInput: Types.PublicViewportInput = { viewportId, @@ -290,38 +444,59 @@ class CornerstoneViewportService extends PubSubService defaultOptions: { background, orientation, + displayArea, }, }; + // Rendering Engine Id set should happen before enabling the element + // since there are callbacks that depend on the renderingEngine id + // Todo: however, this is a limitation which means that we can't change + // the rendering engine id for a given viewport which might be a super edge + // case + viewportInfo.setRenderingEngineId(renderingEngine.id); + // Todo: this is not optimal at all, we are re-enabling the already enabled // element which is not what we want. But enabledElement as part of the // renderingEngine is designed to be used like this. This will trigger // ENABLED_ELEMENT again and again, which will run onEnableElement callbacks renderingEngine.enableElement(viewportInput); + viewportInfo.setViewportOptions(viewportOptions); + viewportInfo.setDisplaySetOptions(displaySetOptions); + viewportInfo.setViewportData(viewportData); + viewportInfo.setViewportId(viewportId); + + this.viewportsById.set(viewportId, viewportInfo); + const viewport = renderingEngine.getViewport(viewportId); - this._setDisplaySets(viewport, viewportData, viewportInfo, presentations); + const displaySetPromise = this._setDisplaySets( + viewport, + viewportData, + viewportInfo, + presentations + ); // The broadcast event here ensures that listeners have a valid, up to date // viewport to access. Doing it too early can result in exceptions or // invalid data. - this._broadcastEvent(this.EVENTS.VIEWPORT_DATA_CHANGED, { - viewportData, - viewportIndex, - viewportId, + displaySetPromise.then(() => { + this._broadcastEvent(this.EVENTS.VIEWPORT_DATA_CHANGED, { + viewportData, + viewportId, + }); }); } - public getCornerstoneViewport( - viewportId: string - ): Types.IStackViewport | Types.IVolumeViewport | null { + /** + * Retrieves the Cornerstone viewport with the specified ID. + * + * @param viewportId - The ID of the viewport. + * @returns The Cornerstone viewport object if found, otherwise null. + */ + public getCornerstoneViewport(viewportId: string): Types.IViewport | null { const viewportInfo = this.getViewportInfo(viewportId); - if ( - !viewportInfo || - !this.renderingEngine || - this.renderingEngine.hasBeenDestroyed - ) { + if (!viewportInfo || !this.renderingEngine || this.renderingEngine.hasBeenDestroyed) { return null; } @@ -330,76 +505,68 @@ class CornerstoneViewportService extends PubSubService return viewport; } - public getCornerstoneViewportByIndex( - viewportIndex: number - ): Types.IStackViewport | Types.IVolumeViewport | null { - const viewportInfo = this.getViewportInfoByIndex(viewportIndex); - - if ( - !viewportInfo || - !this.renderingEngine || - this.renderingEngine.hasBeenDestroyed - ) { - return null; - } - - const viewport = this.renderingEngine.getViewport( - viewportInfo.getViewportId() - ); - - return viewport; - } - /** - * Returns the viewportIndex for the provided viewportId - * @param {string} viewportId - the viewportId - * @returns {number} - the viewportIndex + * Retrieves the viewport information for a given viewport ID. The viewport information + * is the OHIF construct that holds different options and data for a given viewport and + * is different from the cornerstone viewport. + * + * @param viewportId The ID of the viewport. + * @returns The viewport information. */ - public getViewportInfoByIndex(viewportIndex: number): ViewportInfo { - return this.viewportsInfo.get(viewportIndex); + public getViewportInfo(viewportId: string): ViewportInfo { + return this.viewportsById.get(viewportId); } - public getViewportInfo(viewportId: string): ViewportInfo { - // @ts-ignore - for (const [index, viewport] of this.viewportsInfo.entries()) { - if (viewport.getViewportId() === viewportId) { - return viewport; - } + /** + * Looks through the viewports to see if the specified measurement can be + * displayed in one of the viewports. + * + * @param measurement + * The measurement that is desired to view. + * @param activeViewportId - the index that was active at the time the jump + * was initiated. + * @return the viewportId that the measurement should be displayed in. + */ + public getViewportIdToJump( + activeViewportId: string, + displaySetInstanceUID: string, + cameraProps: unknown + ): string { + const viewportInfo = this.getViewportInfo(activeViewportId); + const { referencedImageId } = cameraProps; + if (viewportInfo?.contains(displaySetInstanceUID, referencedImageId)) { + return activeViewportId; } - return null; + + return ( + [...this.viewportsById.values()].find(viewportInfo => + viewportInfo.contains(displaySetInstanceUID, referencedImageId) + )?.viewportId ?? null + ); } - _setStackViewport( + private async _setStackViewport( viewport: Types.IStackViewport, viewportData: StackViewportData, viewportInfo: ViewportInfo, - presentations: Presentations - ): void { + presentations: Presentations = {} + ): Promise { const displaySetOptions = viewportInfo.getDisplaySetOptions(); - const { - imageIds, - initialImageIndex, - displaySetInstanceUID, - } = viewportData.data; + const { imageIds, initialImageIndex, displaySetInstanceUID } = viewportData.data; this.viewportsDisplaySets.set(viewport.id, [displaySetInstanceUID]); let initialImageIndexToUse = - presentations?.positionPresentation?.initialImageIndex ?? - initialImageIndex; + presentations?.positionPresentation?.initialImageIndex ?? initialImageIndex; - if ( - initialImageIndexToUse === undefined || - initialImageIndexToUse === null - ) { - initialImageIndexToUse = - this._getInitialImageIndexForViewport(viewportInfo, imageIds) || 0; + if (initialImageIndexToUse === undefined || initialImageIndexToUse === null) { + initialImageIndexToUse = this._getInitialImageIndexForViewport(viewportInfo, imageIds) || 0; } const properties = { ...presentations.lutPresentation?.properties }; if (!presentations.lutPresentation?.properties) { - const { voi, voiInverted } = displaySetOptions[0]; + const { voi, voiInverted, colormap } = displaySetOptions[0]; if (voi && (voi.windowWidth || voi.windowCenter)) { const { lower, upper } = csUtils.windowLevel.toLowHighRange( voi.windowWidth, @@ -411,14 +578,15 @@ class CornerstoneViewportService extends PubSubService if (voiInverted !== undefined) { properties.invert = voiInverted; } - } - viewport.setStack(imageIds, initialImageIndexToUse).then(() => { - viewport.setProperties(properties); - const camera = presentations.positionPresentation?.camera; - if (camera) { - viewport.setCamera(camera); + if (colormap !== undefined) { + properties.colormap = colormap; } + } + + return viewport.setStack(imageIds, initialImageIndexToUse).then(() => { + viewport.setProperties({ ...properties }); + this.setPresentations(viewport.id, presentations); }); } @@ -439,12 +607,8 @@ class CornerstoneViewportService extends PubSubService if (viewportType === csEnums.ViewportType.STACK) { numberOfSlices = imageIds.length; } else if (viewportType === csEnums.ViewportType.ORTHOGRAPHIC) { - const viewport = this.getCornerstoneViewport( - viewportInfo.getViewportId() - ); - const imageSliceData = csUtils.getImageSliceDataForVolumeViewport( - viewport - ); + const viewport = this.getCornerstoneViewport(viewportInfo.getViewportId()); + const imageSliceData = csUtils.getImageSliceDataForVolumeViewport(viewport); if (!imageSliceData) { return; @@ -458,11 +622,7 @@ class CornerstoneViewportService extends PubSubService return this._getInitialImageIndex(numberOfSlices, index, preset); } - _getInitialImageIndex( - numberOfSlices: number, - imageIndex?: number, - preset?: JumpPresets - ): number { + _getInitialImageIndex(numberOfSlices: number, imageIndex?: number, preset?: JumpPresets): number { const lastSliceIndex = numberOfSlices - 1; if (imageIndex !== undefined) { @@ -484,9 +644,7 @@ class CornerstoneViewportService extends PubSubService // it will jump to a different slice than the middle one which // was the initial slice, and we have some tools such as Crosshairs // which rely on a relative camera modifications and those will break. - return lastSliceIndex % 2 === 0 - ? lastSliceIndex / 2 - : (lastSliceIndex + 1) / 2; + return lastSliceIndex % 2 === 0 ? lastSliceIndex / 2 : (lastSliceIndex + 1) / 2; } return 0; @@ -496,7 +654,7 @@ class CornerstoneViewportService extends PubSubService viewport: Types.IVolumeViewport, viewportData: VolumeViewportData, viewportInfo: ViewportInfo, - presentations: Presentations + presentations: Presentations = {} ): Promise { // TODO: We need to overhaul the way data sources work so requests can be made // async. I think we should follow the image loader pattern which is async and @@ -541,10 +699,7 @@ class CornerstoneViewportService extends PubSubService this.viewportsDisplaySets.set(viewport.id, displaySetInstanceUIDs); - if ( - hangingProtocolService.hasCustomImageLoadStrategy() && - !hangingProtocolService.customImageLoadPerformed - ) { + if (hangingProtocolService.getShouldPerformCustomImageLoad()) { // delegate the volume loading to the hanging protocol service if it has a custom image load strategy return hangingProtocolService.runImageLoadStrategy({ viewportId: viewport.id, @@ -559,22 +714,11 @@ class CornerstoneViewportService extends PubSubService }); // This returns the async continuation only - return this.setVolumesForViewport( - viewport, - volumeInputArray, - presentations - ); + return this.setVolumesForViewport(viewport, volumeInputArray, presentations); } - public async setVolumesForViewport( - viewport, - volumeInputArray, - presentations - ) { - const { - displaySetService, - toolGroupService, - } = this.servicesManager.services; + public async setVolumesForViewport(viewport, volumeInputArray, presentations) { + const { displaySetService, toolGroupService } = this.servicesManager.services; const viewportInfo = this.getViewportInfo(viewport.id); const displaySetOptions = viewportInfo.getDisplaySetOptions(); @@ -614,7 +758,7 @@ class CornerstoneViewportService extends PubSubService viewport.setProperties(properties, volumeId); }); - this.setPresentations(viewport, presentations); + this.setPresentations(viewport.id, presentations); // load any secondary displaySets const displaySetInstanceUIDs = this.viewportsDisplaySets.get(viewport.id); @@ -632,10 +776,7 @@ class CornerstoneViewportService extends PubSubService // associated with the primary displaySet // get segmentations only returns the hydrated segmentations - this._addSegmentationRepresentationToToolGroupIfNecessary( - displaySetInstanceUIDs, - viewport - ); + this._addSegmentationRepresentationToToolGroupIfNecessary(displaySetInstanceUIDs, viewport); } const toolGroup = toolGroupService.getToolGroupForViewport(viewport.id); @@ -656,10 +797,7 @@ class CornerstoneViewportService extends PubSubService displaySetInstanceUIDs: string[], viewport: any ) { - const { - segmentationService, - toolGroupService, - } = this.servicesManager.services; + const { segmentationService, toolGroupService } = this.servicesManager.services; const toolGroup = toolGroupService.getToolGroupForViewport(viewport.id); @@ -668,9 +806,7 @@ class CornerstoneViewportService extends PubSubService for (const segmentation of segmentations) { const toolGroupSegmentationRepresentations = - segmentationService.getSegmentationRepresentationsForToolGroup( - toolGroup.id - ) || []; + segmentationService.getSegmentationRepresentationsForToolGroup(toolGroup.id) || []; // if there is already a segmentation representation for this segmentation // for this toolGroup, don't bother at all @@ -682,19 +818,28 @@ class CornerstoneViewportService extends PubSubService continue; } - // otherwise, check if the hydrated segmentations are in the same FOR + // otherwise, check if the hydrated segmentations are in the same FrameOfReferenceUID // as the primary displaySet, if so add the representation (since it was not there) - const { id: segDisplaySetInstanceUID, type } = segmentation; - const segFrameOfReferenceUID = this._getFrameOfReferenceUID( - segDisplaySetInstanceUID - ); + const { id: segDisplaySetInstanceUID } = segmentation; + let segFrameOfReferenceUID = this._getFrameOfReferenceUID(segDisplaySetInstanceUID); + + if (!segFrameOfReferenceUID) { + // if the segmentation displaySet does not have a FrameOfReferenceUID, we might check the + // segmentation itself maybe it has a FrameOfReferenceUID + const { FrameOfReferenceUID } = segmentation; + if (FrameOfReferenceUID) { + segFrameOfReferenceUID = FrameOfReferenceUID; + } + } + + if (!segFrameOfReferenceUID) { + return; + } let shouldDisplaySeg = false; for (const displaySetInstanceUID of displaySetInstanceUIDs) { - const primaryFrameOfReferenceUID = this._getFrameOfReferenceUID( - displaySetInstanceUID - ); + const primaryFrameOfReferenceUID = this._getFrameOfReferenceUID(displaySetInstanceUID); if (segFrameOfReferenceUID === primaryFrameOfReferenceUID) { shouldDisplaySeg = true; @@ -715,14 +860,8 @@ class CornerstoneViewportService extends PubSubService } } - private addOverlayRepresentationForDisplaySet( - displaySet: any, - viewport: any - ) { - const { - segmentationService, - toolGroupService, - } = this.servicesManager.services; + private addOverlayRepresentationForDisplaySet(displaySet: any, viewport: any) { + const { segmentationService, toolGroupService } = this.servicesManager.services; const { referencedVolumeId } = displaySet; const segmentationId = displaySet.displaySetInstanceUID; @@ -744,63 +883,59 @@ class CornerstoneViewportService extends PubSubService // Todo: keepCamera is an interim solution until we have a better solution for // keeping the camera position when the viewport data is changed - public updateViewport( - viewportIndex: number, - viewportData, - keepCamera = false - ) { - const viewportInfo = this.getViewportInfoByIndex(viewportIndex); - - const viewportId = viewportInfo.getViewportId(); + public updateViewport(viewportId: string, viewportData, keepCamera = false) { + const viewportInfo = this.getViewportInfo(viewportId); const viewport = this.getCornerstoneViewport(viewportId); const viewportCamera = viewport.getCamera(); - if ( - viewport instanceof VolumeViewport || - viewport instanceof VolumeViewport3D - ) { - this._setVolumeViewport(viewport, viewportData, viewportInfo).then(() => { + let displaySetPromise; + + if (viewport instanceof VolumeViewport || viewport instanceof VolumeViewport3D) { + displaySetPromise = this._setVolumeViewport(viewport, viewportData, viewportInfo).then(() => { if (keepCamera) { viewport.setCamera(viewportCamera); viewport.render(); } }); - - return; } if (viewport instanceof StackViewport) { - this._setStackViewport(viewport, viewportData, viewportInfo); - return; + displaySetPromise = this._setStackViewport(viewport, viewportData, viewportInfo); } + + displaySetPromise.then(() => { + this._broadcastEvent(this.EVENTS.VIEWPORT_DATA_CHANGED, { + viewportData, + viewportId, + }); + }); } _setDisplaySets( - viewport: StackViewport | VolumeViewport, + viewport: Types.IViewport, viewportData: StackViewportData | VolumeViewportData, viewportInfo: ViewportInfo, presentations: Presentations = {} - ): void { + ): Promise { if (viewport instanceof StackViewport) { - this._setStackViewport( + return this._setStackViewport( viewport, viewportData as StackViewportData, viewportInfo, presentations ); - } else if ( - viewport instanceof VolumeViewport || - viewport instanceof VolumeViewport3D - ) { - this._setVolumeViewport( - viewport, + } + + if ([VolumeViewport, VolumeViewport3D].some(type => viewport instanceof type)) { + return this._setVolumeViewport( + viewport as Types.IVolumeViewport, viewportData as VolumeViewportData, viewportInfo, presentations ); - } else { - throw new Error('Unknown viewport type'); } + + throw new Error('Unknown viewport type'); } /** @@ -814,10 +949,7 @@ class CornerstoneViewportService extends PubSubService _getSlabThickness(displaySetOptions, volumeId) { const { blendMode } = displaySetOptions; - if ( - blendMode === undefined || - displaySetOptions.slabThickness === undefined - ) { + if (blendMode === undefined || displaySetOptions.slabThickness === undefined) { return; } @@ -841,41 +973,9 @@ class CornerstoneViewportService extends PubSubService } } - _getViewportAndDisplaySetOptions( - publicViewportOptions: PublicViewportOptions, - publicDisplaySetOptions: DisplaySetOptions[], - viewportInfo: ViewportInfo - ): { - viewportOptions: ViewportOptions; - displaySetOptions: DisplaySetOptions[]; - } { - const viewportIndex = viewportInfo.getViewportIndex(); - - // Creating a temporary viewportInfo to handle defaults - const newViewportInfo = new ViewportInfo( - viewportIndex, - viewportInfo.getViewportId() - ); - - // To handle setting the default values if missing for the viewportOptions and - // displaySetOptions - newViewportInfo.setPublicViewportOptions(publicViewportOptions); - newViewportInfo.setPublicDisplaySetOptions(publicDisplaySetOptions); - - const newViewportOptions = newViewportInfo.getViewportOptions(); - const newDisplaySetOptions = newViewportInfo.getDisplaySetOptions(); - - return { - viewportOptions: newViewportOptions, - displaySetOptions: newDisplaySetOptions, - }; - } - _getFrameOfReferenceUID(displaySetInstanceUID) { const { displaySetService } = this.servicesManager.services; - const displaySet = displaySetService.getDisplaySetByUID( - displaySetInstanceUID - ); + const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID); if (!displaySet) { return; @@ -901,32 +1001,57 @@ class CornerstoneViewportService extends PubSubService } } - /** - * Looks through the viewports to see if the specified measurement can be - * displayed in one of the viewports. - * - * @param measurement - * The measurement that is desired to view. - * @param activeViewportIndex - the index that was active at the time the jump - * was initiated. - * @return the viewportIndex to display the given measurement - */ - public getViewportIndexToJump( - activeViewportIndex: number, - displaySetInstanceUID: string, - cameraProps: unknown - ): number { - const viewportInfo = this.viewportsInfo.get(activeViewportIndex); - const { referencedImageId } = cameraProps; - if (viewportInfo?.contains(displaySetInstanceUID, referencedImageId)) { - return activeViewportIndex; + private enqueueViewportResizeRequest() { + this.resizeQueue.push(false); // false indicates viewport resize + + clearTimeout(this.viewportResizeTimer); + this.viewportResizeTimer = setTimeout(() => { + this.processViewportResizeQueue(); + }, this.gridResizeDelay); + } + + private processViewportResizeQueue() { + const isGridResizeInQueue = this.resizeQueue.some(isGridResize => isGridResize); + if (this.resizeQueue.length > 0 && !isGridResizeInQueue && !this.gridResizeTimeOut) { + this.performResize(); } - return ( - [...this.viewportsById.values()].find(viewportInfo => - viewportInfo.contains(displaySetInstanceUID, referencedImageId) - )?.viewportIndex ?? -1 - ); + // Clear the queue after processing viewport resizes + this.resizeQueue = []; + } + + private performResize() { + const isImmediate = false; + + const viewports = this.getRenderingEngine().getViewports(); + + // Store the current position presentations for each viewport. + viewports.forEach(({ id }) => { + const presentation = this.getPositionPresentation(id); + this.beforeResizePositionPresentations.set(id, presentation); + }); + + // Resize the rendering engine and render. + const renderingEngine = this.renderingEngine; + renderingEngine.resize(isImmediate); + renderingEngine.render(); + + // Reset the camera for viewports that should reset their camera on resize, + // which means only those viewports that have a zoom level of 1. + this.beforeResizePositionPresentations.forEach((positionPresentation, viewportId) => { + this.setPresentations(viewportId, { positionPresentation }); + }); + + // Resize and render the rendering engine again. + renderingEngine.resize(isImmediate); + renderingEngine.render(); + } + + private resetGridResizeTimeout() { + clearTimeout(this.gridResizeTimeOut); + this.gridResizeTimeOut = setTimeout(() => { + this.gridResizeTimeOut = null; + }, this.gridResizeDelay); } } diff --git a/extensions/cornerstone/src/services/ViewportService/IViewportService.ts b/extensions/cornerstone/src/services/ViewportService/IViewportService.ts index 87168cb7f67..b16fa989f69 100644 --- a/extensions/cornerstone/src/services/ViewportService/IViewportService.ts +++ b/extensions/cornerstone/src/services/ViewportService/IViewportService.ts @@ -1,10 +1,7 @@ import { Types } from '@cornerstonejs/core'; -import { StackData, VolumeData } from '../../types/CornerstoneCacheService'; -import { - DisplaySetOptions, - PublicViewportOptions, - ViewportOptions, -} from './Viewport'; +import { StackViewportData, VolumeViewportData } from '../../types/CornerstoneCacheService'; +import { DisplaySetOptions, PublicViewportOptions } from './Viewport'; +import { Presentations } from '../../types/Presentation'; /** * Handles cornerstone viewport logic including enabling, disabling, and @@ -17,7 +14,6 @@ export interface IViewportService { viewportGridResizeObserver: unknown; viewportsInfo: unknown; sceneVolumeInputs: unknown; - viewportIndexUIDMap: unknown; viewportDivElements: unknown; ViewportPropertiesMap: unknown; volumeUIDs: unknown; @@ -28,14 +24,9 @@ export interface IViewportService { _broadcastEvent: unknown; /** * Adds the HTML element to the viewportService - * @param {*} viewportIndex * @param {*} elementRef */ - enableViewport( - viewportIndex: number, - viewportOptions: ViewportOptions, - elementRef: HTMLDivElement - ): void; + enableViewport(viewportId: string, elementRef: HTMLDivElement): void; /** * It retrieves the renderingEngine if it does exist, or creates one otherwise * @returns {RenderingEngine} rendering engine @@ -46,7 +37,7 @@ export interface IViewportService { * the element for resizing events * @param {*} elementRef */ - resize(element: HTMLDivElement): void; + resize(isGridResize: boolean): void; /** * Removes the viewport from cornerstone, and destroys the rendering engine */ @@ -54,21 +45,21 @@ export interface IViewportService { /** * Disables the viewport inside the renderingEngine, if no viewport is left * it destroys the renderingEngine. - * @param viewportIndex + * @param viewportId */ - disableElement(viewportIndex: number): void; + disableElement(viewportId: string): void; /** * Uses the renderingEngine to enable the element for the given viewport index * and sets the displaySet data to the viewport - * @param {*} viewportIndex * @param {*} displaySet * @param {*} dataSource * @returns */ setViewportData( - viewportIndex: number, - viewportData: StackData | VolumeData, + viewportId: string, + viewportData: StackViewportData | VolumeViewportData, publicViewportOptions: PublicViewportOptions, - publicDisplaySetOptions: DisplaySetOptions[] + publicDisplaySetOptions: DisplaySetOptions[], + presentations?: Presentations ): void; } diff --git a/extensions/cornerstone/src/services/ViewportService/Viewport.ts b/extensions/cornerstone/src/services/ViewportService/Viewport.ts index 75e976d4e06..2ec852492aa 100644 --- a/extensions/cornerstone/src/services/ViewportService/Viewport.ts +++ b/extensions/cornerstone/src/services/ViewportService/Viewport.ts @@ -1,9 +1,6 @@ import { Types, Enums } from '@cornerstonejs/core'; -import { Types as UITypes } from '@ohif/ui'; -import { - StackViewportData, - VolumeViewportData, -} from '../../types/CornerstoneCacheService'; +import { Types as CoreTypes } from '@ohif/core'; +import { StackViewportData, VolumeViewportData } from '../../types/CornerstoneCacheService'; import getCornerstoneBlendMode from '../../utils/getCornerstoneBlendMode'; import getCornerstoneOrientation from '../../utils/getCornerstoneOrientation'; import getCornerstoneViewportType from '../../utils/getCornerstoneViewportType'; @@ -21,9 +18,10 @@ export type ViewportOptions = { toolGroupId: string; viewportId: string; // Presentation ID to store/load presentation state from - presentationIds?: UITypes.PresentationIds; + presentationIds?: CoreTypes.PresentationIds; orientation?: Enums.OrientationAxis; background?: Types.Point3; + displayArea?: Types.DisplayArea; syncGroups?: SyncGroup[]; initialImageOptions?: InitialImageOptions; customViewportProps?: Record; @@ -38,10 +36,11 @@ export type PublicViewportOptions = { id?: string; viewportType?: string; toolGroupId?: string; - presentationIds?: UITypes.PresentationIds; + presentationIds?: CoreTypes.PresentationIds; viewportId?: string; orientation?: Enums.OrientationAxis; background?: Types.Point3; + displayArea?: Types.DisplayArea; syncGroups?: SyncGroup[]; initialImageOptions?: InitialImageOptions; customViewportProps?: Record; @@ -72,7 +71,7 @@ export type DisplaySetOptions = { voiInverted: boolean; blendMode?: Enums.BlendModes; slabThickness?: number; - colormap?: string; + colormap?: { name: string; opacity?: number }; displayPreset?: string; }; @@ -90,11 +89,7 @@ const DEFAULT_TOOLGROUP_ID = 'default'; // Return true if the data contains the given display set UID OR the imageId // if it is a composite object. -const dataContains = ( - data, - displaySetUID: string, - imageId?: string -): boolean => { +const dataContains = (data, displaySetUID: string, imageId?: string): boolean => { if (data.displaySetInstanceUID === displaySetUID) { return true; } @@ -106,15 +101,13 @@ const dataContains = ( class ViewportInfo { private viewportId = ''; - private viewportIndex: number; private element: HTMLDivElement; private viewportOptions: ViewportOptions; private displaySetOptions: Array; private viewportData: StackViewportData | VolumeViewportData; private renderingEngineId: string; - constructor(viewportIndex: number, viewportId: string) { - this.viewportIndex = viewportIndex; + constructor(viewportId: string) { this.viewportId = viewportId; this.setPublicViewportOptions({}); this.setPublicDisplaySetOptions([{}]); @@ -130,9 +123,7 @@ class ViewportInfo { } if (this.viewportData.data.length) { - return !!this.viewportData.data.find(data => - dataContains(data, displaySetUID, imageId) - ); + return !!this.viewportData.data.find(data => dataContains(data, displaySetUID, imageId)); } return dataContains(this.viewportData.data, displaySetUID, imageId); } @@ -155,17 +146,12 @@ class ViewportInfo { public setViewportId(viewportId: string): void { this.viewportId = viewportId; } - public setViewportIndex(viewportIndex: number): void { - this.viewportIndex = viewportIndex; - } public setElement(element: HTMLDivElement): void { this.element = element; } - public setViewportData( - viewportData: StackViewportData | VolumeViewportData - ): void { + public setViewportData(viewportData: StackViewportData | VolumeViewportData): void { this.viewportData = viewportData; } @@ -173,10 +159,6 @@ class ViewportInfo { return this.viewportData; } - public getViewportIndex(): number { - return this.viewportIndex; - } - public getElement(): HTMLDivElement { return this.element; } @@ -187,13 +169,13 @@ class ViewportInfo { public setPublicDisplaySetOptions( publicDisplaySetOptions: PublicDisplaySetOptions[] | DisplaySetSelector[] - ): void { + ): Array { // map the displaySetOptions and check if they are undefined then set them to default values - const displaySetOptions = this.mapDisplaySetOptions( - publicDisplaySetOptions - ); + const displaySetOptions = this.mapDisplaySetOptions(publicDisplaySetOptions); this.setDisplaySetOptions(displaySetOptions); + + return this.displaySetOptions; } public hasDisplaySet(displaySetInstanceUID: string): boolean { @@ -202,8 +184,9 @@ class ViewportInfo { // via cornerstoneViewportService let viewportData = this.getViewportData(); - if (viewportData.viewportType === Enums.ViewportType.ORTHOGRAPHIC || - viewportData.viewportType === Enums.ViewportType.VOLUME_3D + if ( + viewportData.viewportType === Enums.ViewportType.ORTHOGRAPHIC || + viewportData.viewportType === Enums.ViewportType.VOLUME_3D ) { viewportData = viewportData as VolumeViewportData; return viewportData.data.some( @@ -215,22 +198,15 @@ class ViewportInfo { return viewportData.data.displaySetInstanceUID === displaySetInstanceUID; } - public setPublicViewportOptions( - viewportOptionsEntry: PublicViewportOptions - ): void { + public setPublicViewportOptions(viewportOptionsEntry: PublicViewportOptions): ViewportOptions { let viewportType = viewportOptionsEntry.viewportType; - const { - toolGroupId = DEFAULT_TOOLGROUP_ID, - presentationIds, - } = viewportOptionsEntry; + const { toolGroupId = DEFAULT_TOOLGROUP_ID, presentationIds } = viewportOptionsEntry; let orientation; if (!viewportType) { viewportType = getCornerstoneViewportType(STACK); } else { - viewportType = getCornerstoneViewportType( - viewportOptionsEntry.viewportType - ); + viewportType = getCornerstoneViewportType(viewportOptionsEntry.viewportType); } // map SAGITTAL, AXIAL, CORONAL orientation to be used by cornerstone @@ -250,6 +226,8 @@ class ViewportInfo { toolGroupId, presentationIds, }); + + return this.viewportOptions; } public setViewportOptions(viewportOptions: ViewportOptions): void { @@ -260,9 +238,12 @@ class ViewportInfo { return this.viewportOptions; } - public setDisplaySetOptions( - displaySetOptions: Array - ): void { + public getPresentationIds(): CoreTypes.PresentationIds { + const { presentationIds } = this.viewportOptions; + return presentationIds; + } + + public setDisplaySetOptions(displaySetOptions: Array): void { this.displaySetOptions = displaySetOptions; } @@ -291,6 +272,10 @@ class ViewportInfo { return this.viewportOptions.orientation; } + public getDisplayArea(): Types.DisplayArea { + return this.viewportOptions.displayArea; + } + public getInitialImageOptions(): InitialImageOptions { return this.viewportOptions.initialImageOptions; } diff --git a/extensions/cornerstone/src/state.ts b/extensions/cornerstone/src/state.ts index e1d67a56159..76afcdbb8d6 100644 --- a/extensions/cornerstone/src/state.ts +++ b/extensions/cornerstone/src/state.ts @@ -9,26 +9,22 @@ const state = { * @param {HTMLElement} dom Active viewport element. * @return void */ -const setEnabledElement = ( - viewportIndex: number, - element: HTMLElement, - context?: string -): void => { +const setEnabledElement = (viewportId: string, element: HTMLElement, context?: string): void => { const targetContext = context || state.DEFAULT_CONTEXT; - state.enabledElements[viewportIndex] = { + state.enabledElements[viewportId] = { element, context: targetContext, }; }; /** - * Grabs the enabled element `dom` reference of an ative viewport. + * Grabs the enabled element `dom` reference of an active viewport. * * @return {HTMLElement} Active viewport element. */ -const getEnabledElement = viewportIndex => { - return state.enabledElements[viewportIndex]; +const getEnabledElement = viewportId => { + return state.enabledElements[viewportId]; }; const reset = () => { diff --git a/extensions/cornerstone/src/tools/CalibrationLineTool.ts b/extensions/cornerstone/src/tools/CalibrationLineTool.ts index 1db404034c5..c9e008fc61b 100644 --- a/extensions/cornerstone/src/tools/CalibrationLineTool.ts +++ b/extensions/cornerstone/src/tools/CalibrationLineTool.ts @@ -25,8 +25,7 @@ class CalibrationLineTool extends LengthTool { this._renderingViewport.worldToCanvas(p) ); // for display, round to 2 decimal points - const lengthPx = - Math.round(calculateLength2(canvasPoint1, canvasPoint2) * 100) / 100; + const lengthPx = Math.round(calculateLength2(canvasPoint1, canvasPoint2) * 100) / 100; const textLines = [`${lengthPx}px`]; @@ -64,35 +63,17 @@ export function onCompletedCalibrationLine(servicesManager, csToolsEvent) { const length = Math.round( - calculateLength3( - annotationData.handles.points[0], - annotationData.handles.points[1] - ) * 100 + calculateLength3(annotationData.handles.points[0], annotationData.handles.points[1]) * 100 ) / 100; - // calculate the currently applied pixel spacing on the viewport - const calibratedPixelSpacing = metaData.get( - 'calibratedPixelSpacing', - imageId - ); - const imagePlaneModule = metaData.get('imagePlaneModule', imageId); - const currentRowPixelSpacing = - calibratedPixelSpacing?.[0] || imagePlaneModule?.rowPixelSpacing || 1; - const currentColumnPixelSpacing = - calibratedPixelSpacing?.[1] || imagePlaneModule?.columnPixelSpacing || 1; - const adjustCalibration = newLength => { const spacingScale = newLength / length; - const rowSpacing = spacingScale * currentRowPixelSpacing; - const colSpacing = spacingScale * currentColumnPixelSpacing; // trigger resize of the viewport to adjust the world/pixel mapping - calibrateImageSpacing( - imageId, - viewport.getRenderingEngine(), - rowSpacing, - colSpacing - ); + calibrateImageSpacing(imageId, viewport.getRenderingEngine(), { + type: 'User', + scale: 1 / spacingScale, + }); }; return new Promise((resolve, reject) => { diff --git a/extensions/cornerstone/src/tools/ImageOverlayViewerTool.tsx b/extensions/cornerstone/src/tools/ImageOverlayViewerTool.tsx new file mode 100644 index 00000000000..c2ba56cbd52 --- /dev/null +++ b/extensions/cornerstone/src/tools/ImageOverlayViewerTool.tsx @@ -0,0 +1,269 @@ +import { VolumeViewport, metaData, utilities } from '@cornerstonejs/core'; +import { IStackViewport, IVolumeViewport, Point3 } from '@cornerstonejs/core/dist/esm/types'; +import { AnnotationDisplayTool, drawing } from '@cornerstonejs/tools'; +import { guid, b64toBlob } from '@ohif/core/src/utils'; +import OverlayPlaneModuleProvider from './OverlayPlaneModuleProvider'; + +interface CachedStat { + color: number[]; // [r, g, b, a] + overlays: { + // ...overlayPlaneModule + _id: string; + type: 'G' | 'R'; // G for Graphics, R for ROI + color?: number[]; // Rendered color [r, g, b, a] + dataUrl?: string; // Rendered image in Data URL expression + }[]; +} + +/** + * Image Overlay Viewer tool is not a traditional tool that requires user interactin. + * But it is used to display Pixel Overlays. And it will provide toggling capability. + * + * The documentation for Overlay Plane Module of DICOM can be found in [C.9.2 of + * Part-3 of DICOM standard](https://dicom.nema.org/medical/dicom/2018b/output/chtml/part03/sect_C.9.2.html) + * + * Image Overlay rendered by this tool can be toggled on and off using + * toolGroup.setToolEnabled() and toolGroup.setToolDisabled() + */ +class ImageOverlayViewerTool extends AnnotationDisplayTool { + static toolName = 'ImageOverlayViewer'; + + /** + * The overlay plane module provider add method is exposed here to be used + * when updating the overlay for this tool to use for displaying data. + */ + public static addOverlayPlaneModule = OverlayPlaneModuleProvider.add; + + constructor( + toolProps = {}, + defaultToolProps = { + supportedInteractionTypes: [], + configuration: { + fillColor: [255, 127, 127, 255], + }, + } + ) { + super(toolProps, defaultToolProps); + } + + onSetToolDisabled = (): void => {}; + + protected getReferencedImageId(viewport: IStackViewport | IVolumeViewport): string { + if (viewport instanceof VolumeViewport) { + return; + } + + const targetId = this.getTargetId(viewport); + return targetId.split('imageId:')[1]; + } + + renderAnnotation = (enabledElement, svgDrawingHelper) => { + const { viewport } = enabledElement; + + const imageId = this.getReferencedImageId(viewport); + if (!imageId) { + return; + } + + const overlayMetadata = metaData.get('overlayPlaneModule', imageId); + const overlays = overlayMetadata?.overlays; + + // no overlays + if (!overlays?.length) { + return; + } + + // Fix the x, y positions + overlays.forEach(overlay => { + overlay.x ||= 0; + overlay.y ||= 0; + }); + + // Will clear cached stat data when the overlay data changes + ImageOverlayViewerTool.addOverlayPlaneModule(imageId, overlayMetadata); + + this._getCachedStat(imageId, overlayMetadata, this.configuration.fillColor).then(cachedStat => { + cachedStat.overlays.forEach(overlay => { + this._renderOverlay(enabledElement, svgDrawingHelper, overlay); + }); + }); + + return true; + }; + + /** + * Render to DOM + * + * @param enabledElement + * @param svgDrawingHelper + * @param overlayData + * @returns + */ + private _renderOverlay(enabledElement, svgDrawingHelper, overlayData) { + const { viewport } = enabledElement; + const imageId = this.getReferencedImageId(viewport); + if (!imageId) { + return; + } + + // Decide the rendering position of the overlay image on the current canvas + const { _id, columns: width, rows: height, x, y } = overlayData; + const overlayTopLeftWorldPos = utilities.imageToWorldCoords(imageId, [ + x - 1, // Remind that top-left corner's (x, y) is be (1, 1) + y - 1, + ]); + const overlayTopLeftOnCanvas = viewport.worldToCanvas(overlayTopLeftWorldPos); + const overlayBottomRightWorldPos = utilities.imageToWorldCoords(imageId, [width, height]); + const overlayBottomRightOnCanvas = viewport.worldToCanvas(overlayBottomRightWorldPos); + + // add image to the annotations svg layer + const svgns = 'http://www.w3.org/2000/svg'; + const svgNodeHash = `image-overlay-${_id}`; + const existingImageElement = svgDrawingHelper.getSvgNode(svgNodeHash); + + const attributes = { + 'data-id': svgNodeHash, + width: overlayBottomRightOnCanvas[0] - overlayTopLeftOnCanvas[0], + height: overlayBottomRightOnCanvas[1] - overlayTopLeftOnCanvas[1], + x: overlayTopLeftOnCanvas[0], + y: overlayTopLeftOnCanvas[1], + href: overlayData.dataUrl, + }; + + if ( + isNaN(attributes.x) || + isNaN(attributes.y) || + isNaN(attributes.width) || + isNaN(attributes.height) + ) { + console.warn('Invalid rendering attribute for image overlay', attributes['data-id']); + return false; + } + + if (existingImageElement) { + drawing.setAttributesIfNecessary(attributes, existingImageElement); + svgDrawingHelper.setNodeTouched(svgNodeHash); + } else { + const newImageElement = document.createElementNS(svgns, 'image'); + drawing.setNewAttributesIfValid(attributes, newImageElement); + svgDrawingHelper.appendNode(newImageElement, svgNodeHash); + } + return true; + } + + private async _getCachedStat( + imageId: string, + overlayMetadata, + color: number[] + ): Promise { + const missingOverlay = overlayMetadata.overlays.filter( + overlay => overlay.pixelData && !overlay.dataUrl + ); + if (missingOverlay.length === 0) { + return overlayMetadata; + } + + const overlays = await Promise.all( + overlayMetadata.overlays + .filter(overlay => overlay.pixelData) + .map(async (overlay, idx) => { + let pixelData = null; + if (overlay.pixelData.Value) { + pixelData = overlay.pixelData.Value; + } else if (overlay.pixelData instanceof Array) { + pixelData = overlay.pixelData[0]; + } else if (overlay.pixelData.retrieveBulkData) { + pixelData = await overlay.pixelData.retrieveBulkData(); + } else if (overlay.pixelData.InlineBinary) { + const blob = b64toBlob(overlay.pixelData.InlineBinary); + const arrayBuffer = await blob.arrayBuffer(); + pixelData = arrayBuffer; + } + + if (!pixelData) { + return; + } + + const dataUrl = this._renderOverlayToDataUrl( + { width: overlay.columns, height: overlay.rows }, + overlay.color || color, + pixelData + ); + + return { + ...overlay, + _id: guid(), + dataUrl, // this will be a data url expression of the rendered image + color, + }; + }) + ); + overlayMetadata.overlays = overlays; + + return overlayMetadata; + } + + /** + * compare two RGBA expression of colors. + * + * @param color1 + * @param color2 + * @returns + */ + private _isSameColor(color1: number[], color2: number[]) { + return ( + color1 && + color2 && + color1[0] === color2[0] && + color1[1] === color2[1] && + color1[2] === color2[2] && + color1[3] === color2[3] + ); + } + + /** + * pixelData of overlayPlane module is an array of bits corresponding + * to each of the underlying pixels of the image. + * Let's create pixel data from bit array of overlay data + * + * @param pixelDataRaw + * @param color + * @returns + */ + private _renderOverlayToDataUrl({ width, height }, color, pixelDataRaw) { + const pixelDataView = new DataView(pixelDataRaw); + const totalBits = width * height; + + const canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = height; + + const ctx = canvas.getContext('2d'); + ctx.clearRect(0, 0, width, height); // make it transparent + ctx.globalCompositeOperation = 'copy'; + + const imageData = ctx.getImageData(0, 0, width, height); + const data = imageData.data; + for (let i = 0, bitIdx = 0, byteIdx = 0; i < totalBits; i++) { + if (pixelDataView.getUint8(byteIdx) & (1 << bitIdx)) { + data[i * 4] = color[0]; + data[i * 4 + 1] = color[1]; + data[i * 4 + 2] = color[2]; + data[i * 4 + 3] = color[3]; + } + + // next bit, byte + if (bitIdx >= 7) { + bitIdx = 0; + byteIdx++; + } else { + bitIdx++; + } + } + ctx.putImageData(imageData, 0, 0); + + return canvas.toDataURL(); + } +} + +export default ImageOverlayViewerTool; diff --git a/extensions/cornerstone/src/tools/OverlayPlaneModuleProvider.ts b/extensions/cornerstone/src/tools/OverlayPlaneModuleProvider.ts new file mode 100644 index 00000000000..d36b5437b41 --- /dev/null +++ b/extensions/cornerstone/src/tools/OverlayPlaneModuleProvider.ts @@ -0,0 +1,40 @@ +import { metaData } from '@cornerstonejs/core'; + +const _cachedOverlayMetadata: Map = new Map(); + +/** + * Image Overlay Viewer tool is not a traditional tool that requires user interactin. + * But it is used to display Pixel Overlays. And it will provide toggling capability. + * + * The documentation for Overlay Plane Module of DICOM can be found in [C.9.2 of + * Part-3 of DICOM standard](https://dicom.nema.org/medical/dicom/2018b/output/chtml/part03/sect_C.9.2.html) + * + * Image Overlay rendered by this tool can be toggled on and off using + * toolGroup.setToolEnabled() and toolGroup.setToolDisabled() + */ +const OverlayPlaneModuleProvider = { + /** Adds the metadata for overlayPlaneModule */ + add: (imageId, metadata) => { + if (_cachedOverlayMetadata.get(imageId) === metadata) { + // This is a no-op here as the tool re-caches the data + return; + } + _cachedOverlayMetadata.set(imageId, metadata); + }, + + /** Standard getter for metadata */ + get: (type: string, query: string | string[]) => { + if (Array.isArray(query)) { + return; + } + if (type !== 'overlayPlaneModule') { + return; + } + return _cachedOverlayMetadata.get(query); + }, +}; + +// Needs to be higher priority than default provider +metaData.addProvider(OverlayPlaneModuleProvider.get, 10_000); + +export default OverlayPlaneModuleProvider; diff --git a/extensions/cornerstone/src/types/Presentation.ts b/extensions/cornerstone/src/types/Presentation.ts index 82f13f74510..57737cc44dd 100644 --- a/extensions/cornerstone/src/types/Presentation.ts +++ b/extensions/cornerstone/src/types/Presentation.ts @@ -1,23 +1,45 @@ -/** Store presentation data for either stack viewports or volume viewports */ -import { Types } from '@cornerstonejs/core'; -import { Types as UITypes } from '@ohif/ui'; +import type { Types } from '@cornerstonejs/core'; /** - * Has information on the presentation of the viewport. + * Represents a position presentation in a viewport. This is basically + * viewport specific camera position and zoom, and not the display set */ -export interface Presentation extends Types.StackViewportProperties { - presentationIds: UITypes.PresentationIds; +export type PositionPresentation = { + id: string; viewportType: string; - initialImageIndex: number; - camera: Types.ICamera; - properties: Types.StackViewportProperties | Types.VolumeViewportProperties; - zoom?: number; - pan?: [number, number]; + presentation: { + initialImageIndex: number; + viewUp: Types.Point3; + viewPlaneNormal: Types.Point3; + zoom?: number; + pan?: Types.Point2; + }; +}; + +/** + * Represents a LUT presentation in a viewport, and is really related + * to displaySets and not the viewport itself. So that is why it can + * be an object with volumeId keys, or a single object with the properties + * itself + */ +export interface LutPresentation { + id: string; + viewportType: string; + presentation: Record | Types.ViewportProperties; } +/** + * Presentation can be a PositionPresentation or a LutPresentation. + */ +type Presentation = PositionPresentation | LutPresentation; + +/** + * Viewport presentations object that can contain a positionPresentation + * and or a lutPresentation. + */ export type Presentations = { - positionPresentation?: Presentation; - lutPresentation?: Presentation; + positionPresentation?: PositionPresentation; + lutPresentation?: LutPresentation; }; export default Presentation; diff --git a/extensions/cornerstone/src/types/index.ts b/extensions/cornerstone/src/types/index.ts index 6c729817d6e..c8db85fb761 100644 --- a/extensions/cornerstone/src/types/index.ts +++ b/extensions/cornerstone/src/types/index.ts @@ -1,7 +1,4 @@ -import * as CornerstoneCacheService from './CornerstoneCacheService' +import * as CornerstoneCacheService from './CornerstoneCacheService'; import CornerstoneServices from './CornerstoneServices'; -export type { - CornerstoneCacheService, - CornerstoneServices, -} +export type { CornerstoneCacheService, CornerstoneServices }; diff --git a/extensions/cornerstone/src/utils/CornerstoneViewportDownloadForm.tsx b/extensions/cornerstone/src/utils/CornerstoneViewportDownloadForm.tsx index cfab216e789..ca0de98b79c 100644 --- a/extensions/cornerstone/src/utils/CornerstoneViewportDownloadForm.tsx +++ b/extensions/cornerstone/src/utils/CornerstoneViewportDownloadForm.tsx @@ -20,38 +20,29 @@ const VIEWPORT_ID = 'cornerstone-viewport-download-form'; const CornerstoneViewportDownloadForm = ({ onClose, - activeViewportIndex, + activeViewportId: activeViewportIdProp, cornerstoneViewportService, }) => { - const enabledElement = OHIFgetEnabledElement(activeViewportIndex); + const enabledElement = OHIFgetEnabledElement(activeViewportIdProp); const activeViewportElement = enabledElement?.element; const activeViewportEnabledElement = getEnabledElement(activeViewportElement); - const { - viewportId: activeViewportId, - renderingEngineId, - } = activeViewportEnabledElement; + const { viewportId: activeViewportId, renderingEngineId } = activeViewportEnabledElement; - const toolGroup = ToolGroupManager.getToolGroupForViewport( - activeViewportId, - renderingEngineId - ); + const toolGroup = ToolGroupManager.getToolGroupForViewport(activeViewportId, renderingEngineId); - const toolModeAndBindings = Object.keys(toolGroup.toolOptions).reduce( - (acc, toolName) => { - const tool = toolGroup.toolOptions[toolName]; - const { mode, bindings } = tool; + const toolModeAndBindings = Object.keys(toolGroup.toolOptions).reduce((acc, toolName) => { + const tool = toolGroup.toolOptions[toolName]; + const { mode, bindings } = tool; - return { - ...acc, - [toolName]: { - mode, - bindings, - }, - }; - }, - {} - ); + return { + ...acc, + [toolName]: { + mode, + bindings, + }, + }; + }, {}); useEffect(() => { return () => { @@ -64,9 +55,7 @@ const CornerstoneViewportDownloadForm = ({ const enableViewport = viewportElement => { if (viewportElement) { - const { renderingEngine, viewport } = getEnabledElement( - activeViewportElement - ); + const { renderingEngine, viewport } = getEnabledElement(activeViewportElement); const viewportInput = { viewportId: VIEWPORT_ID, @@ -91,11 +80,7 @@ const CornerstoneViewportDownloadForm = ({ } }; - const updateViewportPreview = ( - downloadViewportElement, - internalCanvas, - fileType - ) => + const updateViewportPreview = (downloadViewportElement, internalCanvas, fileType) => new Promise(resolve => { const enabledElement = getEnabledElement(downloadViewportElement); @@ -108,6 +93,7 @@ const CornerstoneViewportDownloadForm = ({ renderingEngine.resize(); // Trigger the render on the viewport to update the on screen + downloadViewport.resetCamera(); downloadViewport.render(); downloadViewportElement.addEventListener( @@ -133,10 +119,7 @@ const CornerstoneViewportDownloadForm = ({ resolve({ dataUrl, width: newWidth, height: newHeight }); - downloadViewportElement.removeEventListener( - Enums.Events.IMAGE_RENDERED, - updateViewport - ); + downloadViewportElement.removeEventListener(Enums.Events.IMAGE_RENDERED, updateViewport); } ); }); @@ -144,9 +127,7 @@ const CornerstoneViewportDownloadForm = ({ const loadImage = (activeViewportElement, viewportElement, width, height) => new Promise(resolve => { if (activeViewportElement && viewportElement) { - const activeViewportEnabledElement = getEnabledElement( - activeViewportElement - ); + const activeViewportEnabledElement = getEnabledElement(activeViewportElement); if (!activeViewportEnabledElement) { return; @@ -162,15 +143,16 @@ const CornerstoneViewportDownloadForm = ({ const properties = viewport.getProperties(); downloadViewport.setStack([imageId]).then(() => { - downloadViewport.setProperties(properties); - - const newWidth = Math.min(width || image.width, MAX_TEXTURE_SIZE); - const newHeight = Math.min( - height || image.height, - MAX_TEXTURE_SIZE - ); - - resolve({ width: newWidth, height: newHeight }); + try { + downloadViewport.setProperties(properties); + const newWidth = Math.min(width || image.width, MAX_TEXTURE_SIZE); + const newHeight = Math.min(height || image.height, MAX_TEXTURE_SIZE); + + resolve({ width: newWidth, height: newHeight }); + } catch (e) { + // Happens on clicking the cancel button + console.warn('Unable to set properties', e); + } }); } else if (downloadViewport instanceof VolumeViewport) { const actors = viewport.getActors(); @@ -190,31 +172,19 @@ const CornerstoneViewportDownloadForm = ({ } }); - const toggleAnnotations = ( - toggle, - viewportElement, - activeViewportElement - ) => { - const activeViewportEnabledElement = getEnabledElement( - activeViewportElement - ); + const toggleAnnotations = (toggle, viewportElement, activeViewportElement) => { + const activeViewportEnabledElement = getEnabledElement(activeViewportElement); const downloadViewportElement = getEnabledElement(viewportElement); - const { - viewportId: activeViewportId, - renderingEngineId, - } = activeViewportEnabledElement; + const { viewportId: activeViewportId, renderingEngineId } = activeViewportEnabledElement; const { viewportId: downloadViewportId } = downloadViewportElement; if (!activeViewportEnabledElement || !downloadViewportElement) { return; } - const toolGroup = ToolGroupManager.getToolGroupForViewport( - activeViewportId, - renderingEngineId - ); + const toolGroup = ToolGroupManager.getToolGroupForViewport(activeViewportId, renderingEngineId); // add the viewport to the toolGroup toolGroup.addViewport(downloadViewportId, renderingEngineId); @@ -254,7 +224,6 @@ const CornerstoneViewportDownloadForm = ({ minimumSize={MINIMUM_SIZE} maximumSize={MAX_TEXTURE_SIZE} defaultSize={DEFAULT_SIZE} - canvasClass={'cornerstone-canvas'} activeViewportElement={activeViewportElement} enableViewport={enableViewport} disableViewport={disableViewport} @@ -268,7 +237,7 @@ const CornerstoneViewportDownloadForm = ({ CornerstoneViewportDownloadForm.propTypes = { onClose: PropTypes.func, - activeViewportIndex: PropTypes.number.isRequired, + activeViewportId: PropTypes.string.isRequired, }; export default CornerstoneViewportDownloadForm; diff --git a/extensions/cornerstone/src/utils/DicomFileUploader.ts b/extensions/cornerstone/src/utils/DicomFileUploader.ts index 45a6c9e94a6..cfbad0745b1 100644 --- a/extensions/cornerstone/src/utils/DicomFileUploader.ts +++ b/extensions/cornerstone/src/utils/DicomFileUploader.ts @@ -98,22 +98,13 @@ export default class DicomFileUploader extends PubSubService { }); }, timeout: () => { - this._reject( - reject, - new UploadRejection(UploadStatus.Failed, 'The request timed out.') - ); + this._reject(reject, new UploadRejection(UploadStatus.Failed, 'The request timed out.')); }, abort: () => { - this._reject( - reject, - new UploadRejection(UploadStatus.Cancelled, 'Cancelled') - ); + this._reject(reject, new UploadRejection(UploadStatus.Cancelled, 'Cancelled')); }, error: () => { - this._reject( - reject, - new UploadRejection(UploadStatus.Failed, 'The request failed.') - ); + this._reject(reject, new UploadRejection(UploadStatus.Failed, 'The request failed.')); }, }; @@ -122,10 +113,7 @@ export default class DicomFileUploader extends PubSubService { .loadFileRequest(this._fileId) .then(dicomFile => { if (this._abortController.signal.aborted) { - this._reject( - reject, - new UploadRejection(UploadStatus.Cancelled, 'Cancelled') - ); + this._reject(reject, new UploadRejection(UploadStatus.Cancelled, 'Cancelled')); return; } @@ -133,10 +121,7 @@ export default class DicomFileUploader extends PubSubService { // The file is not DICOM this._reject( reject, - new UploadRejection( - UploadStatus.Failed, - 'Not a valid DICOM file.' - ) + new UploadRejection(UploadStatus.Failed, 'Not a valid DICOM file.') ); return; } @@ -164,10 +149,7 @@ export default class DicomFileUploader extends PubSubService { } private _isRejected(): boolean { - return ( - this._status === UploadStatus.Failed || - this._status === UploadStatus.Cancelled - ); + return this._status === UploadStatus.Failed || this._status === UploadStatus.Cancelled; } private _reject(reject: (reason?: any) => void, reason: any) { diff --git a/extensions/cornerstone/src/utils/callInputDialog.tsx b/extensions/cornerstone/src/utils/callInputDialog.tsx index dc35d62665a..37d5021c509 100644 --- a/extensions/cornerstone/src/utils/callInputDialog.tsx +++ b/extensions/cornerstone/src/utils/callInputDialog.tsx @@ -21,11 +21,7 @@ function callInputDialog( dialogConfig: any = {} ) { const dialogId = 'dialog-enter-annotation'; - const label = data - ? isArrowAnnotateInputDialog - ? data.text - : data.label - : ''; + const label = data ? (isArrowAnnotateInputDialog ? data.text : data.label) : ''; const { dialogTitle = 'Annotation', inputLabel = 'Enter your annotation', @@ -69,7 +65,7 @@ function callInputDialog( return ( { return; } - return typeof imageObj.getImageId === 'function' - ? imageObj.getImageId() - : imageObj.url; + return typeof imageObj.getImageId === 'function' ? imageObj.getImageId() : imageObj.url; }; const findImageIdOnStudies = (studies, displaySetInstanceUID) => { @@ -99,7 +97,7 @@ class DicomLoaderService { if ( (!imageInstance && !nonImageInstance) || - !nonImageInstance.imageId.startsWith('dicomfile') + !nonImageInstance.imageId?.startsWith('dicomfile') ) { return; } @@ -163,9 +161,7 @@ class DicomLoaderService { getDicomDataMethod = fetchIt.bind(this, imageId); break; default: - throw new Error( - `Unsupported image type: ${loaderType} for imageId: ${imageId}` - ); + throw new Error(`Unsupported image type: ${loaderType} for imageId: ${imageId}`); } return getDicomDataMethod(); @@ -180,6 +176,7 @@ class DicomLoaderService { authorizationHeaders, wadoRoot, wadoUri, + instance, } = dataset; // Retrieve wadors or just try to fetch wadouri if (!someInvalidStrings(wadoRoot)) { @@ -192,6 +189,13 @@ class DicomLoaderService { ); } else if (!someInvalidStrings(wadoUri)) { return fetchIt(wadoUri, { headers: authorizationHeaders }); + } else if (!someInvalidStrings(instance?.url)) { + // make sure the url is absolute, remove the scope + // from it if it is not absolute. For instance it might be dicomweb:http://.... + // and we need to remove the dicomweb: part + const url = instance.url; + const absoluteUrl = url.startsWith('http') ? url : url.substring(url.indexOf(':') + 1); + return fetchIt(absoluteUrl, { headers: authorizationHeaders }); } } diff --git a/extensions/cornerstone/src/utils/getActiveViewportEnabledElement.ts b/extensions/cornerstone/src/utils/getActiveViewportEnabledElement.ts index d188ece9af0..e63e85f791d 100644 --- a/extensions/cornerstone/src/utils/getActiveViewportEnabledElement.ts +++ b/extensions/cornerstone/src/utils/getActiveViewportEnabledElement.ts @@ -3,11 +3,9 @@ import { IEnabledElement } from '@cornerstonejs/core/dist/esm/types'; import { getEnabledElement as OHIFgetEnabledElement } from '../state'; -export default function getActiveViewportEnabledElement( - viewportGridService -): IEnabledElement { - const { activeViewportIndex } = viewportGridService.getState(); - const { element } = OHIFgetEnabledElement(activeViewportIndex) || {}; +export default function getActiveViewportEnabledElement(viewportGridService): IEnabledElement { + const { activeViewportId } = viewportGridService.getState(); + const { element } = OHIFgetEnabledElement(activeViewportId) || {}; const enabledElement = getEnabledElement(element); return enabledElement; } diff --git a/extensions/cornerstone/src/utils/getCornerstoneBlendMode.ts b/extensions/cornerstone/src/utils/getCornerstoneBlendMode.ts index 4bc7cb449f3..37dca4c10bc 100644 --- a/extensions/cornerstone/src/utils/getCornerstoneBlendMode.ts +++ b/extensions/cornerstone/src/utils/getCornerstoneBlendMode.ts @@ -2,9 +2,7 @@ import { Enums } from '@cornerstonejs/core'; const MIP = 'mip'; -export default function getCornerstoneBlendMode( - blendMode: string -): Enums.BlendModes { +export default function getCornerstoneBlendMode(blendMode: string): Enums.BlendModes { if (!blendMode) { return Enums.BlendModes.COMPOSITE; } diff --git a/extensions/cornerstone/src/utils/getCornerstoneOrientation.ts b/extensions/cornerstone/src/utils/getCornerstoneOrientation.ts index 8ac7b2cb518..6e47fe80765 100644 --- a/extensions/cornerstone/src/utils/getCornerstoneOrientation.ts +++ b/extensions/cornerstone/src/utils/getCornerstoneOrientation.ts @@ -4,9 +4,7 @@ const AXIAL = 'axial'; const SAGITTAL = 'sagittal'; const CORONAL = 'coronal'; -export default function getCornerstoneOrientation( - orientation: string -): Enums.OrientationAxis { +export default function getCornerstoneOrientation(orientation: string): Enums.OrientationAxis { if (orientation) { switch (orientation.toLowerCase()) { case AXIAL: diff --git a/extensions/cornerstone/src/utils/getCornerstoneViewportType.ts b/extensions/cornerstone/src/utils/getCornerstoneViewportType.ts index 47a5ede85b3..9a5dd71de10 100644 --- a/extensions/cornerstone/src/utils/getCornerstoneViewportType.ts +++ b/extensions/cornerstone/src/utils/getCornerstoneViewportType.ts @@ -5,9 +5,7 @@ const VOLUME = 'volume'; const ORTHOGRAPHIC = 'orthographic'; const VOLUME_3D = 'volume3d'; -export default function getCornerstoneViewportType( - viewportType: string -): Enums.ViewportType { +export default function getCornerstoneViewportType(viewportType: string): Enums.ViewportType { const lowerViewportType = viewportType.toLowerCase(); if (lowerViewportType === STACK) { return Enums.ViewportType.STACK; @@ -21,7 +19,5 @@ export default function getCornerstoneViewportType( return Enums.ViewportType.VOLUME_3D; } - throw new Error( - `Invalid viewport type: ${viewportType}. Valid types are: stack, volume` - ); + throw new Error(`Invalid viewport type: ${viewportType}. Valid types are: stack, volume`); } diff --git a/extensions/cornerstone/src/utils/getInterleavedFrames.js b/extensions/cornerstone/src/utils/getInterleavedFrames.js index d8eae131a21..b380a9350b3 100644 --- a/extensions/cornerstone/src/utils/getInterleavedFrames.js +++ b/extensions/cornerstone/src/utils/getInterleavedFrames.js @@ -30,7 +30,7 @@ export default function getInterleavedFrames(imageIds) { !prefetchQueuedFilled.currentPositionUpToMaximum ) { if (!prefetchQueuedFilled.currentPositionDownToMinimum) { - // Add imageId bellow + // Add imageId below lowerImageIdIndex--; imageIdsToPrefetch.push({ imageId: imageIds[lowerImageIdIndex], diff --git a/extensions/cornerstone/src/utils/getNthFrames.js b/extensions/cornerstone/src/utils/getNthFrames.js index 09bfc0b1ec2..38df66d9587 100644 --- a/extensions/cornerstone/src/utils/getNthFrames.js +++ b/extensions/cornerstone/src/utils/getNthFrames.js @@ -19,11 +19,7 @@ export default function getNthFrames(imageIds) { const centerEnd = centerStart + 6; for (let i = 0; i < imageIds.length; i++) { - if ( - i < 2 || - i > imageIds.length - 4 || - (i > centerStart && i < centerEnd) - ) { + if (i < 2 || i > imageIds.length - 4 || (i > centerStart && i < centerEnd)) { frames[0].push(imageIds[i]); } else if (i % 7 === 2) { frames[1].push(imageIds[i]); @@ -33,12 +29,6 @@ export default function getNthFrames(imageIds) { frames[(i % 2) + 3].push(imageIds[i]); } } - const ret = [ - ...frames[0], - ...frames[1], - ...frames[2], - ...frames[3], - ...frames[4], - ]; + const ret = [...frames[0], ...frames[1], ...frames[2], ...frames[3], ...frames[4]]; return ret; } diff --git a/extensions/cornerstone/src/utils/stackSync/calculateViewportRegistrations.ts b/extensions/cornerstone/src/utils/imageSliceSync/calculateViewportRegistrations.ts similarity index 90% rename from extensions/cornerstone/src/utils/stackSync/calculateViewportRegistrations.ts rename to extensions/cornerstone/src/utils/imageSliceSync/calculateViewportRegistrations.ts index cbb976534be..aae2882b051 100644 --- a/extensions/cornerstone/src/utils/stackSync/calculateViewportRegistrations.ts +++ b/extensions/cornerstone/src/utils/imageSliceSync/calculateViewportRegistrations.ts @@ -1,8 +1,6 @@ import { Types, getRenderingEngine, utilities } from '@cornerstonejs/core'; -export default function calculateViewportRegistrations( - viewports: Types.IViewportId[] -) { +export default function calculateViewportRegistrations(viewports: Types.IViewportId[]) { const viewportPairs = _getViewportPairs(viewports); for (const [viewport, nextViewport] of viewportPairs) { diff --git a/extensions/cornerstone/src/utils/imageSliceSync/toggleImageSliceSync.ts b/extensions/cornerstone/src/utils/imageSliceSync/toggleImageSliceSync.ts new file mode 100644 index 00000000000..38e5bdfedd6 --- /dev/null +++ b/extensions/cornerstone/src/utils/imageSliceSync/toggleImageSliceSync.ts @@ -0,0 +1,81 @@ +const IMAGE_SLICE_SYNC_NAME = 'IMAGE_SLICE_SYNC'; + +export default function toggleImageSliceSync({ + toggledState, + servicesManager, + viewports: providedViewports, +}) { + if (!toggledState) { + return disableSync(IMAGE_SLICE_SYNC_NAME, servicesManager); + } + + const { syncGroupService, viewportGridService, displaySetService, cornerstoneViewportService } = + servicesManager.services; + + const viewports = + providedViewports || getReconstructableStackViewports(viewportGridService, displaySetService); + + // create synchronization group and add the viewports to it. + viewports.forEach(gridViewport => { + const { viewportId } = gridViewport.viewportOptions; + const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); + if (!viewport) { + return; + } + syncGroupService.addViewportToSyncGroup(viewportId, viewport.getRenderingEngine().id, { + type: 'stackimage', + id: IMAGE_SLICE_SYNC_NAME, + source: true, + target: true, + }); + }); +} + +function disableSync(syncName, servicesManager) { + const { syncGroupService, viewportGridService, displaySetService, cornerstoneViewportService } = + servicesManager.services; + const viewports = getReconstructableStackViewports(viewportGridService, displaySetService); + viewports.forEach(gridViewport => { + const { viewportId } = gridViewport.viewportOptions; + const viewport = cornerstoneViewportService.getCornerstoneViewport(viewportId); + if (!viewport) { + return; + } + syncGroupService.removeViewportFromSyncGroup( + viewport.id, + viewport.getRenderingEngine().id, + syncName + ); + }); +} + +/** + * Gets the consistent spacing stack viewport types, which are the ones which + * can be navigated using the stack image sync right now. + */ +function getReconstructableStackViewports(viewportGridService, displaySetService) { + let { viewports } = viewportGridService.getState(); + + viewports = [...viewports.values()]; + // filter empty viewports + viewports = viewports.filter( + viewport => viewport.displaySetInstanceUIDs && viewport.displaySetInstanceUIDs.length + ); + + // filter reconstructable viewports + viewports = viewports.filter(viewport => { + const { displaySetInstanceUIDs } = viewport; + + for (const displaySetInstanceUID of displaySetInstanceUIDs) { + const displaySet = displaySetService.getDisplaySetByUID(displaySetInstanceUID); + + // TODO - add a better test than isReconstructable + if (displaySet && displaySet.isReconstructable) { + return true; + } + + return false; + } + }); + return viewports; +} diff --git a/extensions/cornerstone/src/utils/initViewTiming.ts b/extensions/cornerstone/src/utils/initViewTiming.ts new file mode 100644 index 00000000000..a9d1544c48d --- /dev/null +++ b/extensions/cornerstone/src/utils/initViewTiming.ts @@ -0,0 +1,52 @@ +import { log, Enums } from '@ohif/core'; +import { EVENTS } from '@cornerstonejs/core'; + +const IMAGE_TIMING_KEYS = []; + +const imageTiming = { + viewportsWaiting: 0, +}; + +/** + * Defines the initial view timing reporting. + * This allows knowing how many viewports are waiting for initial views and + * when the IMAGE_RENDERED gets sent out. + * The first image rendered will fire the FIRST_IMAGE timeEnd logs, while + * the last of the enabled viewport will fire the ALL_IMAGES timeEnd logs. + * + */ + +export default function initViewTiming({ element }) { + if (!IMAGE_TIMING_KEYS.length) { + // Work around a bug in WebPack that doesn't getting the enums initialized + // quite fast enough to be declared statically. + const { TimingEnum } = Enums; + + IMAGE_TIMING_KEYS.push( + TimingEnum.DISPLAY_SETS_TO_ALL_IMAGES, + TimingEnum.DISPLAY_SETS_TO_FIRST_IMAGE, + TimingEnum.STUDY_TO_FIRST_IMAGE, + ); + } + + if (!IMAGE_TIMING_KEYS.find(key => log.timingKeys[key])) { + return; + } + imageTiming.viewportsWaiting += 1; + element.addEventListener(EVENTS.IMAGE_RENDERED, imageRenderedListener); +} + +function imageRenderedListener(evt) { + if (evt.detail.viewportStatus === 'preRender') { + return; + } + const { TimingEnum } = Enums; + log.timeEnd(TimingEnum.DISPLAY_SETS_TO_FIRST_IMAGE); + log.timeEnd(TimingEnum.STUDY_TO_FIRST_IMAGE); + log.timeEnd(TimingEnum.SCRIPT_TO_VIEW); + imageTiming.viewportsWaiting -= 1; + evt.detail.element.removeEventListener(EVENTS.IMAGE_RENDERED, imageRenderedListener); + if (!imageTiming.viewportsWaiting) { + log.timeEnd(TimingEnum.DISPLAY_SETS_TO_ALL_IMAGES); + } +} diff --git a/extensions/cornerstone/src/utils/interleaveCenterLoader.ts b/extensions/cornerstone/src/utils/interleaveCenterLoader.ts index 0047f0ac0ab..d8f64b5d838 100644 --- a/extensions/cornerstone/src/utils/interleaveCenterLoader.ts +++ b/extensions/cornerstone/src/utils/interleaveCenterLoader.ts @@ -105,9 +105,7 @@ export default function interleaveCenterLoader({ const { imageId } = request; AllRequests.forEach(volumeRequests => { - const volumeImageIdRequest = volumeRequests.find( - req => req.imageId === imageId - ); + const volumeImageIdRequest = volumeRequests.find(req => req.imageId === imageId); if (volumeImageIdRequest) { finalRequests.push(volumeImageIdRequest); } @@ -117,31 +115,17 @@ export default function interleaveCenterLoader({ const requestType = Enums.RequestType.Prefetch; const priority = 0; - finalRequests.forEach( - ({ callLoadImage, additionalDetails, imageId, imageIdIndex, options }) => { - const callLoadImageBound = callLoadImage.bind( - null, - imageId, - imageIdIndex, - options - ); - - imageLoadPoolManager.addRequest( - callLoadImageBound, - requestType, - additionalDetails, - priority - ); - } - ); + finalRequests.forEach(({ callLoadImage, additionalDetails, imageId, imageIdIndex, options }) => { + const callLoadImageBound = callLoadImage.bind(null, imageId, imageIdIndex, options); + + imageLoadPoolManager.addRequest(callLoadImageBound, requestType, additionalDetails, priority); + }); // clear the volumeIdMapsToLoad volumeIdMapsToLoad.clear(); // copy the viewportIdVolumeInputArrayMap - const viewportIdVolumeInputArrayMapCopy = new Map( - viewportIdVolumeInputArrayMap - ); + const viewportIdVolumeInputArrayMapCopy = new Map(viewportIdVolumeInputArrayMap); // reset the viewportIdVolumeInputArrayMap viewportIdVolumeInputArrayMap.clear(); diff --git a/extensions/cornerstone/src/utils/interleaveTopToBottom.ts b/extensions/cornerstone/src/utils/interleaveTopToBottom.ts index 162bb5588cf..0caf4037292 100644 --- a/extensions/cornerstone/src/utils/interleaveTopToBottom.ts +++ b/extensions/cornerstone/src/utils/interleaveTopToBottom.ts @@ -94,9 +94,7 @@ export default function interleaveTopToBottom({ const { imageId } = request; AllRequests.forEach(volumeRequests => { - const volumeImageIdRequest = volumeRequests.find( - req => req.imageId === imageId - ); + const volumeImageIdRequest = volumeRequests.find(req => req.imageId === imageId); if (volumeImageIdRequest) { finalRequests.push(volumeImageIdRequest); } @@ -106,31 +104,17 @@ export default function interleaveTopToBottom({ const requestType = Enums.RequestType.Prefetch; const priority = 0; - finalRequests.forEach( - ({ callLoadImage, additionalDetails, imageId, imageIdIndex, options }) => { - const callLoadImageBound = callLoadImage.bind( - null, - imageId, - imageIdIndex, - options - ); - - imageLoadPoolManager.addRequest( - callLoadImageBound, - requestType, - additionalDetails, - priority - ); - } - ); + finalRequests.forEach(({ callLoadImage, additionalDetails, imageId, imageIdIndex, options }) => { + const callLoadImageBound = callLoadImage.bind(null, imageId, imageIdIndex, options); + + imageLoadPoolManager.addRequest(callLoadImageBound, requestType, additionalDetails, priority); + }); // clear the volumeIdMapsToLoad volumeIdMapsToLoad.clear(); // copy the viewportIdVolumeInputArrayMap - const viewportIdVolumeInputArrayMapCopy = new Map( - viewportIdVolumeInputArrayMap - ); + const viewportIdVolumeInputArrayMapCopy = new Map(viewportIdVolumeInputArrayMap); // reset the viewportIdVolumeInputArrayMap viewportIdVolumeInputArrayMap.clear(); diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/Angle.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/Angle.ts index 767f550d41d..7173e134466 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/Angle.ts +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/Angle.ts @@ -1,4 +1,5 @@ import SUPPORTED_TOOLS from './constants/supportedTools'; +import { getDisplayUnit } from './utils'; import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes'; import { utils } from '@ohif/core'; @@ -32,11 +33,7 @@ const Angle = { throw new Error('Tool not supported'); } - const { - SOPInstanceUID, - SeriesInstanceUID, - StudyInstanceUID, - } = getSOPInstanceAttributes( + const { SOPInstanceUID, SeriesInstanceUID, StudyInstanceUID } = getSOPInstanceAttributes( referencedImageId, CornerstoneViewportService, viewportId @@ -53,22 +50,19 @@ const Angle = { displaySet = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID); } - const { points } = data.handles; + const { points, textBox } = data.handles; - const mappedAnnotations = getMappedAnnotations( - annotation, - displaySetService - ); + const mappedAnnotations = getMappedAnnotations(annotation, displaySetService); const displayText = getDisplayText(mappedAnnotations, displaySet); - const getReport = () => - _getReport(mappedAnnotations, points, FrameOfReferenceUID); + const getReport = () => _getReport(mappedAnnotations, points, FrameOfReferenceUID); return { uid: annotationUID, SOPInstanceUID, FrameOfReferenceUID, points, + textBox, metadata, referenceSeriesUID: SeriesInstanceUID, referenceStudyUID: StudyInstanceUID, @@ -99,16 +93,11 @@ function getMappedAnnotations(annotation, DisplaySetService) { const targetStats = cachedStats[targetId]; if (!referencedImageId) { - throw new Error( - 'Non-acquisition plane measurement mapping not supported' - ); + throw new Error('Non-acquisition plane measurement mapping not supported'); } - const { - SOPInstanceUID, - SeriesInstanceUID, - frameNumber, - } = getSOPInstanceAttributes(referencedImageId); + const { SOPInstanceUID, SeriesInstanceUID, frameNumber } = + getSOPInstanceAttributes(referencedImageId); const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( SOPInstanceUID, @@ -179,17 +168,9 @@ function getDisplayText(mappedAnnotations, displaySet) { const displayText = []; // Area is the same for all series - const { - angle, - unit, - SeriesNumber, - SOPInstanceUID, - frameNumber, - } = mappedAnnotations[0]; - - const instance = displaySet.images.find( - image => image.SOPInstanceUID === SOPInstanceUID - ); + const { angle, unit, SeriesNumber, SOPInstanceUID, frameNumber } = mappedAnnotations[0]; + + const instance = displaySet.images.find(image => image.SOPInstanceUID === SOPInstanceUID); let InstanceNumber; if (instance) { @@ -203,7 +184,7 @@ function getDisplayText(mappedAnnotations, displaySet) { } const roundedAngle = utils.roundNumber(angle, 2); displayText.push( - `${roundedAngle} ${unit} (S: ${SeriesNumber}${instanceText}${frameText})` + `${roundedAngle} ${getDisplayUnit(unit)} (S: ${SeriesNumber}${instanceText}${frameText})` ); return displayText; diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/ArrowAnnotate.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/ArrowAnnotate.ts index 9a6f6e49b3b..3d5904171b4 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/ArrowAnnotate.ts +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/ArrowAnnotate.ts @@ -31,11 +31,7 @@ const Length = { throw new Error('Tool not supported'); } - const { - SOPInstanceUID, - SeriesInstanceUID, - StudyInstanceUID, - } = getSOPInstanceAttributes( + const { SOPInstanceUID, SeriesInstanceUID, StudyInstanceUID } = getSOPInstanceAttributes( referencedImageId, cornerstoneViewportService, viewportId @@ -52,12 +48,9 @@ const Length = { displaySet = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID); } - const { points } = data.handles; + const { points, textBox } = data.handles; - const mappedAnnotations = getMappedAnnotations( - annotation, - displaySetService - ); + const mappedAnnotations = getMappedAnnotations(annotation, displaySetService); const displayText = getDisplayText(mappedAnnotations, displaySet); @@ -66,6 +59,7 @@ const Length = { SOPInstanceUID, FrameOfReferenceUID, points, + textBox, metadata, referenceSeriesUID: SeriesInstanceUID, referenceStudyUID: StudyInstanceUID, @@ -73,7 +67,6 @@ const Length = { toolName: metadata.toolName, displaySetInstanceUID: displaySet.displaySetInstanceUID, label: data.text, - text: data.text, displayText: displayText, data: data.cachedStats, type: getValueTypeFromToolType(toolName), @@ -91,11 +84,8 @@ function getMappedAnnotations(annotation, displaySetService) { const annotations = []; - const { - SOPInstanceUID, - SeriesInstanceUID, - frameNumber, - } = getSOPInstanceAttributes(referencedImageId); + const { SOPInstanceUID, SeriesInstanceUID, frameNumber } = + getSOPInstanceAttributes(referencedImageId); const displaySet = displaySetService.getDisplaySetForSOPInstanceUID( SOPInstanceUID, @@ -126,9 +116,7 @@ function getDisplayText(mappedAnnotations, displaySet) { // Area is the same for all series const { SeriesNumber, SOPInstanceUID, frameNumber } = mappedAnnotations[0]; - const instance = displaySet.images.find( - image => image.SOPInstanceUID === SOPInstanceUID - ); + const instance = displaySet.images.find(image => image.SOPInstanceUID === SOPInstanceUID); let InstanceNumber; if (instance) { diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/Bidirectional.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/Bidirectional.ts index 781a0f909fa..958e22ed6b0 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/Bidirectional.ts +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/Bidirectional.ts @@ -3,6 +3,7 @@ import { annotation } from '@cornerstonejs/tools'; import SUPPORTED_TOOLS from './constants/supportedTools'; import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes'; import { utils } from '@ohif/core'; +import { getDisplayUnit } from './utils'; const Bidirectional = { toAnnotation: measurement => {}, @@ -27,11 +28,7 @@ const Bidirectional = { throw new Error('Tool not supported'); } - const { - SOPInstanceUID, - SeriesInstanceUID, - StudyInstanceUID, - } = getSOPInstanceAttributes( + const { SOPInstanceUID, SeriesInstanceUID, StudyInstanceUID } = getSOPInstanceAttributes( referencedImageId, cornerstoneViewportService, viewportId @@ -48,22 +45,19 @@ const Bidirectional = { displaySet = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID); } - const { points } = data.handles; + const { points, textBox } = data.handles; - const mappedAnnotations = getMappedAnnotations( - annotation, - displaySetService - ); + const mappedAnnotations = getMappedAnnotations(annotation, displaySetService); const displayText = getDisplayText(mappedAnnotations, displaySet); - const getReport = () => - _getReport(mappedAnnotations, points, FrameOfReferenceUID); + const getReport = () => _getReport(mappedAnnotations, points, FrameOfReferenceUID); return { uid: annotationUID, SOPInstanceUID, FrameOfReferenceUID, points, + textBox, metadata, referenceSeriesUID: SeriesInstanceUID, referenceStudyUID: StudyInstanceUID, @@ -94,16 +88,11 @@ function getMappedAnnotations(annotation, displaySetService) { const targetStats = cachedStats[targetId]; if (!referencedImageId) { - throw new Error( - 'Non-acquisition plane measurement mapping not supported' - ); + throw new Error('Non-acquisition plane measurement mapping not supported'); } - const { - SOPInstanceUID, - SeriesInstanceUID, - frameNumber, - } = getSOPInstanceAttributes(referencedImageId); + const { SOPInstanceUID, SeriesInstanceUID, frameNumber } = + getSOPInstanceAttributes(referencedImageId); const displaySet = displaySetService.getDisplaySetForSOPInstanceUID( SOPInstanceUID, @@ -112,8 +101,7 @@ function getMappedAnnotations(annotation, displaySetService) { ); const { SeriesNumber } = displaySet; - const { length, width } = targetStats; - const unit = 'mm'; + const { length, width, unit } = targetStats; annotations.push({ SeriesInstanceUID, @@ -143,9 +131,9 @@ function _getReport(mappedAnnotations, points, FrameOfReferenceUID) { values.push('Cornerstone:Bidirectional'); mappedAnnotations.forEach(annotation => { - const { length, width } = annotation; - columns.push(`Length (mm)`, `Width (mm)`); - values.push(length, width); + const { length, width, unit } = annotation; + columns.push(`Length`, `Width`, 'Unit'); + values.push(length, width, unit); }); if (FrameOfReferenceUID) { @@ -175,19 +163,11 @@ function getDisplayText(mappedAnnotations, displaySet) { const displayText = []; // Area is the same for all series - const { - length, - width, - SeriesNumber, - SOPInstanceUID, - frameNumber, - } = mappedAnnotations[0]; + const { length, width, unit, SeriesNumber, SOPInstanceUID, frameNumber } = mappedAnnotations[0]; const roundedLength = utils.roundNumber(length, 2); const roundedWidth = utils.roundNumber(width, 2); - const instance = displaySet.images.find( - image => image.SOPInstanceUID === SOPInstanceUID - ); + const instance = displaySet.images.find(image => image.SOPInstanceUID === SOPInstanceUID); let InstanceNumber; if (instance) { @@ -198,9 +178,9 @@ function getDisplayText(mappedAnnotations, displaySet) { const frameText = displaySet.isMultiFrame ? ` F: ${frameNumber}` : ''; displayText.push( - `L: ${roundedLength} mm (S: ${SeriesNumber}${instanceText}${frameText})` + `L: ${roundedLength} ${getDisplayUnit(unit)} (S: ${SeriesNumber}${instanceText}${frameText})` ); - displayText.push(`W: ${roundedWidth} mm`); + displayText.push(`W: ${roundedWidth} ${getDisplayUnit(unit)}`); return displayText; } diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/CircleROI.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/CircleROI.ts index f261ac4ba39..e65564c8188 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/CircleROI.ts +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/CircleROI.ts @@ -1,4 +1,5 @@ import SUPPORTED_TOOLS from './constants/supportedTools'; +import { getDisplayUnit } from './utils'; import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes'; import { utils } from '@ohif/core'; @@ -25,11 +26,7 @@ const CircleROI = { throw new Error('Tool not supported'); } - const { - SOPInstanceUID, - SeriesInstanceUID, - StudyInstanceUID, - } = getSOPInstanceAttributes( + const { SOPInstanceUID, SeriesInstanceUID, StudyInstanceUID } = getSOPInstanceAttributes( referencedImageId, CornerstoneViewportService, viewportId @@ -46,22 +43,19 @@ const CircleROI = { displaySet = DisplaySetService.getDisplaySetsForSeries(SeriesInstanceUID); } - const { points } = data.handles; + const { points, textBox } = data.handles; - const mappedAnnotations = getMappedAnnotations( - annotation, - DisplaySetService - ); + const mappedAnnotations = getMappedAnnotations(annotation, DisplaySetService); const displayText = getDisplayText(mappedAnnotations, displaySet); - const getReport = () => - _getReport(mappedAnnotations, points, FrameOfReferenceUID); + const getReport = () => _getReport(mappedAnnotations, points, FrameOfReferenceUID); return { uid: annotationUID, SOPInstanceUID, FrameOfReferenceUID, points, + textBox, metadata, referenceSeriesUID: SeriesInstanceUID, referenceStudyUID: StudyInstanceUID, @@ -93,16 +87,11 @@ function getMappedAnnotations(annotation, DisplaySetService) { if (!referencedImageId) { // Todo: Non-acquisition plane measurement mapping not supported yet - throw new Error( - 'Non-acquisition plane measurement mapping not supported' - ); + throw new Error('Non-acquisition plane measurement mapping not supported'); } - const { - SOPInstanceUID, - SeriesInstanceUID, - frameNumber, - } = getSOPInstanceAttributes(referencedImageId); + const { SOPInstanceUID, SeriesInstanceUID, frameNumber } = + getSOPInstanceAttributes(referencedImageId); const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( SOPInstanceUID, @@ -111,7 +100,7 @@ function getMappedAnnotations(annotation, DisplaySetService) { ); const { SeriesNumber } = displaySet; - const { mean, stdDev, max, area, Modality, modalityUnit } = targetStats; + const { mean, stdDev, max, area, Modality, areaUnit, modalityUnit } = targetStats; annotations.push({ SeriesInstanceUID, @@ -124,6 +113,7 @@ function getMappedAnnotations(annotation, DisplaySetService) { stdDev, max, area, + areaUnit, }); }); @@ -144,19 +134,14 @@ function _getReport(mappedAnnotations, points, FrameOfReferenceUID) { values.push('Cornerstone:CircleROI'); mappedAnnotations.forEach(annotation => { - const { mean, stdDev, max, area, unit } = annotation; + const { mean, stdDev, max, area, unit, areaUnit } = annotation; if (!mean || !unit || !max || !area) { return; } - columns.push( - `max (${unit})`, - `mean (${unit})`, - `std (${unit})`, - `area (mm2)` - ); - values.push(max, mean, stdDev, area); + columns.push(`max (${unit})`, `mean (${unit})`, `std (${unit})`, 'Area', 'Unit'); + values.push(max, mean, stdDev, area, areaUnit); }); if (FrameOfReferenceUID) { @@ -186,11 +171,9 @@ function getDisplayText(mappedAnnotations, displaySet) { const displayText = []; // Area is the same for all series - const { area, SOPInstanceUID, frameNumber } = mappedAnnotations[0]; + const { area, SOPInstanceUID, frameNumber, areaUnit } = mappedAnnotations[0]; - const instance = displaySet.images.find( - image => image.SOPInstanceUID === SOPInstanceUID - ); + const instance = displaySet.images.find(image => image.SOPInstanceUID === SOPInstanceUID); let InstanceNumber; if (instance) { @@ -202,7 +185,7 @@ function getDisplayText(mappedAnnotations, displaySet) { // Area sometimes becomes undefined if `preventHandleOutsideImage` is off. const roundedArea = utils.roundNumber(area || 0, 2); - displayText.push(`${roundedArea} mm2`); + displayText.push(`${roundedArea} ${getDisplayUnit(areaUnit)}`); // Todo: we need a better UI for displaying all these information mappedAnnotations.forEach(mappedAnnotation => { @@ -211,7 +194,7 @@ function getDisplayText(mappedAnnotations, displaySet) { let maxStr = ''; if (max) { const roundedMax = utils.roundNumber(max, 2); - maxStr = `Max: ${roundedMax} ${unit} `; + maxStr = `Max: ${roundedMax} ${getDisplayUnit(unit)} `; } const str = `${maxStr}(S:${SeriesNumber}${instanceText}${frameText})`; diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/CobbAngle.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/CobbAngle.ts index 6e2ebc79c53..dbde82860b8 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/CobbAngle.ts +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/CobbAngle.ts @@ -1,4 +1,5 @@ import SUPPORTED_TOOLS from './constants/supportedTools'; +import { getDisplayUnit } from './utils'; import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes'; import { utils } from '@ohif/core'; @@ -32,11 +33,7 @@ const CobbAngle = { throw new Error('Tool not supported'); } - const { - SOPInstanceUID, - SeriesInstanceUID, - StudyInstanceUID, - } = getSOPInstanceAttributes( + const { SOPInstanceUID, SeriesInstanceUID, StudyInstanceUID } = getSOPInstanceAttributes( referencedImageId, CornerstoneViewportService, viewportId @@ -53,22 +50,19 @@ const CobbAngle = { displaySet = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID); } - const { points } = data.handles; + const { points, textBox } = data.handles; - const mappedAnnotations = getMappedAnnotations( - annotation, - displaySetService - ); + const mappedAnnotations = getMappedAnnotations(annotation, displaySetService); const displayText = getDisplayText(mappedAnnotations, displaySet); - const getReport = () => - _getReport(mappedAnnotations, points, FrameOfReferenceUID); + const getReport = () => _getReport(mappedAnnotations, points, FrameOfReferenceUID); return { uid: annotationUID, SOPInstanceUID, FrameOfReferenceUID, points, + textBox, metadata, referenceSeriesUID: SeriesInstanceUID, referenceStudyUID: StudyInstanceUID, @@ -99,16 +93,11 @@ function getMappedAnnotations(annotation, DisplaySetService) { const targetStats = cachedStats[targetId]; if (!referencedImageId) { - throw new Error( - 'Non-acquisition plane measurement mapping not supported' - ); + throw new Error('Non-acquisition plane measurement mapping not supported'); } - const { - SOPInstanceUID, - SeriesInstanceUID, - frameNumber, - } = getSOPInstanceAttributes(referencedImageId); + const { SOPInstanceUID, SeriesInstanceUID, frameNumber } = + getSOPInstanceAttributes(referencedImageId); const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( SOPInstanceUID, @@ -179,17 +168,9 @@ function getDisplayText(mappedAnnotations, displaySet) { const displayText = []; // Area is the same for all series - const { - angle, - unit, - SeriesNumber, - SOPInstanceUID, - frameNumber, - } = mappedAnnotations[0]; - - const instance = displaySet.images.find( - image => image.SOPInstanceUID === SOPInstanceUID - ); + const { angle, unit, SeriesNumber, SOPInstanceUID, frameNumber } = mappedAnnotations[0]; + + const instance = displaySet.images.find(image => image.SOPInstanceUID === SOPInstanceUID); let InstanceNumber; if (instance) { @@ -203,7 +184,7 @@ function getDisplayText(mappedAnnotations, displaySet) { } const roundedAngle = utils.roundNumber(angle, 2); displayText.push( - `${roundedAngle} ${unit} (S: ${SeriesNumber}${instanceText}${frameText})` + `${roundedAngle} ${getDisplayUnit(unit)} (S: ${SeriesNumber}${instanceText}${frameText})` ); return displayText; diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/EllipticalROI.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/EllipticalROI.ts index e20998534ab..1882d9ba0c0 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/EllipticalROI.ts +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/EllipticalROI.ts @@ -1,4 +1,5 @@ import SUPPORTED_TOOLS from './constants/supportedTools'; +import { getDisplayUnit } from './utils'; import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes'; import { utils } from '@ohif/core'; @@ -25,11 +26,7 @@ const EllipticalROI = { throw new Error('Tool not supported'); } - const { - SOPInstanceUID, - SeriesInstanceUID, - StudyInstanceUID, - } = getSOPInstanceAttributes( + const { SOPInstanceUID, SeriesInstanceUID, StudyInstanceUID } = getSOPInstanceAttributes( referencedImageId, cornerstoneViewportService, viewportId @@ -46,22 +43,19 @@ const EllipticalROI = { displaySet = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID); } - const { points } = data.handles; + const { points, textBox } = data.handles; - const mappedAnnotations = getMappedAnnotations( - annotation, - displaySetService - ); + const mappedAnnotations = getMappedAnnotations(annotation, displaySetService); const displayText = getDisplayText(mappedAnnotations, displaySet); - const getReport = () => - _getReport(mappedAnnotations, points, FrameOfReferenceUID); + const getReport = () => _getReport(mappedAnnotations, points, FrameOfReferenceUID); return { uid: annotationUID, SOPInstanceUID, FrameOfReferenceUID, points, + textBox, metadata, referenceSeriesUID: SeriesInstanceUID, referenceStudyUID: StudyInstanceUID, @@ -93,16 +87,11 @@ function getMappedAnnotations(annotation, displaySetService) { if (!referencedImageId) { // Todo: Non-acquisition plane measurement mapping not supported yet - throw new Error( - 'Non-acquisition plane measurement mapping not supported' - ); + throw new Error('Non-acquisition plane measurement mapping not supported'); } - const { - SOPInstanceUID, - SeriesInstanceUID, - frameNumber, - } = getSOPInstanceAttributes(referencedImageId); + const { SOPInstanceUID, SeriesInstanceUID, frameNumber } = + getSOPInstanceAttributes(referencedImageId); const displaySet = displaySetService.getDisplaySetForSOPInstanceUID( SOPInstanceUID, @@ -111,7 +100,7 @@ function getMappedAnnotations(annotation, displaySetService) { ); const { SeriesNumber } = displaySet; - const { mean, stdDev, max, area, Modality, modalityUnit } = targetStats; + const { mean, stdDev, max, area, Modality, areaUnit, modalityUnit } = targetStats; annotations.push({ SeriesInstanceUID, @@ -120,6 +109,7 @@ function getMappedAnnotations(annotation, displaySetService) { frameNumber, Modality, unit: modalityUnit, + areaUnit, mean, stdDev, max, @@ -144,19 +134,14 @@ function _getReport(mappedAnnotations, points, FrameOfReferenceUID) { values.push('Cornerstone:EllipticalROI'); mappedAnnotations.forEach(annotation => { - const { mean, stdDev, max, area, unit } = annotation; + const { mean, stdDev, max, area, unit, areaUnit } = annotation; if (!mean || !unit || !max || !area) { return; } - columns.push( - `max (${unit})`, - `mean (${unit})`, - `std (${unit})`, - `area (mm2)` - ); - values.push(max, mean, stdDev, area); + columns.push(`max (${unit})`, `mean (${unit})`, `std (${unit})`, 'Area', 'Unit'); + values.push(max, mean, stdDev, area, areaUnit); }); if (FrameOfReferenceUID) { @@ -186,11 +171,9 @@ function getDisplayText(mappedAnnotations, displaySet) { const displayText = []; // Area is the same for all series - const { area, SOPInstanceUID, frameNumber } = mappedAnnotations[0]; + const { area, SOPInstanceUID, frameNumber, areaUnit } = mappedAnnotations[0]; - const instance = displaySet.images.find( - image => image.SOPInstanceUID === SOPInstanceUID - ); + const instance = displaySet.images.find(image => image.SOPInstanceUID === SOPInstanceUID); let InstanceNumber; if (instance) { @@ -200,9 +183,8 @@ function getDisplayText(mappedAnnotations, displaySet) { const instanceText = InstanceNumber ? ` I: ${InstanceNumber}` : ''; const frameText = displaySet.isMultiFrame ? ` F: ${frameNumber}` : ''; - // Area sometimes becomes undefined if `preventHandleOutsideImage` is off. - const roundedArea = utils.roundNumber(area || 0, 2); - displayText.push(`${roundedArea} mm2`); + const roundedArea = utils.roundNumber(area, 2); + displayText.push(`${roundedArea} ${getDisplayUnit(areaUnit)}`); // Todo: we need a better UI for displaying all these information mappedAnnotations.forEach(mappedAnnotation => { @@ -211,7 +193,7 @@ function getDisplayText(mappedAnnotations, displaySet) { let maxStr = ''; if (max) { const roundedMax = utils.roundNumber(max, 2); - maxStr = `Max: ${roundedMax} ${unit} `; + maxStr = `Max: ${roundedMax} ${getDisplayUnit(unit)} `; } const str = `${maxStr}(S:${SeriesNumber}${instanceText}${frameText})`; diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/Length.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/Length.ts index fdc5993abe4..e5e9886b379 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/Length.ts +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/Length.ts @@ -53,22 +53,19 @@ const Length = { displaySet = displaySetService.getDisplaySetsForSeries(SeriesInstanceUID); } - const { points } = data.handles; + const { points, textBox } = data.handles; - const mappedAnnotations = getMappedAnnotations( - annotation, - displaySetService - ); + const mappedAnnotations = getMappedAnnotations(annotation, displaySetService); const displayText = getDisplayText(mappedAnnotations, displaySet); - const getReport = () => - _getReport(mappedAnnotations, points, FrameOfReferenceUID); + const getReport = () => _getReport(mappedAnnotations, points, FrameOfReferenceUID); return { uid: annotationUID, SOPInstanceUID, FrameOfReferenceUID, points, + textBox, metadata, referenceSeriesUID: SeriesInstanceUID, referenceStudyUID: StudyInstanceUID, @@ -99,9 +96,7 @@ function getMappedAnnotations(annotation, displaySetService) { const targetStats = cachedStats[targetId]; if (!referencedImageId) { - throw new Error( - 'Non-acquisition plane measurement mapping not supported' - ); + throw new Error('Non-acquisition plane measurement mapping not supported'); } const { @@ -117,8 +112,7 @@ function getMappedAnnotations(annotation, displaySetService) { ); const { SeriesNumber } = displaySet; - const { length } = targetStats; - const unit = 'mm'; + const { length, unit = 'mm' } = targetStats; annotations.push({ SeriesInstanceUID, @@ -147,9 +141,11 @@ function _getReport(mappedAnnotations, points, FrameOfReferenceUID) { values.push('Cornerstone:Length'); mappedAnnotations.forEach(annotation => { - const { length } = annotation; - columns.push(`Length (mm)`); + const { length, unit } = annotation; + columns.push(`Length`); values.push(length); + columns.push('Unit'); + values.push(unit); }); if (FrameOfReferenceUID) { @@ -184,11 +180,10 @@ function getDisplayText(mappedAnnotations, displaySet) { SeriesNumber, SOPInstanceUID, frameNumber, + unit, } = mappedAnnotations[0]; - const instance = displaySet.images.find( - image => image.SOPInstanceUID === SOPInstanceUID - ); + const instance = displaySet.images.find(image => image.SOPInstanceUID === SOPInstanceUID); let InstanceNumber; if (instance) { @@ -203,7 +198,7 @@ function getDisplayText(mappedAnnotations, displaySet) { } const roundedLength = utils.roundNumber(length, 2); displayText.push( - `${roundedLength} mm (S: ${SeriesNumber}${instanceText}${frameText})` + `${roundedLength} ${unit} (S: ${SeriesNumber}${instanceText}${frameText})` ); return displayText; diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/PlanarFreehandROI.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/PlanarFreehandROI.ts index 2b0fd700891..d1322e48438 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/PlanarFreehandROI.ts +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/PlanarFreehandROI.ts @@ -31,11 +31,7 @@ const PlanarFreehandROI = { throw new Error('Tool not supported'); } - const { - SOPInstanceUID, - SeriesInstanceUID, - StudyInstanceUID, - } = getSOPInstanceAttributes( + const { SOPInstanceUID, SeriesInstanceUID, StudyInstanceUID } = getSOPInstanceAttributes( referencedImageId, CornerstoneViewportService, viewportId @@ -52,22 +48,19 @@ const PlanarFreehandROI = { displaySet = DisplaySetService.getDisplaySetsForSeries(SeriesInstanceUID); } - const { points } = data.handles; + const { points, textBox } = data.handles; - const mappedAnnotations = getMappedAnnotations( - annotation, - DisplaySetService - ); + const mappedAnnotations = getMappedAnnotations(annotation, DisplaySetService); const displayText = getDisplayText(mappedAnnotations); - const getReport = () => - _getReport(mappedAnnotations, points, FrameOfReferenceUID); + const getReport = () => _getReport(mappedAnnotations, points, FrameOfReferenceUID); return { uid: annotationUID, SOPInstanceUID, FrameOfReferenceUID, points, + textBox, metadata, referenceSeriesUID: SeriesInstanceUID, referenceStudyUID: StudyInstanceUID, @@ -96,10 +89,8 @@ function getMappedAnnotations(annotationData, DisplaySetService) { const annotations = []; - const { - SOPInstanceUID: _SOPInstanceUID, - SeriesInstanceUID: _SeriesInstanceUID, - } = getSOPInstanceAttributes(referencedImageId) || {}; + const { SOPInstanceUID: _SOPInstanceUID, SeriesInstanceUID: _SeriesInstanceUID } = + getSOPInstanceAttributes(referencedImageId) || {}; if (!_SOPInstanceUID || !_SeriesInstanceUID) { return annotations; diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/RectangleROI.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/RectangleROI.ts index 3694f76d00b..6c7e4b4ca1b 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/RectangleROI.ts +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/RectangleROI.ts @@ -1,4 +1,5 @@ import SUPPORTED_TOOLS from './constants/supportedTools'; +import { getDisplayUnit } from './utils'; import getSOPInstanceAttributes from './utils/getSOPInstanceAttributes'; import { utils } from '@ohif/core'; @@ -25,11 +26,7 @@ const RectangleROI = { throw new Error('Tool not supported'); } - const { - SOPInstanceUID, - SeriesInstanceUID, - StudyInstanceUID, - } = getSOPInstanceAttributes( + const { SOPInstanceUID, SeriesInstanceUID, StudyInstanceUID } = getSOPInstanceAttributes( referencedImageId, CornerstoneViewportService, viewportId @@ -46,22 +43,19 @@ const RectangleROI = { displaySet = DisplaySetService.getDisplaySetsForSeries(SeriesInstanceUID); } - const { points } = data.handles; + const { points, textBox } = data.handles; - const mappedAnnotations = getMappedAnnotations( - annotation, - DisplaySetService - ); + const mappedAnnotations = getMappedAnnotations(annotation, DisplaySetService); const displayText = getDisplayText(mappedAnnotations, displaySet); - const getReport = () => - _getReport(mappedAnnotations, points, FrameOfReferenceUID); + const getReport = () => _getReport(mappedAnnotations, points, FrameOfReferenceUID); return { uid: annotationUID, SOPInstanceUID, FrameOfReferenceUID, points, + textBox, metadata, referenceSeriesUID: SeriesInstanceUID, referenceStudyUID: StudyInstanceUID, @@ -93,16 +87,11 @@ function getMappedAnnotations(annotation, DisplaySetService) { if (!referencedImageId) { // Todo: Non-acquisition plane measurement mapping not supported yet - throw new Error( - 'Non-acquisition plane measurement mapping not supported' - ); + throw new Error('Non-acquisition plane measurement mapping not supported'); } - const { - SOPInstanceUID, - SeriesInstanceUID, - frameNumber, - } = getSOPInstanceAttributes(referencedImageId); + const { SOPInstanceUID, SeriesInstanceUID, frameNumber } = + getSOPInstanceAttributes(referencedImageId); const displaySet = DisplaySetService.getDisplaySetForSOPInstanceUID( SOPInstanceUID, @@ -111,7 +100,7 @@ function getMappedAnnotations(annotation, DisplaySetService) { ); const { SeriesNumber } = displaySet; - const { mean, stdDev, max, area, Modality, modalityUnit } = targetStats; + const { mean, stdDev, max, area, Modality, modalityUnit, areaUnit } = targetStats; annotations.push({ SeriesInstanceUID, @@ -124,6 +113,7 @@ function getMappedAnnotations(annotation, DisplaySetService) { stdDev, max, area, + areaUnit, }); }); @@ -144,19 +134,14 @@ function _getReport(mappedAnnotations, points, FrameOfReferenceUID) { values.push('Cornerstone:RectangleROI'); mappedAnnotations.forEach(annotation => { - const { mean, stdDev, max, area, unit } = annotation; + const { mean, stdDev, max, area, unit, areaUnit } = annotation; if (!mean || !unit || !max || !area) { return; } - columns.push( - `max (${unit})`, - `mean (${unit})`, - `std (${unit})`, - `area (mm2)` - ); - values.push(max, mean, stdDev, area); + columns.push(`Maximum`, `Mean`, `Std Dev`, 'Pixel Unit', `Area`, 'Unit'); + values.push(max, mean, stdDev, unit, area, areaUnit); }); if (FrameOfReferenceUID) { @@ -186,11 +171,9 @@ function getDisplayText(mappedAnnotations, displaySet) { const displayText = []; // Area is the same for all series - const { area, SOPInstanceUID, frameNumber } = mappedAnnotations[0]; + const { area, SOPInstanceUID, frameNumber, areaUnit } = mappedAnnotations[0]; - const instance = displaySet.images.find( - image => image.SOPInstanceUID === SOPInstanceUID - ); + const instance = displaySet.images.find(image => image.SOPInstanceUID === SOPInstanceUID); let InstanceNumber; if (instance) { @@ -202,7 +185,7 @@ function getDisplayText(mappedAnnotations, displaySet) { // Area sometimes becomes undefined if `preventHandleOutsideImage` is off. const roundedArea = utils.roundNumber(area || 0, 2); - displayText.push(`${roundedArea} mm2`); + displayText.push(`${roundedArea} ${getDisplayUnit(areaUnit)}`); // Todo: we need a better UI for displaying all these information mappedAnnotations.forEach(mappedAnnotation => { @@ -211,7 +194,7 @@ function getDisplayText(mappedAnnotations, displaySet) { let maxStr = ''; if (max) { const roundedMax = utils.roundNumber(max, 2); - maxStr = `Max: ${roundedMax} ${unit} `; + maxStr = `Max: ${roundedMax} ${getDisplayUnit(unit)} `; } const str = `${maxStr}(S:${SeriesNumber}${instanceText}${frameText})`; diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.ts index 2a4562e7dbc..e3ccf4e51e8 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.ts +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/measurementServiceMappingsFactory.ts @@ -23,15 +23,8 @@ const measurementServiceMappingsFactory = ( */ const _getValueTypeFromToolType = toolType => { - const { - POLYLINE, - ELLIPSE, - CIRCLE, - RECTANGLE, - BIDIRECTIONAL, - POINT, - ANGLE, - } = MeasurementService.VALUE_TYPES; + const { POLYLINE, ELLIPSE, CIRCLE, RECTANGLE, BIDIRECTIONAL, POINT, ANGLE } = + MeasurementService.VALUE_TYPES; // TODO -> I get why this was attempted, but its not nearly flexible enough. // A single measurement may have an ellipse + a bidirectional measurement, for instances. diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/utils/getDisplayUnit.js b/extensions/cornerstone/src/utils/measurementServiceMappings/utils/getDisplayUnit.js new file mode 100644 index 00000000000..4c9d86413c6 --- /dev/null +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/utils/getDisplayUnit.js @@ -0,0 +1,3 @@ +const getDisplayUnit = unit => (unit == null ? '' : unit); + +export default getDisplayUnit; diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/utils/index.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/utils/index.ts index e7cd75f7143..82ec07f842c 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/utils/index.ts +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/utils/index.ts @@ -5,6 +5,7 @@ import { getFirstAnnotationSelected, } from './selection'; import getSOPInstanceAttributes from './getSOPInstanceAttributes'; +import getDisplayUnit from './getDisplayUnit'; export { getHandlesFromPoints, @@ -12,4 +13,5 @@ export { isAnnotationSelected, setAnnotationSelected, getFirstAnnotationSelected, + getDisplayUnit, }; diff --git a/extensions/cornerstone/src/utils/measurementServiceMappings/utils/selection.ts b/extensions/cornerstone/src/utils/measurementServiceMappings/utils/selection.ts index 156133306de..1de09d9426f 100644 --- a/extensions/cornerstone/src/utils/measurementServiceMappings/utils/selection.ts +++ b/extensions/cornerstone/src/utils/measurementServiceMappings/utils/selection.ts @@ -18,24 +18,16 @@ function setAnnotationSelected(annotationUID: string, selected: boolean): void { const isCurrentSelected = isAnnotationSelected(annotationUID); // branch cut, avoid invoking imaging library unnecessarily. if (isCurrentSelected !== selected) { - cs3dToolAnnotationUtils.selection.setAnnotationSelected( - annotationUID, - selected - ); + cs3dToolAnnotationUtils.selection.setAnnotationSelected(annotationUID, selected); } } function getFirstAnnotationSelected(element) { - const [selectedAnnotationUID] = - cs3dToolAnnotationUtils.selection.getAnnotationsSelected() || []; + const [selectedAnnotationUID] = cs3dToolAnnotationUtils.selection.getAnnotationsSelected() || []; if (selectedAnnotationUID) { return cs3dToolAnnotationUtils.state.getAnnotation(selectedAnnotationUID); } } -export { - isAnnotationSelected, - setAnnotationSelected, - getFirstAnnotationSelected, -}; +export { isAnnotationSelected, setAnnotationSelected, getFirstAnnotationSelected }; diff --git a/extensions/cornerstone/src/utils/nthLoader.ts b/extensions/cornerstone/src/utils/nthLoader.ts index bce0d22f5fb..90aab65a8b4 100644 --- a/extensions/cornerstone/src/utils/nthLoader.ts +++ b/extensions/cornerstone/src/utils/nthLoader.ts @@ -51,9 +51,7 @@ export default function interleaveNthLoader({ .map(volume => volume.getImageLoadRequests()) .filter(requests => requests?.[0]?.imageId); - const orderedRequests = originalRequests.map(request => - getNthFrames(request) - ); + const orderedRequests = originalRequests.map(request => getNthFrames(request)); // set the finalRequests to the imageLoadPoolManager const finalRequests = interleave(orderedRequests); @@ -61,31 +59,17 @@ export default function interleaveNthLoader({ const requestType = Enums.RequestType.Prefetch; const priority = 0; - finalRequests.forEach( - ({ callLoadImage, additionalDetails, imageId, imageIdIndex, options }) => { - const callLoadImageBound = callLoadImage.bind( - null, - imageId, - imageIdIndex, - options - ); - - imageLoadPoolManager.addRequest( - callLoadImageBound, - requestType, - additionalDetails, - priority - ); - } - ); + finalRequests.forEach(({ callLoadImage, additionalDetails, imageId, imageIdIndex, options }) => { + const callLoadImageBound = callLoadImage.bind(null, imageId, imageIdIndex, options); + + imageLoadPoolManager.addRequest(callLoadImageBound, requestType, additionalDetails, priority); + }); // clear the volumeIdMapsToLoad volumeIdMapsToLoad.clear(); // copy the viewportIdVolumeInputArrayMap - const viewportIdVolumeInputArrayMapCopy = new Map( - viewportIdVolumeInputArrayMap - ); + const viewportIdVolumeInputArrayMapCopy = new Map(viewportIdVolumeInputArrayMap); // reset the viewportIdVolumeInputArrayMap viewportIdVolumeInputArrayMap.clear(); diff --git a/extensions/cornerstone/src/utils/removeToolGroupSegmentationRepresentations.ts b/extensions/cornerstone/src/utils/removeToolGroupSegmentationRepresentations.ts index 513321b32ff..b0f3e57968d 100644 --- a/extensions/cornerstone/src/utils/removeToolGroupSegmentationRepresentations.ts +++ b/extensions/cornerstone/src/utils/removeToolGroupSegmentationRepresentations.ts @@ -1,9 +1,7 @@ import { segmentation } from '@cornerstonejs/tools'; function removeToolGroupSegmentationRepresentations(toolGroupId) { - const representations = segmentation.state.getSegmentationRepresentations( - toolGroupId - ); + const representations = segmentation.state.getSegmentationRepresentations(toolGroupId); if (!representations || !representations.length) { return; diff --git a/extensions/cornerstone/src/utils/stackSync/toggleStackImageSync.ts b/extensions/cornerstone/src/utils/stackSync/toggleStackImageSync.ts deleted file mode 100644 index 5a0de4677f0..00000000000 --- a/extensions/cornerstone/src/utils/stackSync/toggleStackImageSync.ts +++ /dev/null @@ -1,115 +0,0 @@ -import calculateViewportRegistrations from './calculateViewportRegistrations'; - -// [ { -// synchronizerId: string, -// viewports: [ { viewportId: number, renderingEngineId: string, index: number } , ...] -// ]} -let STACK_IMAGE_SYNC_GROUPS_INFO = []; - -export default function toggleStackImageSync({ - toggledState, - servicesManager, - getEnabledElement, -}) { - const { - syncGroupService, - viewportGridService, - displaySetService, - cornerstoneViewportService, - } = servicesManager.services; - - if (!toggledState) { - STACK_IMAGE_SYNC_GROUPS_INFO.forEach(syncGroupInfo => { - const { viewports, synchronizerId } = syncGroupInfo; - - viewports.forEach(({ viewportId, renderingEngineId }) => { - syncGroupService.removeViewportFromSyncGroup( - viewportId, - renderingEngineId, - synchronizerId - ); - }); - }); - - return; - } - - STACK_IMAGE_SYNC_GROUPS_INFO = []; - - // create synchronization groups and add viewports - let { viewports } = viewportGridService.getState(); - - // filter empty viewports - viewports = viewports.filter( - viewport => - viewport.displaySetInstanceUIDs && viewport.displaySetInstanceUIDs.length - ); - - // filter reconstructable viewports - viewports = viewports.filter(viewport => { - const { displaySetInstanceUIDs } = viewport; - - for (const displaySetInstanceUID of displaySetInstanceUIDs) { - const displaySet = displaySetService.getDisplaySetByUID( - displaySetInstanceUID - ); - - if (displaySet && displaySet.isReconstructable) { - return true; - } - - return false; - } - }); - - const viewportsByOrientation = viewports.reduce((acc, viewport) => { - const { viewportId, viewportType } = viewport.viewportOptions; - - if (viewportType !== 'stack') { - console.warn('Viewport is not a stack, cannot sync images yet'); - return acc; - } - - const { element } = cornerstoneViewportService.getViewportInfo(viewportId); - const { viewport: csViewport, renderingEngineId } = getEnabledElement( - element - ); - const { viewPlaneNormal } = csViewport.getCamera(); - - // Should we round here? I guess so, but not sure how much precision we need - const orientation = viewPlaneNormal.map(v => Math.round(v)).join(','); - - if (!acc[orientation]) { - acc[orientation] = []; - } - - acc[orientation].push({ viewportId, renderingEngineId }); - - return acc; - }, {}); - - // create synchronizer for each group - Object.values(viewportsByOrientation).map(viewports => { - let synchronizerId = viewports - .map(({ viewportId }) => viewportId) - .join(','); - - synchronizerId = `imageSync_${synchronizerId}`; - - calculateViewportRegistrations(viewports); - - viewports.forEach(({ viewportId, renderingEngineId }) => { - syncGroupService.addViewportToSyncGroup(viewportId, renderingEngineId, { - type: 'stackimage', - id: synchronizerId, - source: true, - target: true, - }); - }); - - STACK_IMAGE_SYNC_GROUPS_INFO.push({ - synchronizerId, - viewports, - }); - }); -} diff --git a/extensions/default/.webpack/webpack.dev.js b/extensions/default/.webpack/webpack.dev.js index 96d68096d9d..6aea859ca74 100644 --- a/extensions/default/.webpack/webpack.dev.js +++ b/extensions/default/.webpack/webpack.dev.js @@ -7,7 +7,6 @@ const ENTRY = { app: `${SRC_DIR}/index.tsx`, }; - module.exports = (env, argv) => { return webpackCommon(env, argv, { SRC_DIR, DIST_DIR, ENTRY }); }; diff --git a/extensions/default/.webpack/webpack.prod.js b/extensions/default/.webpack/webpack.prod.js index ea7b2e5bb02..1151efc8b2e 100644 --- a/extensions/default/.webpack/webpack.prod.js +++ b/extensions/default/.webpack/webpack.prod.js @@ -40,13 +40,7 @@ module.exports = (env, argv) => { libraryTarget: 'umd', filename: pkg.main, }, - externals: [ - /\b(vtk.js)/, - /\b(dcmjs)/, - /\b(gl-matrix)/, - /^@ohif/, - /^@cornerstonejs/, - ], + externals: [/\b(vtk.js)/, /\b(dcmjs)/, /\b(gl-matrix)/, /^@ohif/, /^@cornerstonejs/], plugins: [ new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1, diff --git a/extensions/default/CHANGELOG.md b/extensions/default/CHANGELOG.md new file mode 100644 index 00000000000..8976c4ad6c8 --- /dev/null +++ b/extensions/default/CHANGELOG.md @@ -0,0 +1,1029 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [3.8.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.59...v3.8.0-beta.60) (2024-03-15) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.58...v3.8.0-beta.59) (2024-03-08) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.57...v3.8.0-beta.58) (2024-03-05) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.56...v3.8.0-beta.57) (2024-02-28) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.55...v3.8.0-beta.56) (2024-02-22) + + +### Bug Fixes + +* **demo:** Deploy issue ([#3951](https://github.com/OHIF/Viewers/issues/3951)) ([21e8a2b](https://github.com/OHIF/Viewers/commit/21e8a2bd0b7cc72f90a31e472d285d761be15d30)) + + + + + +# [3.8.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.54...v3.8.0-beta.55) (2024-02-21) + + +### Features + +* **resize:** Optimize resizing process and maintain zoom level ([#3889](https://github.com/OHIF/Viewers/issues/3889)) ([b3a0faf](https://github.com/OHIF/Viewers/commit/b3a0faf5f5f0a1993b2b017eb4cc1216164ea2c6)) + + + + + +# [3.8.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.53...v3.8.0-beta.54) (2024-02-14) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.52...v3.8.0-beta.53) (2024-02-05) + + +### Bug Fixes + +* 🐛 Sort merge results based on default data source (input) ([#3903](https://github.com/OHIF/Viewers/issues/3903)) ([5bba98e](https://github.com/OHIF/Viewers/commit/5bba98ed848bdf46b5ba4fc4708527cced3308b5)) + + + + + +# [3.8.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.51...v3.8.0-beta.52) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.50...v3.8.0-beta.51) (2024-01-22) + + +### Bug Fixes + +* catch errors in getPTImageIdInstanceMetadata ([#3897](https://github.com/OHIF/Viewers/issues/3897)) ([a47aeb8](https://github.com/OHIF/Viewers/commit/a47aeb8bd729dcb8d2cfc13b27a31b0dd88f11ad)) + + + + + +# [3.8.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.49...v3.8.0-beta.50) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.48...v3.8.0-beta.49) (2024-01-19) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.47...v3.8.0-beta.48) (2024-01-17) + + +### Bug Fixes + +* 🐛 Check merge key for merge data source ([#3901](https://github.com/OHIF/Viewers/issues/3901)) ([911d672](https://github.com/OHIF/Viewers/commit/911d67283536b2fe7930948f2819ea0ad66e2a32)) + + + + + +# [3.8.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.46...v3.8.0-beta.47) (2024-01-12) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.45...v3.8.0-beta.46) (2024-01-12) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.44...v3.8.0-beta.45) (2024-01-09) + + +### Features + +* **hp:** enable OHIF to run with partial metadata for large studies at the cost of less effective hanging protocol ([#3804](https://github.com/OHIF/Viewers/issues/3804)) ([0049f4c](https://github.com/OHIF/Viewers/commit/0049f4c0303f0b6ea995972326fc8784259f5a47)) + + + + + +# [3.8.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.43...v3.8.0-beta.44) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.42...v3.8.0-beta.43) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.41...v3.8.0-beta.42) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.40...v3.8.0-beta.41) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.39...v3.8.0-beta.40) (2024-01-08) + + +### Features + +* **ui:** sidePanel expandedWidth ([#3728](https://github.com/OHIF/Viewers/issues/3728)) ([61bf22c](https://github.com/OHIF/Viewers/commit/61bf22c6f80e764bdf5c3b56bb0124a95aa0f793)) + + + + + +# [3.8.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.38...v3.8.0-beta.39) (2024-01-08) + + +### Features + +* improve disableEditing flag ([#3875](https://github.com/OHIF/Viewers/issues/3875)) ([2049c09](https://github.com/OHIF/Viewers/commit/2049c0936c86f819604c243d3dc7b3fe971b5b2c)) + + + + + +# [3.8.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.37...v3.8.0-beta.38) (2024-01-08) + + +### Bug Fixes + +* PDF display request in v3 ([#3878](https://github.com/OHIF/Viewers/issues/3878)) ([9865030](https://github.com/OHIF/Viewers/commit/98650302c7575f0aea386e32cfc4112c378035e6)) + + + + + +# [3.8.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.36...v3.8.0-beta.37) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.35...v3.8.0-beta.36) (2023-12-15) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.34...v3.8.0-beta.35) (2023-12-14) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.33...v3.8.0-beta.34) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.32...v3.8.0-beta.33) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.31...v3.8.0-beta.32) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.30...v3.8.0-beta.31) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.29...v3.8.0-beta.30) (2023-12-13) + + +### Features + +* **customizationService:** Enable saving and loading of private tags in SRs ([#3842](https://github.com/OHIF/Viewers/issues/3842)) ([e1f55e6](https://github.com/OHIF/Viewers/commit/e1f55e65f2d2a34136ad5d0b1ada77d337a0ea23)) + + + + + +# [3.8.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.28...v3.8.0-beta.29) (2023-12-13) + + +### Features + +* **i18n:** enhanced i18n support ([#3761](https://github.com/OHIF/Viewers/issues/3761)) ([d14a8f0](https://github.com/OHIF/Viewers/commit/d14a8f0199db95cd9e85866a011b64d6bf830d57)) + + + + + +# [3.8.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.27...v3.8.0-beta.28) (2023-12-08) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.26...v3.8.0-beta.27) (2023-12-06) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.25...v3.8.0-beta.26) (2023-11-28) + + +### Bug Fixes + +* **SM:** drag and drop is now fixed for SM ([#3813](https://github.com/OHIF/Viewers/issues/3813)) ([f1a6764](https://github.com/OHIF/Viewers/commit/f1a67647aed635437b188cea7cf5d5a8fb974bbe)) + + + + + +# [3.8.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.24...v3.8.0-beta.25) (2023-11-27) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.23...v3.8.0-beta.24) (2023-11-24) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.22...v3.8.0-beta.23) (2023-11-24) + + +### Features + +* Merge Data Source ([#3788](https://github.com/OHIF/Viewers/issues/3788)) ([c4ff2c2](https://github.com/OHIF/Viewers/commit/c4ff2c2f09546ce8b72eab9c5e7beed611e3cab0)) + + + + + +# [3.8.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.21...v3.8.0-beta.22) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.20...v3.8.0-beta.21) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.19...v3.8.0-beta.20) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.18...v3.8.0-beta.19) (2023-11-18) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.17...v3.8.0-beta.18) (2023-11-15) + + +### Features + +* **url:** Add SeriesInstanceUIDs wado query param ([#3746](https://github.com/OHIF/Viewers/issues/3746)) ([b694228](https://github.com/OHIF/Viewers/commit/b694228dd535e4b97cb86a1dc085b6e8716bdaf3)) + + + + + +# [3.8.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.16...v3.8.0-beta.17) (2023-11-13) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.15...v3.8.0-beta.16) (2023-11-13) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.14...v3.8.0-beta.15) (2023-11-10) + + +### Features + +* **dicomJSON:** Add Loading Other Display Sets and JSON Metadata Generation script ([#3777](https://github.com/OHIF/Viewers/issues/3777)) ([43b1c17](https://github.com/OHIF/Viewers/commit/43b1c17209502e4876ad59bae09ed9442eda8024)) + + + + + +# [3.8.0-beta.14](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.13...v3.8.0-beta.14) (2023-11-10) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.13](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.12...v3.8.0-beta.13) (2023-11-09) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.12](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.11...v3.8.0-beta.12) (2023-11-08) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.11](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.10...v3.8.0-beta.11) (2023-11-08) + + +### Features + +* **hp callback:** Add viewport ready callback ([#3772](https://github.com/OHIF/Viewers/issues/3772)) ([bf252bc](https://github.com/OHIF/Viewers/commit/bf252bcec2aae3a00479fdcb732110b344bcf2c0)) + + + + + +# [3.8.0-beta.10](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.9...v3.8.0-beta.10) (2023-11-03) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.9](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.8...v3.8.0-beta.9) (2023-11-02) + + +### Bug Fixes + +* **thumbnail:** Avoid multiple promise creations for thumbnails ([#3756](https://github.com/OHIF/Viewers/issues/3756)) ([b23eeff](https://github.com/OHIF/Viewers/commit/b23eeff93745769e67e60c33d75293d6242c5ec9)) + + + + + +# [3.8.0-beta.8](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.7...v3.8.0-beta.8) (2023-10-31) + + +### Features + +* **i18n:** enhanced i18n support ([#3730](https://github.com/OHIF/Viewers/issues/3730)) ([330e11c](https://github.com/OHIF/Viewers/commit/330e11c7ff0151e1096e19b8ffdae7d64cae280e)) + + + + + +# [3.8.0-beta.7](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.6...v3.8.0-beta.7) (2023-10-30) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.6](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.5...v3.8.0-beta.6) (2023-10-25) + + +### Bug Fixes + +* **toolbar:** allow customizable toolbar for active viewport and allow active tool to be deactivated via a click ([#3608](https://github.com/OHIF/Viewers/issues/3608)) ([dd6d976](https://github.com/OHIF/Viewers/commit/dd6d9768bbca1d3cc472e8c1e6d85822500b96ef)) + + + + + +# [3.8.0-beta.5](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.4...v3.8.0-beta.5) (2023-10-24) + + +### Bug Fixes + +* **sr:** dcm4chee requires the patient name for an SR to match what is in the original study ([#3739](https://github.com/OHIF/Viewers/issues/3739)) ([d98439f](https://github.com/OHIF/Viewers/commit/d98439fe7f3825076dbc87b664a1d1480ff414d3)) + + + + + +# [3.8.0-beta.4](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.3...v3.8.0-beta.4) (2023-10-23) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.3](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.2...v3.8.0-beta.3) (2023-10-23) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.1...v3.8.0-beta.2) (2023-10-19) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.0...v3.8.0-beta.1) (2023-10-19) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.8.0-beta.0](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.110...v3.8.0-beta.0) (2023-10-12) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.7.0-beta.110](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.109...v3.7.0-beta.110) (2023-10-11) + + +### Bug Fixes + +* **display messages:** broken after timings ([#3719](https://github.com/OHIF/Viewers/issues/3719)) ([157b88c](https://github.com/OHIF/Viewers/commit/157b88c909d3289cb89ace731c1f9a19d40797ac)) + + + + + +# [3.7.0-beta.109](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.108...v3.7.0-beta.109) (2023-10-11) + + +### Bug Fixes + +* **export:** wrong export for the tmtv RT function ([#3715](https://github.com/OHIF/Viewers/issues/3715)) ([a3f2a1a](https://github.com/OHIF/Viewers/commit/a3f2a1a7b0d16bfcc0ecddc2ab731e54c5e377c8)) + + + + + +# [3.7.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.107...v3.7.0-beta.108) (2023-10-10) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.7.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.106...v3.7.0-beta.107) (2023-10-10) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.7.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.105...v3.7.0-beta.106) (2023-10-10) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.7.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.104...v3.7.0-beta.105) (2023-10-10) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.7.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.103...v3.7.0-beta.104) (2023-10-09) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.7.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.102...v3.7.0-beta.103) (2023-10-09) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.7.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.101...v3.7.0-beta.102) (2023-10-06) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.7.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.100...v3.7.0-beta.101) (2023-10-06) + + +### Bug Fixes + +* **bugs:** fixing lots of bugs regarding release candidate ([#3700](https://github.com/OHIF/Viewers/issues/3700)) ([8bc12a3](https://github.com/OHIF/Viewers/commit/8bc12a37d0353160ae5ea4624dc0b244b7d59c07)) + + + + + +# [3.7.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.99...v3.7.0-beta.100) (2023-10-06) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.7.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.98...v3.7.0-beta.99) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.7.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.97...v3.7.0-beta.98) (2023-10-04) + + +### Features + +* **locale:** add German translations - community PR ([#3697](https://github.com/OHIF/Viewers/issues/3697)) ([ebe8f71](https://github.com/OHIF/Viewers/commit/ebe8f71da22c1d24b58f889c5d803951e19817b6)) + + + + + +# [3.7.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.96...v3.7.0-beta.97) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.7.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.95...v3.7.0-beta.96) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.7.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.94...v3.7.0-beta.95) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.7.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.93...v3.7.0-beta.94) (2023-10-03) + + +### Features + +* **debug:** Add timing information about time to first image/all images, and query time ([#3681](https://github.com/OHIF/Viewers/issues/3681)) ([108383b](https://github.com/OHIF/Viewers/commit/108383b9ef51e4bef82d9c932b9bc7aa5354e799)) + + + + + +# [3.7.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.92...v3.7.0-beta.93) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.7.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.91...v3.7.0-beta.92) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.7.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.90...v3.7.0-beta.91) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.7.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.89...v3.7.0-beta.90) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.7.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.88...v3.7.0-beta.89) (2023-10-03) + + +### Bug Fixes + +* **StackSync:** Miscellaneous fixes for stack image sync ([#3663](https://github.com/OHIF/Viewers/issues/3663)) ([8a335bd](https://github.com/OHIF/Viewers/commit/8a335bd03d14ba87d65d7468d93f74040aa828d9)) + + + + + +# [3.7.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.87...v3.7.0-beta.88) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.7.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.86...v3.7.0-beta.87) (2023-09-29) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.7.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.85...v3.7.0-beta.86) (2023-09-29) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26) + + +### Bug Fixes + +* **toggleOneUp:** fixed one up for main tmtv layout ([#3677](https://github.com/OHIF/Viewers/issues/3677)) ([86f54d0](https://github.com/OHIF/Viewers/commit/86f54d0d07042750a863ae876aa8dd5fb16029a5)) + + + + + +# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22) + + +### Features + +* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1)) +* **SidePanel:** new side panel tab look-and-feel ([#3657](https://github.com/OHIF/Viewers/issues/3657)) ([85c899b](https://github.com/OHIF/Viewers/commit/85c899b399e2521480724be145538993721b9378)) + + + + + +# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18) + + +### Bug Fixes + +* **DicomJson:** retrieve.series.metadata method should be async ([#3659](https://github.com/OHIF/Viewers/issues/3659)) ([2737903](https://github.com/OHIF/Viewers/commit/2737903386cf97399473e0fa64fe53ad14da155a)) + + + + + +# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12) + + +### Bug Fixes + +* **health imaging:** studies not loading from healthimaging if imagepositionpatient is missing ([#3646](https://github.com/OHIF/Viewers/issues/3646)) ([74e62a1](https://github.com/OHIF/Viewers/commit/74e62a176374f720080d4e777972f70e7f2d8b2b)) + + + + + +# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12) + + +### Bug Fixes + +* **suv:** import calculate-suv library version that prevents SUV calculation for a zero PatientWeight ([#3638](https://github.com/OHIF/Viewers/issues/3638)) ([0d10f46](https://github.com/OHIF/Viewers/commit/0d10f46b885fe54ec3dae1848134da658eb6280a)) + + + + + +# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06) + + +### Bug Fixes + +* **hotkeys:** preserve hotkeys if changed, and reduce re-rendering ([#3635](https://github.com/OHIF/Viewers/issues/3635)) ([94f7cfb](https://github.com/OHIF/Viewers/commit/94f7cfb08e3490488394efc42ef089ebe55e86be)) + + + + + +# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01) + + +### Features + +* **grid:** remove viewportIndex and only rely on viewportId ([#3591](https://github.com/OHIF/Viewers/issues/3591)) ([4c6ff87](https://github.com/OHIF/Viewers/commit/4c6ff873e887cc30ffc09223f5cb99e5f94c9cdd)) + + + + + +# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30) + + +### Features + +* **data source UI config:** Popup the configuration dialogue whenever a data source is not fully configured ([#3620](https://github.com/OHIF/Viewers/issues/3620)) ([adedc8c](https://github.com/OHIF/Viewers/commit/adedc8c382e18a2e86a569e3d023cc55a157363f)) + + + + + +# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29) + + +### Bug Fixes + +* **PT Metadata:** Allow for PatientWeight to be missing from the metadata ([#3621](https://github.com/OHIF/Viewers/issues/3621)) ([44f101d](https://github.com/OHIF/Viewers/commit/44f101d3f2b3204b67e31f4e4939062e65a246ee)) + + + + + +# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-default + + + + + +# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25) + + +### Features + +* **cloud data source config:** GUI and API for configuring a cloud data source with Google cloud healthcare implementation ([#3589](https://github.com/OHIF/Viewers/issues/3589)) ([a336992](https://github.com/OHIF/Viewers/commit/a336992971c07552c9dbb6e1de43169d37762ef1)) + + + + + +# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23) + +**Note:** Version bump only for package @ohif/extension-default diff --git a/extensions/default/babel.config.js b/extensions/default/babel.config.js new file mode 100644 index 00000000000..325ca2a8ee7 --- /dev/null +++ b/extensions/default/babel.config.js @@ -0,0 +1 @@ +module.exports = require('../../babel.config.js'); diff --git a/extensions/default/jest.config.js b/extensions/default/jest.config.js new file mode 100644 index 00000000000..ba90c0c4724 --- /dev/null +++ b/extensions/default/jest.config.js @@ -0,0 +1,17 @@ +const base = require('../../jest.config.base.js'); +const pkg = require('./package'); + +module.exports = { + ...base, + name: pkg.name, + displayName: pkg.name, + moduleNameMapper: { + ...base.moduleNameMapper, + '@ohif/(.*)': '/../../platform/$1/src', + }, + // rootDir: "../.." + // testMatch: [ + // //`/platform/${pack.name}/**/*.spec.js` + // "/platform/app/**/*.test.js" + // ] +}; diff --git a/extensions/default/package.json b/extensions/default/package.json index 2c166f1127d..3f292fbc9d4 100644 --- a/extensions/default/package.json +++ b/extensions/default/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-default", - "version": "3.7.0-beta.46", + "version": "3.8.0-beta.60", "description": "Common/default features and functionality for basic image viewing", "author": "OHIF Core Team", "license": "MIT", @@ -23,6 +23,8 @@ "ohif-extension" ], "scripts": { + "clean": "shx rm -rf dist", + "clean:deep": "yarn run clean && shx rm -rf node_modules", "dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --output-pathinfo", "dev:dicom-pdf": "yarn run dev", "build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js", @@ -30,10 +32,10 @@ "start": "yarn run dev" }, "peerDependencies": { - "@ohif/core": "3.7.0-beta.46", - "@ohif/i18n": "3.7.0-beta.46", - "dcmjs": "^0.29.5", - "dicomweb-client": "^0.10.2", + "@ohif/core": "3.8.0-beta.60", + "@ohif/i18n": "3.8.0-beta.60", + "dcmjs": "^0.29.12", + "dicomweb-client": "^0.10.4", "prop-types": "^15.6.2", "react": "^17.0.2", "react-dom": "^17.0.2", @@ -44,6 +46,6 @@ }, "dependencies": { "@babel/runtime": "^7.20.13", - "@cornerstonejs/calculate-suv": "^1.0.3" + "@cornerstonejs/calculate-suv": "^1.1.0" } } diff --git a/extensions/default/src/Actions/createReportAsync.tsx b/extensions/default/src/Actions/createReportAsync.tsx index 98080288b0d..d0f34a48a23 100644 --- a/extensions/default/src/Actions/createReportAsync.tsx +++ b/extensions/default/src/Actions/createReportAsync.tsx @@ -4,53 +4,31 @@ import { DicomMetadataStore } from '@ohif/core'; /** * * @param {*} servicesManager - * @param {*} dataSource - * @param {*} measurements - * @param {*} options - * @returns {string[]} displaySetInstanceUIDs */ -async function createReportAsync( - servicesManager, - commandsManager, - dataSource, - measurements, - options -) { - const { - displaySetService, - uiNotificationService, - uiDialogService, - } = servicesManager.services; +async function createReportAsync({ servicesManager, getReport, reportType = 'measurement' }) { + const { displaySetService, uiNotificationService, uiDialogService } = servicesManager.services; const loadingDialogId = uiDialogService.create({ showOverlay: true, isDraggable: false, centralize: true, - // TODO: Create a loading indicator component + zeplin design? content: Loading, }); try { - const naturalizedReport = await commandsManager.runCommand( - 'storeMeasurements', - { - measurementData: measurements, - dataSource, - additionalFindingTypes: ['ArrowAnnotate'], - options, - }, - 'CORNERSTONE_STRUCTURED_REPORT' - ); + const naturalizedReport = await getReport(); // The "Mode" route listens for DicomMetadataStore changes // When a new instance is added, it listens and // automatically calls makeDisplaySets DicomMetadataStore.addInstances([naturalizedReport], true); - const displaySetInstanceUID = displaySetService.getMostRecentDisplaySet(); + const displaySet = displaySetService.getMostRecentDisplaySet(); + + const displaySetInstanceUID = displaySet.displaySetInstanceUID; uiNotificationService.show({ title: 'Create Report', - message: 'Measurements saved successfully', + message: `${reportType} saved successfully`, type: 'success', }); @@ -58,7 +36,7 @@ async function createReportAsync( } catch (error) { uiNotificationService.show({ title: 'Create Report', - message: error.message || 'Failed to store measurements', + message: error.message || `Failed to store ${reportType}`, type: 'error', }); } finally { diff --git a/extensions/default/src/Components/DataSourceConfigurationComponent.tsx b/extensions/default/src/Components/DataSourceConfigurationComponent.tsx new file mode 100644 index 00000000000..97896a92c7f --- /dev/null +++ b/extensions/default/src/Components/DataSourceConfigurationComponent.tsx @@ -0,0 +1,121 @@ +import React, { ReactElement, useCallback, useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Icon, useModal } from '@ohif/ui'; +import { ExtensionManager, ServicesManager, Types } from '@ohif/core'; +import DataSourceConfigurationModalComponent from './DataSourceConfigurationModalComponent'; + +type DataSourceConfigurationComponentProps = { + servicesManager: ServicesManager; + extensionManager: ExtensionManager; +}; + +function DataSourceConfigurationComponent({ + servicesManager, + extensionManager, +}: DataSourceConfigurationComponentProps): ReactElement { + const { t } = useTranslation('DataSourceConfiguration'); + const { show, hide } = useModal(); + + const { customizationService } = servicesManager.services; + + const [configurationAPI, setConfigurationAPI] = useState(); + + const [configuredItems, setConfiguredItems] = + useState>(); + + useEffect(() => { + let shouldUpdate = true; + + const dataSourceChangedCallback = async () => { + const activeDataSourceDef = extensionManager.getActiveDataSourceDefinition(); + + if (!activeDataSourceDef.configuration.configurationAPI) { + return; + } + + const { factory: configurationAPIFactory } = + customizationService.get(activeDataSourceDef.configuration.configurationAPI) ?? {}; + + if (!configurationAPIFactory) { + return; + } + + const configAPI = configurationAPIFactory(activeDataSourceDef.sourceName); + setConfigurationAPI(configAPI); + + // New configuration API means that the existing configured items must be cleared. + setConfiguredItems(null); + + configAPI.getConfiguredItems().then(list => { + if (shouldUpdate) { + setConfiguredItems(list); + } + }); + }; + + const sub = extensionManager.subscribe( + extensionManager.EVENTS.ACTIVE_DATA_SOURCE_CHANGED, + dataSourceChangedCallback + ); + + dataSourceChangedCallback(); + + return () => { + shouldUpdate = false; + sub.unsubscribe(); + }; + }, []); + + const showConfigurationModal = useCallback(() => { + show({ + content: DataSourceConfigurationModalComponent, + title: t('Configure Data Source'), + contentProps: { + configurationAPI, + configuredItems, + onHide: hide, + }, + }); + }, [configurationAPI, configuredItems]); + + useEffect(() => { + if (!configurationAPI || !configuredItems) { + return; + } + + if (configuredItems.length !== configurationAPI.getItemLabels().length) { + // Not the correct number of configured items, so show the modal to configure the data source. + showConfigurationModal(); + } + }, [configurationAPI, configuredItems, showConfigurationModal]); + + return configuredItems ? ( +
+ + {configuredItems.map((item, itemIndex) => { + return ( +
+
+ {item.name} +
+ {itemIndex !== configuredItems.length - 1 &&
|
} +
+ ); + })} +
+ ) : ( + <> + ); +} + +export default DataSourceConfigurationComponent; diff --git a/extensions/default/src/Components/DataSourceConfigurationModalComponent.tsx b/extensions/default/src/Components/DataSourceConfigurationModalComponent.tsx new file mode 100644 index 00000000000..c709a45e193 --- /dev/null +++ b/extensions/default/src/Components/DataSourceConfigurationModalComponent.tsx @@ -0,0 +1,195 @@ +import classNames from 'classnames'; +import React, { ReactElement, useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Icon } from '@ohif/ui'; +import { Types } from '@ohif/core'; +import ItemListComponent from './ItemListComponent'; + +const NO_WRAP_ELLIPSIS_CLASS_NAMES = 'text-ellipsis whitespace-nowrap overflow-hidden'; + +type DataSourceConfigurationModalComponentProps = { + configurationAPI: Types.BaseDataSourceConfigurationAPI; + configuredItems: Array; + onHide: () => void; +}; + +function DataSourceConfigurationModalComponent({ + configurationAPI, + configuredItems, + onHide, +}: DataSourceConfigurationModalComponentProps) { + const { t } = useTranslation('DataSourceConfiguration'); + + const [itemList, setItemList] = useState>(); + + const [selectedItems, setSelectedItems] = useState(configuredItems); + + const [errorMessage, setErrorMessage] = useState(); + + const [itemLabels] = useState(configurationAPI.getItemLabels()); + + // Determines whether to show the full/existing configuration for the data source. + // A full or complete configuration is one where the data source (path) has the + // maximum/required number of path items. Anything less is considered not complete and + // the configuration starts from scratch (i.e. as if no items are configured at all). + // TODO: consider configuration starting from a partial (i.e. non-empty) configuration + const [showFullConfig, setShowFullConfig] = useState( + itemLabels.length === configuredItems.length + ); + + /** + * The index of the selected item that is considered current and for which + * its sub-items should be displayed in the items list component. When the + * full/existing configuration for a data source is to be shown, the current + * selected item is the second to last in the `selectedItems` list. + */ + const currentSelectedItemIndex = showFullConfig + ? selectedItems.length - 2 + : selectedItems.length - 1; + + useEffect(() => { + let shouldUpdate = true; + + setErrorMessage(null); + + // Clear out the former/old list while we fetch the next sub item list. + setItemList(null); + + if (selectedItems.length === 0) { + configurationAPI + .initialize() + .then(items => { + if (shouldUpdate) { + setItemList(items); + } + }) + .catch(error => setErrorMessage(error.message)); + } else if (!showFullConfig && selectedItems.length === itemLabels.length) { + // The last item to configure the data source (path) has been selected. + configurationAPI.setCurrentItem(selectedItems[selectedItems.length - 1]); + // We can hide the modal dialog now. + onHide(); + } else { + configurationAPI + .setCurrentItem(selectedItems[currentSelectedItemIndex]) + .then(items => { + if (shouldUpdate) { + setItemList(items); + } + }) + .catch(error => setErrorMessage(error.message)); + } + + return () => { + shouldUpdate = false; + }; + }, [ + selectedItems, + configurationAPI, + onHide, + itemLabels, + showFullConfig, + currentSelectedItemIndex, + ]); + + const getSelectedItemCursorClasses = itemIndex => + itemIndex !== itemLabels.length - 1 && itemIndex < selectedItems.length + ? 'cursor-pointer' + : 'cursor-auto'; + + const getSelectedItemBackgroundClasses = itemIndex => + itemIndex < selectedItems.length + ? classNames( + 'bg-black/[.4]', + itemIndex !== itemLabels.length - 1 ? 'hover:bg-transparent active:bg-secondary-dark' : '' + ) + : 'bg-transparent'; + + const getSelectedItemBorderClasses = itemIndex => + itemIndex === currentSelectedItemIndex + 1 + ? classNames('border-2', 'border-solid', 'border-primary-light') + : itemIndex < selectedItems.length + ? 'border border-solid border-primary-active hover:border-primary-light active:border-white' + : 'border border-dashed border-secondary-light'; + + const getSelectedItemTextClasses = itemIndex => + itemIndex <= selectedItems.length ? 'text-primary-light' : 'text-primary-active'; + + const getErrorComponent = (): ReactElement => { + return ( +
+
+ {t(`Error fetching ${itemLabels[selectedItems.length]} list`)} +
+
{errorMessage}
+
+ ); + }; + + const getSelectedItemsComponent = (): ReactElement => { + return ( +
+ {itemLabels.map((itemLabel, itemLabelIndex) => { + return ( +
{ + setShowFullConfig(false); + setSelectedItems(theList => theList.slice(0, itemLabelIndex)); + } + : undefined + } + > +
+ {itemLabelIndex < selectedItems.length ? ( + + ) : ( + + )} +
{t(itemLabel)}
+
+ {itemLabelIndex < selectedItems.length ? ( +
+ {selectedItems[itemLabelIndex].name} +
+ ) : ( +

+ )} +
+ ); + })} +
+ ); + }; + + return ( +
+ {getSelectedItemsComponent()} +
+ {errorMessage ? ( + getErrorComponent() + ) : ( + { + setShowFullConfig(false); + setSelectedItems(theList => [...theList.slice(0, currentSelectedItemIndex + 1), item]); + }} + > + )} +
+ ); +} + +export default DataSourceConfigurationModalComponent; diff --git a/extensions/default/src/Components/ItemListComponent.tsx b/extensions/default/src/Components/ItemListComponent.tsx new file mode 100644 index 00000000000..d8c30714f09 --- /dev/null +++ b/extensions/default/src/Components/ItemListComponent.tsx @@ -0,0 +1,86 @@ +import classNames from 'classnames'; +import React, { ReactElement, useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Button, Icon, InputFilterText, LoadingIndicatorProgress } from '@ohif/ui'; +import { Types } from '@ohif/core'; + +type ItemListComponentProps = { + itemLabel: string; + itemList: Array; + onItemClicked: (item: Types.BaseDataSourceConfigurationAPIItem) => void; +}; + +function ItemListComponent({ + itemLabel, + itemList, + onItemClicked, +}: ItemListComponentProps): ReactElement { + const { t } = useTranslation('DataSourceConfiguration'); + const [filterValue, setFilterValue] = useState(''); + + useEffect(() => { + setFilterValue(''); + }, [itemList]); + + return ( +
+
+
{t(`Select ${itemLabel}`)}
+ +
+
+ {itemList == null ? ( + + ) : itemList.length === 0 ? ( +
+ + {t(`No ${itemLabel} available`)} +
+ ) : ( + <> +
{t(itemLabel)}
+
+ {itemList + .filter( + item => + !filterValue || item.name.toLowerCase().includes(filterValue.toLowerCase()) + ) + .map(item => { + const border = + 'rounded border-transparent border-b-secondary-light border-[1px] hover:border-primary-light'; + return ( +
+
{item.name}
+ +
+ ); + })} +
+ + )} +
+
+ ); +} + +export default ItemListComponent; diff --git a/extensions/default/src/Components/SidePanelWithServices.tsx b/extensions/default/src/Components/SidePanelWithServices.tsx new file mode 100644 index 00000000000..5e6ba007457 --- /dev/null +++ b/extensions/default/src/Components/SidePanelWithServices.tsx @@ -0,0 +1,63 @@ +import React, { useEffect, useState } from 'react'; +import { SidePanel } from '@ohif/ui'; +import { PanelService, ServicesManager } from '@ohif/core'; + +export type SidePanelWithServicesProps = { + servicesManager: ServicesManager; + side: 'left' | 'right'; + className: string; + activeTabIndex: number; + tabs: any; + expandedWidth?: number; +}; + +const SidePanelWithServices = ({ + servicesManager, + side, + className, + activeTabIndex: activeTabIndexProp, + tabs, + expandedWidth, +}: SidePanelWithServicesProps) => { + const panelService: PanelService = servicesManager?.services?.panelService; + + // Tracks whether this SidePanel has been opened at least once since this SidePanel was inserted into the DOM. + // Thus going to the Study List page and back to the viewer resets this flag for a SidePanel. + const [hasBeenOpened, setHasBeenOpened] = useState(false); + const [activeTabIndex, setActiveTabIndex] = useState(activeTabIndexProp); + + useEffect(() => { + if (panelService) { + const activatePanelSubscription = panelService.subscribe( + panelService.EVENTS.ACTIVATE_PANEL, + (activatePanelEvent: Types.ActivatePanelEvent) => { + if (!hasBeenOpened || activatePanelEvent.forceActive) { + const tabIndex = tabs.findIndex(tab => tab.id === activatePanelEvent.panelId); + if (tabIndex !== -1) { + setActiveTabIndex(tabIndex); + } + } + } + ); + + return () => { + activatePanelSubscription.unsubscribe(); + }; + } + }, [tabs, hasBeenOpened, panelService]); + + return ( + { + setHasBeenOpened(true); + }} + expandedWidth={expandedWidth} + > + ); +}; + +export default SidePanelWithServices; diff --git a/extensions/default/src/CustomizableContextMenu/ContextMenuController.tsx b/extensions/default/src/CustomizableContextMenu/ContextMenuController.tsx index 82879bf02af..75292906364 100644 --- a/extensions/default/src/CustomizableContextMenu/ContextMenuController.tsx +++ b/extensions/default/src/CustomizableContextMenu/ContextMenuController.tsx @@ -1,6 +1,7 @@ import * as ContextMenuItemsBuilder from './ContextMenuItemsBuilder'; import ContextMenu from '../../../../platform/ui/src/components/ContextMenu/ContextMenu'; import { CommandsManager, ServicesManager, Types } from '@ohif/core'; +import { annotation as CsAnnotation } from '@cornerstonejs/tools'; import { Menu, MenuItem, Point, ContextMenuProps } from './types'; /** @@ -19,10 +20,7 @@ export default class ContextMenuController { services: Types.Services; menuItems: Menu[] | MenuItem[]; - constructor( - servicesManager: ServicesManager, - commandsManager: CommandsManager - ) { + constructor(servicesManager: ServicesManager, commandsManager: CommandsManager) { this.services = servicesManager.services as Obj; this.commandsManager = commandsManager; } @@ -50,6 +48,18 @@ export default class ContextMenuController { const { event, subMenu, menuId, menus, selectorProps } = contextMenuProps; + const annotationManager = CsAnnotation.state.getAnnotationManager(); + const { locking } = CsAnnotation; + const targetAnnotationId = selectorProps?.nearbyToolData?.annotationUID as string; + const isLocked = locking.isAnnotationLocked( + annotationManager.getAnnotation(targetAnnotationId) + ); + + if (isLocked) { + console.warn('Annotation is locked.'); + return; + } + console.log('Getting items from', menus); const items = ContextMenuItemsBuilder.getMenuItems( selectorProps || contextMenuProps, @@ -72,10 +82,9 @@ export default class ContextMenuController { event, content: ContextMenu, - // This naming is part of hte uiDialogService convention - // Clicking outside simpy closes the dialog box. - onClickOutside: () => - this.services.uiDialogService.dismiss({ id: 'context-menu' }), + // This naming is part of the uiDialogService convention + // Clicking outside simply closes the dialog box. + onClickOutside: () => this.services.uiDialogService.dismiss({ id: 'context-menu' }), contentProps: { items, @@ -170,9 +179,7 @@ export default class ContextMenuController { }; static _isValidPosition = (source): boolean => { - return ( - source && typeof source.x === 'number' && typeof source.y === 'number' - ); + return source && typeof source.x === 'number' && typeof source.y === 'number'; }; /** @@ -180,10 +187,7 @@ export default class ContextMenuController { */ static _getDefaultPosition = (canvasPoints, eventDetail, viewerElement) => { function* getPositionIterator() { - yield ContextMenuController._getCanvasPointsPosition( - canvasPoints, - viewerElement - ); + yield ContextMenuController._getCanvasPointsPosition(canvasPoints, viewerElement); yield ContextMenuController._getEventDefaultPosition(eventDetail); yield ContextMenuController._getElementDefaultPosition(viewerElement); yield ContextMenuController.getDefaultPosition(); diff --git a/extensions/default/src/CustomizableContextMenu/ContextMenuItemsBuilder.test.js b/extensions/default/src/CustomizableContextMenu/ContextMenuItemsBuilder.test.js index b5555f71f12..2231f750341 100644 --- a/extensions/default/src/CustomizableContextMenu/ContextMenuItemsBuilder.test.js +++ b/extensions/default/src/CustomizableContextMenu/ContextMenuItemsBuilder.test.js @@ -1,14 +1,14 @@ -import ContextMenuItemsBuilder from "./ContextMenuItemsBuilder"; +import * as ContextMenuItemsBuilder from './ContextMenuItemsBuilder'; const menus = [ { id: 'one', - selector: ({ value }) => value === 'one', + selector: ({ value } = {}) => value === 'one', items: [], }, { id: 'two', - selector: ({ value }) => value === 'two', + selector: ({ value } = {}) => value === 'two', items: [], }, { @@ -17,13 +17,13 @@ const menus = [ }, ]; -const menuBuilder = new ContextMenuItemsBuilder(); - describe('ContextMenuItemsBuilder', () => { test('findMenuDefault', () => { - expect(menuBuilder.findMenuDefault(menus, {})).toBe(menus[2]); - expect(menuBuilder.findMenuDefault(menus, { value: 'two' })).toBe(menus[1]); - expect(menuBuilder.findMenuDefault([], {})).toBeUndefined(); - expect(menuBuilder.findMenuDefault(undefined, undefined)).toBeNull(); + expect(ContextMenuItemsBuilder.findMenuDefault(menus, {})).toBe(menus[2]); + expect( + ContextMenuItemsBuilder.findMenuDefault(menus, { selectorProps: { value: 'two' } }) + ).toBe(menus[1]); + expect(ContextMenuItemsBuilder.findMenuDefault([], {})).toBeUndefined(); + expect(ContextMenuItemsBuilder.findMenuDefault(undefined, undefined)).toBeNull(); }); }); diff --git a/extensions/default/src/CustomizableContextMenu/ContextMenuItemsBuilder.ts b/extensions/default/src/CustomizableContextMenu/ContextMenuItemsBuilder.ts index 2fe20e8d96d..8b8b985326f 100644 --- a/extensions/default/src/CustomizableContextMenu/ContextMenuItemsBuilder.ts +++ b/extensions/default/src/CustomizableContextMenu/ContextMenuItemsBuilder.ts @@ -31,16 +31,11 @@ export function findMenuById(menus: Menu[], menuId?: string): Menu { * @param {*} subProps * @returns */ -export function findMenuDefault( - menus: Menu[], - subProps: Record -): Menu { +export function findMenuDefault(menus: Menu[], subProps: Record): Menu { if (!menus) { return null; } - return menus.find( - menu => !menu.selector || menu.selector(subProps.selectorProps) - ); + return menus.find(menu => !menu.selector || menu.selector(subProps.selectorProps)); } /** @@ -53,11 +48,7 @@ export function findMenuDefault( * @param menuIdFilter - menu id identifier (to be considered on selection) * This is intended to support other types of filtering in the future. */ -export function findMenu( - menus: Menu[], - props?: Types.IProps, - menuIdFilter?: string -) { +export function findMenu(menus: Menu[], props?: Types.IProps, menuIdFilter?: string) { const { subMenu } = props; function* findMenuIterator() { @@ -137,10 +128,7 @@ export function getMenuItems( if (!selector || selector(selectorProps)) { if (delegating) { - menuItems = [ - ...menuItems, - ...getMenuItems(selectorProps, event, menus, subMenu), - ]; + menuItems = [...menuItems, ...getMenuItems(selectorProps, event, menus, subMenu)]; } else { const toAdd = adaptItem(item, subProps); menuItems.push(toAdd); @@ -161,10 +149,7 @@ export function getMenuItems( * @returns a MenuItem that is compatible with the base ContextMenu * This requires having a label and set of actions to be called. */ -export function adaptItem( - item: MenuItem, - subProps: ContextMenuProps -): ContextMenuItem { +export function adaptItem(item: MenuItem, subProps: ContextMenuProps): ContextMenuItem { const newItem: ContextMenuItem = { ...item, value: subProps.selectorProps?.value, diff --git a/extensions/default/src/CustomizableContextMenu/types.ts b/extensions/default/src/CustomizableContextMenu/types.ts index 23075d05787..c251fd33d79 100644 --- a/extensions/default/src/CustomizableContextMenu/types.ts +++ b/extensions/default/src/CustomizableContextMenu/types.ts @@ -5,7 +5,7 @@ import { Types } from '@ohif/core'; * menu item for display. * An instance of SelectorProps is provided to the selector functions, which * return true to include the item or false to exclude it. - * The point of this is to allow more specific conext menus which hide + * The point of this is to allow more specific context menus which hide * non-relevant menu options, optimizing the speed of selection of menus */ export interface SelectorProps { @@ -69,7 +69,7 @@ export interface MenuItem { // or more importantly, if the delegating subMenu will be included. selector?: (props: SelectorProps) => boolean; - /** Adapts the item by filling in additional properties as requried */ + /** Adapts the item by filling in additional properties as required */ adaptItem?: (item: MenuItem, props: ContextMenuProps) => UIMenuItem; /** List of commands to run when this item's action is taken. */ diff --git a/extensions/default/src/DataSourceConfigurationAPI/GoogleCloudDataSourceConfigurationAPI.ts b/extensions/default/src/DataSourceConfigurationAPI/GoogleCloudDataSourceConfigurationAPI.ts new file mode 100644 index 00000000000..4e02cafae9f --- /dev/null +++ b/extensions/default/src/DataSourceConfigurationAPI/GoogleCloudDataSourceConfigurationAPI.ts @@ -0,0 +1,236 @@ +import { ExtensionManager, Types } from '@ohif/core'; + +/** + * This file contains the implementations of BaseDataSourceConfigurationAPIItem + * and BaseDataSourceConfigurationAPI for the Google cloud healthcare API. To + * better understand this implementation and/or to implement custom implementations, + * see the platform\core\src\types\DataSourceConfigurationAPI.ts and its JS doc + * comments as a guide. + */ + +/** + * The various Google Cloud Healthcare path item types. + */ +enum ItemType { + projects = 0, + locations = 1, + datasets = 2, + dicomStores = 3, +} + +interface NamedItem { + name: string; +} +interface Project extends NamedItem { + projectId: string; +} + +const initialUrl = 'https://cloudresourcemanager.googleapis.com/v1'; +const baseHealthcareUrl = 'https://healthcare.googleapis.com/v1'; + +class GoogleCloudDataSourceConfigurationAPIItem + implements Types.BaseDataSourceConfigurationAPIItem +{ + id: string; + name: string; + url: string; + itemType: ItemType; +} + +class GoogleCloudDataSourceConfigurationAPI implements Types.BaseDataSourceConfigurationAPI { + private _extensionManager: ExtensionManager; + private _fetchOptions: { method: string; headers: unknown }; + private _dataSourceName: string; + + constructor(dataSourceName, servicesManager, extensionManager) { + this._dataSourceName = dataSourceName; + this._extensionManager = extensionManager; + const userAuthenticationService = servicesManager.services.userAuthenticationService; + this._fetchOptions = { + method: 'GET', + headers: userAuthenticationService.getAuthorizationHeader(), + }; + } + + getItemLabels = () => ['Project', 'Location', 'Data set', 'DICOM store']; + + async initialize(): Promise { + const url = `${initialUrl}/projects`; + + const projects = (await GoogleCloudDataSourceConfigurationAPI._doFetch( + url, + ItemType.projects, + this._fetchOptions + )) as Array; + + if (!projects?.length) { + return []; + } + + const projectItems = projects.map(project => { + return { + id: project.projectId, + name: project.name, + itemType: ItemType.projects, + url: `${baseHealthcareUrl}/projects/${project.projectId}`, + }; + }); + + return projectItems; + } + + async setCurrentItem( + anItem: Types.BaseDataSourceConfigurationAPIItem + ): Promise { + const googleCloudItem = anItem as GoogleCloudDataSourceConfigurationAPIItem; + + if (googleCloudItem.itemType === ItemType.dicomStores) { + // Last configurable item, so update the data source configuration. + const url = `${googleCloudItem.url}/dicomWeb`; + const dataSourceDefCopy = JSON.parse( + JSON.stringify(this._extensionManager.getDataSourceDefinition(this._dataSourceName)) + ); + dataSourceDefCopy.configuration = { + ...dataSourceDefCopy.configuration, + wadoUriRoot: url, + qidoRoot: url, + wadoRoot: url, + }; + + this._extensionManager.updateDataSourceConfiguration( + dataSourceDefCopy.sourceName, + dataSourceDefCopy.configuration + ); + + return []; + } + + const subItemType = googleCloudItem.itemType + 1; + const subItemField = `${ItemType[subItemType]}`; + + const url = `${googleCloudItem.url}/${subItemField}`; + + const fetchedSubItems = await GoogleCloudDataSourceConfigurationAPI._doFetch( + url, + subItemType, + this._fetchOptions + ); + + if (!fetchedSubItems?.length) { + return []; + } + + const subItems = fetchedSubItems.map(subItem => { + const nameSplit = subItem.name.split('/'); + return { + id: subItem.name, + name: nameSplit[nameSplit.length - 1], + itemType: subItemType, + url: `${baseHealthcareUrl}/${subItem.name}`, + }; + }); + + return subItems; + } + + async getConfiguredItems(): Promise> { + const dataSourceDefinition = this._extensionManager.getDataSourceDefinition( + this._dataSourceName + ); + + const url = dataSourceDefinition.configuration.wadoUriRoot; + const projectsIndex = url.indexOf('projects'); + // Split the configured URL into (essentially) pairs (i.e. item type followed by item) + // Explicitly: ['projects','aProject','locations','aLocation','datasets','aDataSet','dicomStores','aDicomStore'] + // Note that a partial configuration will have a subset of the above. + const urlSplit = url.substring(projectsIndex).split('/'); + + const configuredItems = []; + + for ( + let itemType = 0; + // the number of configured items is either the max (4) or the number extracted from the url split + itemType < 4 && (itemType + 1) * 2 < urlSplit.length; + itemType += 1 + ) { + if (itemType === ItemType.projects) { + const projectId = urlSplit[1]; + const projectUrl = `${initialUrl}/projects/${projectId}`; + const data = await GoogleCloudDataSourceConfigurationAPI._doFetch( + projectUrl, + ItemType.projects, + this._fetchOptions + ); + const project = data[0] as Project; + configuredItems.push({ + id: project.projectId, + name: project.name, + itemType: itemType, + url: `${baseHealthcareUrl}/projects/${project.projectId}`, + }); + } else { + const relativePath = urlSplit.slice(0, itemType * 2 + 2).join('/'); + configuredItems.push({ + id: relativePath, + name: urlSplit[itemType * 2 + 1], + itemType: itemType, + url: `${baseHealthcareUrl}/${relativePath}`, + }); + } + } + + return configuredItems; + } + + /** + * Fetches an array of items the specified item type. + * @param urlStr the fetch url + * @param fetchItemType the type to fetch + * @param fetchOptions the header options for the fetch (e.g. authorization header) + * @param fetchSearchParams any search query params; currently only used for paging results + * @returns an array of items of the specified type + */ + private static async _doFetch( + urlStr: string, + fetchItemType: ItemType, + fetchOptions = {}, + fetchSearchParams: Record = {} + ): Promise | Array> { + try { + const url = new URL(urlStr); + url.search = new URLSearchParams(fetchSearchParams).toString(); + + const response = await fetch(url, fetchOptions); + const data = await response.json(); + if (response.status >= 200 && response.status < 300 && data != null) { + if (data.nextPageToken != null) { + fetchSearchParams.pageToken = data.nextPageToken; + const subPageData = await this._doFetch( + urlStr, + fetchItemType, + fetchOptions, + fetchSearchParams + ); + data[ItemType[fetchItemType]] = data[ItemType[fetchItemType]].concat(subPageData); + } + if (data[ItemType[fetchItemType]]) { + return data[ItemType[fetchItemType]]; + } else if (data.name) { + return [data]; + } else { + return []; + } + } else { + const message = + data?.error?.message || + `Error returned from Google Cloud Healthcare: ${response.status} - ${response.statusText}`; + throw new Error(message); + } + } catch (err) { + const message = err?.message || 'Error occurred during fetch request.'; + throw new Error(message); + } + } +} + +export { GoogleCloudDataSourceConfigurationAPI }; diff --git a/extensions/default/src/DicomJSONDataSource/index.js b/extensions/default/src/DicomJSONDataSource/index.js index a78d5ddaa7f..6c7ae45ee69 100644 --- a/extensions/default/src/DicomJSONDataSource/index.js +++ b/extensions/default/src/DicomJSONDataSource/index.js @@ -25,6 +25,23 @@ let _store = { // } }; +function wrapSequences(obj) { + return Object.keys(obj).reduce( + (acc, key) => { + if (typeof obj[key] === 'object' && obj[key] !== null) { + // Recursively wrap sequences for nested objects + acc[key] = wrapSequences(obj[key]); + } else { + acc[key] = obj[key]; + } + if (key.endsWith('Sequence')) { + acc[key] = OHIF.utils.addAccessors(acc[key]); + } + return acc; + }, + Array.isArray(obj) ? [] : {} + ); +} const getMetaDataByURL = url => { return _store.urls.find(metaData => metaData.url === url); }; @@ -122,18 +139,18 @@ function createDicomJSONApi(dicomJsonConfig) { }); }, processResults: () => { - console.debug(' DICOMJson QUERY processResults'); + console.warn(' DICOMJson QUERY processResults not implemented'); }, }, series: { // mapParams: mapParams.bind(), search: () => { - console.debug(' DICOMJson QUERY SERIES SEARCH'); + console.warn(' DICOMJson QUERY SERIES SEARCH not implemented'); }, }, instances: { search: () => { - console.debug(' DICOMJson QUERY instances SEARCH'); + console.warn(' DICOMJson QUERY instances SEARCH not implemented'); }, }, }, @@ -155,15 +172,9 @@ function createDicomJSONApi(dicomJsonConfig) { return getDirectURL(wadoRoot, params); }, series: { - metadata: ({ - StudyInstanceUID, - madeInClient = false, - customSort, - } = {}) => { + metadata: async ({ StudyInstanceUID, madeInClient = false, customSort } = {}) => { if (!StudyInstanceUID) { - throw new Error( - 'Unable to query for SeriesMetadata without StudyInstanceUID' - ); + throw new Error('Unable to query for SeriesMetadata without StudyInstanceUID'); } const study = findStudies('StudyInstanceUID', StudyInstanceUID)[0]; @@ -189,24 +200,24 @@ function createDicomJSONApi(dicomJsonConfig) { DicomMetadataStore.addInstances(naturalizedInstances, madeInClient); } - DicomMetadataStore.addSeriesMetadata( - seriesSummaryMetadata, - madeInClient - ); + DicomMetadataStore.addSeriesMetadata(seriesSummaryMetadata, madeInClient); function setSuccessFlag() { - const study = DicomMetadataStore.getStudy( - StudyInstanceUID, - madeInClient - ); + const study = DicomMetadataStore.getStudy(StudyInstanceUID, madeInClient); study.isLoaded = true; } const numberOfSeries = series.length; series.forEach((series, index) => { const instances = series.instances.map(instance => { + // for instance.metadata if the key ends with sequence then + // we need to add a proxy to the first item in the sequence + // so that we can access the value of the sequence + // by using sequenceName.value + const modifiedMetadata = wrapSequences(instance.metadata); + const obj = { - ...instance.metadata, + ...modifiedMetadata, url: instance.url, imageId: instance.url, ...series, @@ -226,7 +237,7 @@ function createDicomJSONApi(dicomJsonConfig) { }, store: { dicom: () => { - console.debug(' DICOMJson store dicom'); + console.warn(' DICOMJson store dicom not implemented'); }, }, getImageIdsForDisplaySet(displaySet) { diff --git a/extensions/default/src/DicomLocalDataSource/index.js b/extensions/default/src/DicomLocalDataSource/index.js index aa9949981b7..a7628e80a9c 100644 --- a/extensions/default/src/DicomLocalDataSource/index.js +++ b/extensions/default/src/DicomLocalDataSource/index.js @@ -85,7 +85,7 @@ function createDicomLocalApi(dicomLocalConfig) { }); }, processResults: () => { - console.debug(' DICOMLocal QUERY processResults'); + console.warn(' DICOMLocal QUERY processResults not implemented'); }, }, series: { @@ -107,7 +107,7 @@ function createDicomLocalApi(dicomLocalConfig) { }, instances: { search: () => { - console.debug(' DICOMLocal QUERY instances SEARCH'); + console.warn(' DICOMLocal QUERY instances SEARCH not implemented'); }, }, }, @@ -127,16 +127,11 @@ function createDicomLocalApi(dicomLocalConfig) { series: { metadata: async ({ StudyInstanceUID, madeInClient = false } = {}) => { if (!StudyInstanceUID) { - throw new Error( - 'Unable to query for SeriesMetadata without StudyInstanceUID' - ); + throw new Error('Unable to query for SeriesMetadata without StudyInstanceUID'); } // Instances metadata already added via local upload - const study = DicomMetadataStore.getStudy( - StudyInstanceUID, - madeInClient - ); + const study = DicomMetadataStore.getStudy(StudyInstanceUID, madeInClient); // Series metadata already added via local upload DicomMetadataStore._broadcastEvent(EVENTS.SERIES_ADDED, { @@ -236,8 +231,7 @@ function createDicomLocalApi(dicomLocalConfig) { const { StudyInstanceUIDs: paramsStudyInstanceUIDs } = params; const queryStudyInstanceUIDs = query.getAll('StudyInstanceUIDs'); - const StudyInstanceUIDs = - queryStudyInstanceUIDs || paramsStudyInstanceUIDs; + const StudyInstanceUIDs = queryStudyInstanceUIDs || paramsStudyInstanceUIDs; const StudyInstanceUIDsAsArray = StudyInstanceUIDs && Array.isArray(StudyInstanceUIDs) ? StudyInstanceUIDs diff --git a/extensions/default/src/DicomTagBrowser/DicomTagBrowser.css b/extensions/default/src/DicomTagBrowser/DicomTagBrowser.css index fe6a9609631..2845f2f39c1 100644 --- a/extensions/default/src/DicomTagBrowser/DicomTagBrowser.css +++ b/extensions/default/src/DicomTagBrowser/DicomTagBrowser.css @@ -4,7 +4,7 @@ } .dicom-tag-browser-table-wrapper { -/* height: 500px;*/ + /* height: 500px;*/ /*overflow-y: scroll;*/ overflow-x: scroll; } @@ -45,7 +45,7 @@ padding-left: 10px; padding-right: 10px; text-align: center; - color: "#20A5D6"; + color: '#20A5D6'; } .dicom-tag-browser-table th.dicom-tag-browser-table-left { diff --git a/extensions/default/src/DicomTagBrowser/DicomTagBrowser.tsx b/extensions/default/src/DicomTagBrowser/DicomTagBrowser.tsx index 57cf04fa83c..22eea9bc85a 100644 --- a/extensions/default/src/DicomTagBrowser/DicomTagBrowser.tsx +++ b/extensions/default/src/DicomTagBrowser/DicomTagBrowser.tsx @@ -1,10 +1,9 @@ import dcmjs from 'dcmjs'; import moment from 'moment'; -import React, { useState, useMemo, useEffect, useRef } from 'react'; +import React, { useState, useMemo, useEffect } from 'react'; import { classes } from '@ohif/core'; -import { Icon, InputRange, Select, Typography } from '@ohif/ui'; +import { InputRange, Select, Typography, InputFilterText } from '@ohif/ui'; import debounce from 'lodash.debounce'; -import classNames from 'classnames'; import DicomTagTable from './DicomTagTable'; import './DicomTagBrowser.css'; @@ -22,10 +21,8 @@ const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => { // 3: Value const excludedColumnIndicesForFilter: Set = new Set([1]); - const [ - selectedDisplaySetInstanceUID, - setSelectedDisplaySetInstanceUID, - ] = useState(displaySetInstanceUID); + const [selectedDisplaySetInstanceUID, setSelectedDisplaySetInstanceUID] = + useState(displaySetInstanceUID); const [instanceNumber, setInstanceNumber] = useState(1); const [filterValue, setFilterValue] = useState(''); @@ -34,8 +31,6 @@ const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => { setInstanceNumber(1); }; - const searchInputRef = useRef(null); - const activeDisplaySet = displaySets.find( ds => ds.displaySetInstanceUID === selectedDisplaySetInstanceUID ); @@ -113,27 +108,31 @@ const DicomTagBrowser = ({ displaySets, displaySetInstanceUID }) => { return (
-
-
- +
+
+ Series -
+
debouncedSetFilterValue(event.target.value)} - autoComplete="off" - > - - { - searchInputRef.current.value = ''; - debouncedSetFilterValue(''); - }} - > - - +
+
+
@@ -196,24 +173,14 @@ function getFormattedRowsFromTags(tags, metadata) { tags.forEach(tagInfo => { if (tagInfo.vr === 'SQ') { - rows.push([ - `${tagInfo.tagIndent}${tagInfo.tag}`, - tagInfo.vr, - tagInfo.keyword, - '', - ]); + rows.push([`${tagInfo.tagIndent}${tagInfo.tag}`, tagInfo.vr, tagInfo.keyword, '']); const { values } = tagInfo; values.forEach((item, index) => { const formatedRowsFromTags = getFormattedRowsFromTags(item, metadata); - rows.push([ - `${item[0].tagIndent}(FFFE,E000)`, - '', - `Item #${index}`, - '', - ]); + rows.push([`${item[0].tagIndent}(FFFE,E000)`, '', `Item #${index}`, '']); rows.push(...formatedRowsFromTags); }); @@ -224,17 +191,10 @@ function getFormattedRowsFromTags(tags, metadata) { const originalTagInfo = metadata[tag]; tagInfo.vr = originalTagInfo.vr; } catch (error) { - console.error( - `Failed to parse value representation for tag '${tagInfo.keyword}'` - ); + console.error(`Failed to parse value representation for tag '${tagInfo.keyword}'`); } } - rows.push([ - `${tagInfo.tagIndent}${tagInfo.tag}`, - tagInfo.vr, - tagInfo.keyword, - tagInfo.value, - ]); + rows.push([`${tagInfo.tagIndent}${tagInfo.tag}`, tagInfo.vr, tagInfo.keyword, tagInfo.value]); } }); diff --git a/extensions/default/src/DicomTagBrowser/DicomTagTable.tsx b/extensions/default/src/DicomTagBrowser/DicomTagTable.tsx index 982af013b48..a661da9f7bb 100644 --- a/extensions/default/src/DicomTagBrowser/DicomTagTable.tsx +++ b/extensions/default/src/DicomTagBrowser/DicomTagTable.tsx @@ -17,48 +17,40 @@ function ColumnHeaders({ tagRef, vrRef, keywordRef, valueRef }) { return (
-
+
-
+
-
+
-
+
@@ -114,10 +106,7 @@ function DicomTagTable({ rows }) { * When the browser window resizes, update the row virtualization (i.e. row heights) */ useEffect(() => { - const debouncedResize = debounce( - () => listRef.current.resetAfterIndex(0), - 100 - ); + const debouncedResize = debounce(() => listRef.current.resetAfterIndex(0), 100); window.addEventListener('resize', debouncedResize); @@ -135,15 +124,15 @@ function DicomTagTable({ rows }) {
-
{row[0]}
-
{row[1]}
-
{row[2]}
-
{row[3]}
+
{row[0]}
+
{row[1]}
+
{row[2]}
+
{row[3]}
); }, @@ -154,9 +143,7 @@ function DicomTagTable({ rows }) { * Whenever any one of the column headers is set, then the header is rendered. * Here we chose the tag header. */ - const isHeaderRendered = useCallback(() => tagHeaderElem !== null, [ - tagHeaderElem, - ]); + const isHeaderRendered = useCallback(() => tagHeaderElem !== null, [tagHeaderElem]); /** * Get the item/row size. We use the header column widths to calculate the various row heights. @@ -179,11 +166,7 @@ function DicomTagTable({ rows }) { .map((colText, index) => { const colOneLineWidth = context.measureText(colText).width; const numLines = Math.ceil(colOneLineWidth / headerWidths[index]); - return ( - numLines * lineHeightPx + - 2 * rowVerticalPaddingPx + - rowBottomBorderPx - ); + return numLines * lineHeightPx + 2 * rowVerticalPaddingPx + rowBottomBorderPx; }) .reduce((maxHeight, colHeight) => Math.max(maxHeight, colHeight)); }, @@ -204,7 +187,7 @@ function DicomTagTable({ rows }) { valueRef={valueRef} />
{isHeaderRendered() && ( diff --git a/extensions/default/src/DicomWebDataSource/dcm4cheeReject.js b/extensions/default/src/DicomWebDataSource/dcm4cheeReject.js index e902aa09598..542267abefb 100644 --- a/extensions/default/src/DicomWebDataSource/dcm4cheeReject.js +++ b/extensions/default/src/DicomWebDataSource/dcm4cheeReject.js @@ -1,4 +1,4 @@ -export default function(wadoRoot) { +export default function (wadoRoot) { return { series: (StudyInstanceUID, SeriesInstanceUID) => { return new Promise((resolve, reject) => { @@ -15,7 +15,7 @@ export default function(wadoRoot) { console.log(xhr); - xhr.onreadystatechange = function() { + xhr.onreadystatechange = function () { //Call a function when the state changes. if (xhr.readyState == 4) { switch (xhr.status) { diff --git a/extensions/default/src/DicomWebDataSource/index.js b/extensions/default/src/DicomWebDataSource/index.js index efbcf944b1c..23c5329c40f 100644 --- a/extensions/default/src/DicomWebDataSource/index.js +++ b/extensions/default/src/DicomWebDataSource/index.js @@ -1,11 +1,5 @@ import { api } from 'dicomweb-client'; -import { - DicomMetadataStore, - IWebApiDataSource, - utils, - errorHandler, - classes, -} from '@ohif/core'; +import { DicomMetadataStore, IWebApiDataSource, utils, errorHandler, classes } from '@ohif/core'; import { mapParams, @@ -18,10 +12,7 @@ import dcm4cheeReject from './dcm4cheeReject'; import getImageId from './utils/getImageId'; import dcmjs from 'dcmjs'; -import { - retrieveStudyMetadata, - deleteStudyMetadataPromise, -} from './retrieveStudyMetadata.js'; +import { retrieveStudyMetadata, deleteStudyMetadataPromise } from './retrieveStudyMetadata.js'; import StaticWadoClient from './utils/StaticWadoClient'; import getDirectURL from '../utils/getDirectURL'; import { fixBulkDataURI } from './utils/fixBulkDataURI'; @@ -30,27 +21,45 @@ const { DicomMetaDictionary, DicomDict } = dcmjs.data; const { naturalizeDataset, denaturalizeDataset } = DicomMetaDictionary; -const ImplementationClassUID = - '2.25.270695996825855179949881587723571202391.2.0.0'; +const ImplementationClassUID = '2.25.270695996825855179949881587723571202391.2.0.0'; const ImplementationVersionName = 'OHIF-VIEWER-2.0.0'; const EXPLICIT_VR_LITTLE_ENDIAN = '1.2.840.10008.1.2.1'; const metadataProvider = classes.MetadataProvider; /** + * Creates a DICOM Web API based on the provided configuration. * - * @param {string} name - Data source name - * @param {string} wadoUriRoot - Legacy? (potentially unused/replaced) - * @param {string} qidoRoot - Base URL to use for QIDO requests - * @param {string} wadoRoot - Base URL to use for WADO requests - * @param {boolean} qidoSupportsIncludeField - Whether QIDO supports the "Include" option to request additional fields in response - * @param {string} imageRengering - wadors | ? (unsure of where/how this is used) - * @param {string} thumbnailRendering - wadors | ? (unsure of where/how this is used) - * @param {bool} supportsReject - Whether the server supports reject calls (i.e. DCM4CHEE) - * @param {bool} lazyLoadStudy - "enableStudyLazyLoad"; Request series meta async instead of blocking - * @param {string|bool} singlepart - indicates of the retrieves can fetch singlepart. Options are bulkdata, video, image or boolean true + * @param {object} dicomWebConfig - Configuration for the DICOM Web API + * @param {string} dicomWebConfig.name - Data source name + * @param {string} dicomWebConfig.wadoUriRoot - Legacy? (potentially unused/replaced) + * @param {string} dicomWebConfig.qidoRoot - Base URL to use for QIDO requests + * @param {string} dicomWebConfig.wadoRoot - Base URL to use for WADO requests + * @param {string} dicomWebConfig.wadoUri - Base URL to use for WADO URI requests + * @param {boolean} dicomWebConfig.qidoSupportsIncludeField - Whether QIDO supports the "Include" option to request additional fields in response + * @param {string} dicomWebConfig.imageRendering - wadors | ? (unsure of where/how this is used) + * @param {string} dicomWebConfig.thumbnailRendering - wadors | ? (unsure of where/how this is used) + * @param {boolean} dicomWebConfig.supportsReject - Whether the server supports reject calls (i.e. DCM4CHEE) + * @param {boolean} dicomWebConfig.lazyLoadStudy - "enableStudyLazyLoad"; Request series meta async instead of blocking + * @param {string|boolean} dicomWebConfig.singlepart - indicates if the retrieves can fetch singlepart. Options are bulkdata, video, image, or boolean true + * @param {string} dicomWebConfig.requestTransferSyntaxUID - Transfer syntax to request from the server + * @param {object} dicomWebConfig.acceptHeader - Accept header to use for requests + * @param {boolean} dicomWebConfig.omitQuotationForMultipartRequest - Whether to omit quotation marks for multipart requests + * @param {boolean} dicomWebConfig.supportsFuzzyMatching - Whether the server supports fuzzy matching + * @param {boolean} dicomWebConfig.supportsWildcard - Whether the server supports wildcard matching + * @param {boolean} dicomWebConfig.supportsNativeDICOMModel - Whether the server supports the native DICOM model + * @param {boolean} dicomWebConfig.enableStudyLazyLoad - Whether to enable study lazy loading + * @param {boolean} dicomWebConfig.enableRequestTag - Whether to enable request tag + * @param {boolean} dicomWebConfig.enableStudyLazyLoad - Whether to enable study lazy loading + * @param {boolean} dicomWebConfig.bulkDataURI - Whether to enable bulkDataURI + * @param {function} dicomWebConfig.onConfiguration - Function that is called after the configuration is initialized + * @param {boolean} dicomWebConfig.staticWado - Whether to use the static WADO client + * @param {object} userAuthenticationService - User authentication service + * @param {object} userAuthenticationService.getAuthorizationHeader - Function that returns the authorization header + * @returns {object} - DICOM Web API object */ -function createDicomWebApi(dicomWebConfig, userAuthenticationService) { +function createDicomWebApi(dicomWebConfig, servicesManager) { + const { userAuthenticationService, customizationService } = servicesManager.services; let dicomWebConfigCopy, qidoConfig, wadoConfig, @@ -61,10 +70,7 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { const implementation = { initialize: ({ params, query }) => { - if ( - dicomWebConfig.onConfiguration && - typeof dicomWebConfig.onConfiguration === 'function' - ) { + if (dicomWebConfig.onConfiguration && typeof dicomWebConfig.onConfiguration === 'function') { dicomWebConfig = dicomWebConfig.onConfiguration(dicomWebConfig, { params, query, @@ -126,7 +132,7 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { query: { studies: { mapParams: mapParams.bind(), - search: async function(origParams) { + search: async function (origParams) { qidoDicomWebClient.headers = getAuthrorizationHeader(); const { studyInstanceUid, seriesInstanceUid, ...mappedParams } = mapParams(origParams, { @@ -134,12 +140,7 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { supportsWildcard: dicomWebConfig.supportsWildcard, }) || {}; - const results = await qidoSearch( - qidoDicomWebClient, - undefined, - undefined, - mappedParams - ); + const results = await qidoSearch(qidoDicomWebClient, undefined, undefined, mappedParams); return processResults(results); }, @@ -147,12 +148,9 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { }, series: { // mapParams: mapParams.bind(), - search: async function(studyInstanceUid) { + search: async function (studyInstanceUid) { qidoDicomWebClient.headers = getAuthrorizationHeader(); - const results = await seriesInStudy( - qidoDicomWebClient, - studyInstanceUid - ); + const results = await seriesInStudy(qidoDicomWebClient, studyInstanceUid); return processSeriesResults(results); }, @@ -161,7 +159,7 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { instances: { search: (studyInstanceUid, queryParameters) => { qidoDicomWebClient.headers = getAuthrorizationHeader(); - qidoSearch.call( + return qidoSearch.call( undefined, qidoDicomWebClient, studyInstanceUid, @@ -211,11 +209,10 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { sortCriteria, sortFunction, madeInClient = false, + returnPromises = false, } = {}) => { if (!StudyInstanceUID) { - throw new Error( - 'Unable to query for SeriesMetadata without StudyInstanceUID' - ); + throw new Error('Unable to query for SeriesMetadata without StudyInstanceUID'); } if (dicomWebConfig.enableStudyLazyLoad) { @@ -224,7 +221,8 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { filters, sortCriteria, sortFunction, - madeInClient + madeInClient, + returnPromises ); } @@ -240,7 +238,7 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { }, store: { - dicom: async (dataset, request) => { + dicom: async (dataset, request, dicomDict) => { wadoDicomWebClient.headers = getAuthrorizationHeader(); if (dataset instanceof ArrayBuffer) { const options = { @@ -249,22 +247,26 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { }; await wadoDicomWebClient.storeInstances(options); } else { - const meta = { - FileMetaInformationVersion: - dataset._meta.FileMetaInformationVersion.Value, - MediaStorageSOPClassUID: dataset.SOPClassUID, - MediaStorageSOPInstanceUID: dataset.SOPInstanceUID, - TransferSyntaxUID: EXPLICIT_VR_LITTLE_ENDIAN, - ImplementationClassUID, - ImplementationVersionName, - }; + let effectiveDicomDict = dicomDict; + + if (!dicomDict) { + const meta = { + FileMetaInformationVersion: dataset._meta?.FileMetaInformationVersion?.Value, + MediaStorageSOPClassUID: dataset.SOPClassUID, + MediaStorageSOPInstanceUID: dataset.SOPInstanceUID, + TransferSyntaxUID: EXPLICIT_VR_LITTLE_ENDIAN, + ImplementationClassUID, + ImplementationVersionName, + }; - const denaturalized = denaturalizeDataset(meta); - const dicomDict = new DicomDict(denaturalized); + const denaturalized = denaturalizeDataset(meta); + const defaultDicomDict = new DicomDict(denaturalized); + defaultDicomDict.dict = denaturalizeDataset(dataset); - dicomDict.dict = denaturalizeDataset(dataset); + effectiveDicomDict = defaultDicomDict; + } - const part10Buffer = dicomDict.write(); + const part10Buffer = effectiveDicomDict.write(); const options = { datasets: [part10Buffer], @@ -292,7 +294,8 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { enableStudyLazyLoad, filters, sortCriteria, - sortFunction + sortFunction, + dicomWebConfig ); // first naturalize the data @@ -325,6 +328,8 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { }); instance.imageId = imageId; + instance.wadoRoot = dicomWebConfig.wadoRoot; + instance.wadoUri = dicomWebConfig.wadoUri; metadataProvider.addImageIdToUIDs(imageId, { StudyInstanceUID, @@ -340,11 +345,10 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { DicomMetadataStore.addSeriesMetadata(seriesMetadata, madeInClient); Object.keys(instancesPerSeries).forEach(seriesInstanceUID => - DicomMetadataStore.addInstances( - instancesPerSeries[seriesInstanceUID], - madeInClient - ) + DicomMetadataStore.addInstances(instancesPerSeries[seriesInstanceUID], madeInClient) ); + + return seriesSummaryMetadata; }, _retrieveSeriesMetadataAsync: async ( @@ -352,22 +356,22 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { filters, sortCriteria, sortFunction, - madeInClient = false + madeInClient = false, + returnPromises = false ) => { const enableStudyLazyLoad = true; wadoDicomWebClient.headers = generateWadoHeader(); // Get Series - const { - preLoadData: seriesSummaryMetadata, - promises: seriesPromises, - } = await retrieveStudyMetadata( - wadoDicomWebClient, - StudyInstanceUID, - enableStudyLazyLoad, - filters, - sortCriteria, - sortFunction - ); + const { preLoadData: seriesSummaryMetadata, promises: seriesPromises } = + await retrieveStudyMetadata( + wadoDicomWebClient, + StudyInstanceUID, + enableStudyLazyLoad, + filters, + sortCriteria, + sortFunction, + dicomWebConfig + ); /** * naturalizes the dataset, and adds a retrieve bulkdata method @@ -378,7 +382,7 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { const addRetrieveBulkData = instance => { const naturalized = naturalizeDataset(instance); - // if we konw the server doesn't use bulkDataURI, then don't + // if we know the server doesn't use bulkDataURI, then don't if (!dicomWebConfig.bulkDataURI?.enabled) { return naturalized; } @@ -390,11 +394,12 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { // in which case it isn't necessary to re-read this. if (value && value.BulkDataURI && !value.Value) { // Provide a method to fetch bulkdata - value.retrieveBulkData = () => { + value.retrieveBulkData = (options = {}) => { // handle the scenarios where bulkDataURI is relative path fixBulkDataURI(value, naturalized, dicomWebConfig); - const options = { + const { mediaType } = options; + const useOptions = { // The bulkdata fetches work with either multipart or // singlepart, so set multipart to false to let the server // decide which type to respond with. @@ -405,15 +410,18 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { // isn't well specified in the standard, but is needed in // any implementation that stores static copies of the metadata StudyInstanceUID: naturalized.StudyInstanceUID, + mediaTypes: mediaType + ? [{ mediaType }, { mediaType: 'application/octet-stream' }] + : undefined, + ...options, }; // Todo: this needs to be from wado dicom web client - return qidoDicomWebClient.retrieveBulkData(options).then(val => { + return qidoDicomWebClient.retrieveBulkData(useOptions).then(val => { // There are DICOM PDF cases where the first ArrayBuffer in the array is // the bulk data and DICOM video cases where the second ArrayBuffer is // the bulk data. Here we play it safe and do a find. const ret = - (val instanceof Array && - val.find(arrayBuffer => arrayBuffer?.byteLength)) || + (val instanceof Array && val.find(arrayBuffer => arrayBuffer?.byteLength)) || undefined; value.Value = ret; return ret; @@ -429,7 +437,7 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { const naturalizedInstances = instances.map(addRetrieveBulkData); // Adding instanceMetadata to OHIF MetadataProvider - naturalizedInstances.forEach((instance, index) => { + naturalizedInstances.forEach(instance => { instance.wadoRoot = dicomWebConfig.wadoRoot; instance.wadoUri = dicomWebConfig.wadoUri; @@ -456,10 +464,10 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { } function setSuccessFlag() { - const study = DicomMetadataStore.getStudy( - StudyInstanceUID, - madeInClient - ); + const study = DicomMetadataStore.getStudy(StudyInstanceUID); + if (!study) { + return; + } study.isLoaded = true; } @@ -471,13 +479,24 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { DicomMetadataStore.addSeriesMetadata(seriesSummaryMetadata, madeInClient); - const seriesDeliveredPromises = seriesPromises.map(promise => - promise.then(instances => { + const seriesDeliveredPromises = seriesPromises.map(promise => { + if (!returnPromises) { + promise?.start(); + } + return promise.then(instances => { storeInstances(instances); - }) - ); - await Promise.all(seriesDeliveredPromises); - setSuccessFlag(); + }); + }); + + if (returnPromises) { + Promise.all(seriesDeliveredPromises).then(() => setSuccessFlag()); + return seriesPromises; + } else { + await Promise.all(seriesDeliveredPromises); + setSuccessFlag(); + } + + return seriesSummaryMetadata; }, deleteStudyMetadataPromise, getImageIdsForDisplaySet(displaySet) { @@ -507,7 +526,7 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { return imageIds; }, - getImageIdsForInstance({ instance, frame }) { + getImageIdsForInstance({ instance, frame = undefined }) { const imageIds = getImageId({ instance, frame, @@ -520,13 +539,10 @@ function createDicomWebApi(dicomWebConfig, userAuthenticationService) { }, getStudyInstanceUIDs({ params, query }) { const { StudyInstanceUIDs: paramsStudyInstanceUIDs } = params; - const queryStudyInstanceUIDs = utils.splitComma( - query.getAll('StudyInstanceUIDs') - ); + const queryStudyInstanceUIDs = utils.splitComma(query.getAll('StudyInstanceUIDs')); const StudyInstanceUIDs = - (queryStudyInstanceUIDs.length && queryStudyInstanceUIDs) || - paramsStudyInstanceUIDs; + (queryStudyInstanceUIDs.length && queryStudyInstanceUIDs) || paramsStudyInstanceUIDs; const StudyInstanceUIDsAsArray = StudyInstanceUIDs && Array.isArray(StudyInstanceUIDs) ? StudyInstanceUIDs diff --git a/extensions/default/src/DicomWebDataSource/qido.js b/extensions/default/src/DicomWebDataSource/qido.js index ce372cd5fc6..4bea9a07472 100644 --- a/extensions/default/src/DicomWebDataSource/qido.js +++ b/extensions/default/src/DicomWebDataSource/qido.js @@ -54,10 +54,7 @@ function processResults(qidoStudies) { patientName: utils.formatPN(getName(qidoStudy['00100010'])) || '', instances: Number(getString(qidoStudy['00201208'])) || 0, // number description: getString(qidoStudy['00081030']) || '', - modalities: - getString( - getModalities(qidoStudy['00080060'], qidoStudy['00080061']) - ) || '', + modalities: getString(getModalities(qidoStudy['00080060'], qidoStudy['00080061'])) || '', }) ); @@ -105,12 +102,7 @@ export function processSeriesResults(qidoSeries) { * @param {string} [queryParamaters] * @returns {Promise} - Promise that resolves results */ -async function search( - dicomWebClient, - studyInstanceUid, - seriesInstanceUid, - queryParameters -) { +async function search(dicomWebClient, studyInstanceUid, seriesInstanceUid, queryParameters) { let searchResult = await dicomWebClient.searchForStudies({ studyInstanceUid: undefined, queryParams: queryParameters, @@ -136,10 +128,7 @@ export function seriesInStudy(dicomWebClient, studyInstanceUID) { } export default function searchStudies(server, filter) { - const queryParams = getQIDOQueryParams( - filter, - server.qidoSupportsIncludeField - ); + const queryParams = getQIDOQueryParams(filter, server.qidoSupportsIncludeField); const options = { queryParams, }; diff --git a/extensions/default/src/DicomWebDataSource/retrieveStudyMetadata.js b/extensions/default/src/DicomWebDataSource/retrieveStudyMetadata.js index faf5e2c9010..f05115333a5 100644 --- a/extensions/default/src/DicomWebDataSource/retrieveStudyMetadata.js +++ b/extensions/default/src/DicomWebDataSource/retrieveStudyMetadata.js @@ -1,3 +1,4 @@ +import retrieveMetadataFiltered from './utils/retrieveMetadataFiltered.js'; import RetrieveMetadata from './wado/retrieveMetadata.js'; const moduleName = 'RetrieveStudyMetadata'; @@ -5,14 +6,17 @@ const moduleName = 'RetrieveStudyMetadata'; const StudyMetaDataPromises = new Map(); /** - * Retrieves study metadata + * Retrieves study metadata. * - * @param {Object} server Object with server configuration parameters + * @param {Object} dicomWebClient The DICOMWebClient instance to be used for series load * @param {string} StudyInstanceUID The UID of the Study to be retrieved - * @param {boolean} enabledStudyLazyLoad Whether the study metadata should be loaded asynchronusly. - * @param {function} storeInstancesCallback A callback used to store the retrieved instance metadata. - * @param {Object} [filters] - Object containing filters to be applied on retrieve metadata process - * @param {string} [filter.seriesInstanceUID] - series instance uid to filter results against + * @param {boolean} enableStudyLazyLoad Whether the study metadata should be loaded asynchronously. + * @param {Object} [filters] Object containing filters to be applied on retrieve metadata process + * @param {string} [filters.seriesInstanceUID] Series instance uid to filter results against + * @param {array} [filters.SeriesInstanceUIDs] Series instance uids to filter results against + * @param {function} [sortCriteria] Sort criteria function + * @param {function} [sortFunction] Sort function + * * @returns {Promise} that will be resolved with the metadata or rejected with the error */ export function retrieveStudyMetadata( @@ -21,54 +25,65 @@ export function retrieveStudyMetadata( enableStudyLazyLoad, filters, sortCriteria, - sortFunction + sortFunction, + dicomWebConfig = {} ) { // @TODO: Whenever a study metadata request has failed, its related promise will be rejected once and for all // and further requests for that metadata will always fail. On failure, we probably need to remove the // corresponding promise from the "StudyMetaDataPromises" map... if (!dicomWebClient) { - throw new Error( - `${moduleName}: Required 'dicomWebClient' parameter not provided.` - ); + throw new Error(`${moduleName}: Required 'dicomWebClient' parameter not provided.`); } if (!StudyInstanceUID) { - throw new Error( - `${moduleName}: Required 'StudyInstanceUID' parameter not provided.` - ); + throw new Error(`${moduleName}: Required 'StudyInstanceUID' parameter not provided.`); } + const promiseId = `${dicomWebConfig.name}:${StudyInstanceUID}`; + // Already waiting on result? Return cached promise - if (StudyMetaDataPromises.has(StudyInstanceUID)) { - return StudyMetaDataPromises.get(StudyInstanceUID); + if (StudyMetaDataPromises.has(promiseId)) { + return StudyMetaDataPromises.get(promiseId); } - // Create a promise to handle the data retrieval - const promise = new Promise((resolve, reject) => { - RetrieveMetadata( + let promise; + + if (filters && filters.SeriesInstanceUIDs) { + promise = retrieveMetadataFiltered( dicomWebClient, StudyInstanceUID, enableStudyLazyLoad, filters, sortCriteria, sortFunction - ).then(function(data) { - resolve(data); - }, reject); - }); + ); + } else { + // Create a promise to handle the data retrieval + promise = new Promise((resolve, reject) => { + RetrieveMetadata( + dicomWebClient, + StudyInstanceUID, + enableStudyLazyLoad, + filters, + sortCriteria, + sortFunction + ).then(function (data) { + resolve(data); + }, reject); + }); + } // Store the promise in cache - StudyMetaDataPromises.set(StudyInstanceUID, promise); + StudyMetaDataPromises.set(promiseId, promise); return promise; } /** * Delete the cached study metadata retrieval promise to ensure that the browser will - * re-retrieve the study metadata when it is next requested + * re-retrieve the study metadata when it is next requested. * * @param {String} StudyInstanceUID The UID of the Study to be removed from cache - * */ export function deleteStudyMetadataPromise(StudyInstanceUID) { if (StudyMetaDataPromises.has(StudyInstanceUID)) { diff --git a/extensions/default/src/DicomWebDataSource/utils/StaticWadoClient.ts b/extensions/default/src/DicomWebDataSource/utils/StaticWadoClient.ts index c6ff3c7d802..ed2815c6046 100644 --- a/extensions/default/src/DicomWebDataSource/utils/StaticWadoClient.ts +++ b/extensions/default/src/DicomWebDataSource/utils/StaticWadoClient.ts @@ -7,6 +7,7 @@ import { api } from 'dicomweb-client'; * performing searches doesn't work. This version fixes the query issue * by manually implementing a query option. */ + export default class StaticWadoClient extends api.DICOMwebClient { static studyFilterKeys = { studyinstanceuid: '0020000D', @@ -50,14 +51,7 @@ export default class StaticWadoClient extends api.DICOMwebClient { const lowerParams = this.toLowerParams(queryParams); const filtered = searchResult.filter(study => { for (const key of Object.keys(StaticWadoClient.studyFilterKeys)) { - if ( - !this.filterItem( - key, - lowerParams, - study, - StaticWadoClient.studyFilterKeys - ) - ) { + if (!this.filterItem(key, lowerParams, study, StaticWadoClient.studyFilterKeys)) { return false; } } @@ -80,14 +74,7 @@ export default class StaticWadoClient extends api.DICOMwebClient { const filtered = searchResult.filter(series => { for (const key of Object.keys(StaticWadoClient.seriesFilterKeys)) { - if ( - !this.filterItem( - key, - lowerParams, - series, - StaticWadoClient.seriesFilterKeys - ) - ) { + if (!this.filterItem(key, lowerParams, series, StaticWadoClient.seriesFilterKeys)) { return false; } } @@ -132,10 +119,7 @@ export default class StaticWadoClient extends api.DICOMwebClient { } else if (desired[desired.length - 1] === '*') { return actual.indexOf(desired.substring(0, desired.length - 1)) != -1; } else if (desired[0] === '*') { - return ( - actual.indexOf(desired.substring(1)) === - actual.length - desired.length + 1 - ); + return actual.indexOf(desired.substring(1)) === actual.length - desired.length + 1; } } return desired === actual; @@ -177,7 +161,7 @@ export default class StaticWadoClient extends api.DICOMwebClient { if (!valueElem) { return false; } - if (valueElem.vr == 'DA') { + if (valueElem.vr === 'DA' && valueElem.Value?.[0]) { return this.compareDateRange(testValue, valueElem.Value[0]); } const value = valueElem.Value; diff --git a/extensions/default/src/DicomWebDataSource/utils/fixBulkDataURI.ts b/extensions/default/src/DicomWebDataSource/utils/fixBulkDataURI.ts index 1a408b597fe..2e592c75700 100644 --- a/extensions/default/src/DicomWebDataSource/utils/fixBulkDataURI.ts +++ b/extensions/default/src/DicomWebDataSource/utils/fixBulkDataURI.ts @@ -20,10 +20,7 @@ function fixBulkDataURI(value, instance, dicomWebConfig) { // in case of the relative path, make it absolute. The current DICOM standard says // the bulkdataURI is relative to the series. However, there are situations where // it can be relative to the study too - if ( - !value.BulkDataURI.startsWith('http') && - !value.BulkDataURI.startsWith('/') - ) { + if (!value.BulkDataURI.startsWith('http') && !value.BulkDataURI.startsWith('/')) { if (dicomWebConfig.bulkDataURI?.relativeResolution === 'studies') { value.BulkDataURI = `${dicomWebConfig.wadoRoot}/studies/${instance.StudyInstanceUID}/${value.BulkDataURI}`; } else if ( diff --git a/extensions/default/src/DicomWebDataSource/utils/getImageId.js b/extensions/default/src/DicomWebDataSource/utils/getImageId.js index a493a3d3ad1..29877bd432c 100644 --- a/extensions/default/src/DicomWebDataSource/utils/getImageId.js +++ b/extensions/default/src/DicomWebDataSource/utils/getImageId.js @@ -24,12 +24,7 @@ function buildInstanceWadoUrl(config, instance) { * @param thumbnail * @returns {string} The imageId to be used by Cornerstone */ -export default function getImageId({ - instance, - frame, - config, - thumbnail = false, -}) { +export default function getImageId({ instance, frame, config, thumbnail = false }) { if (!instance) { return; } diff --git a/extensions/default/src/DicomWebDataSource/utils/retrieveMetadataFiltered.js b/extensions/default/src/DicomWebDataSource/utils/retrieveMetadataFiltered.js new file mode 100644 index 00000000000..3e147fd48bf --- /dev/null +++ b/extensions/default/src/DicomWebDataSource/utils/retrieveMetadataFiltered.js @@ -0,0 +1,56 @@ +import RetrieveMetadata from '../wado/retrieveMetadata'; + +/** + * Retrieve metadata filtered. + * + * @param {*} dicomWebClient The DICOMWebClient instance to be used for series load + * @param {*} StudyInstanceUID The UID of the Study to be retrieved + * @param {*} enableStudyLazyLoad Whether the study metadata should be loaded asynchronously + * @param {object} filters Object containing filters to be applied on retrieve metadata process + * @param {string} [filters.seriesInstanceUID] Series instance uid to filter results against + * @param {array} [filters.SeriesInstanceUIDs] Series instance uids to filter results against + * @param {function} [sortCriteria] Sort criteria function + * @param {function} [sortFunction] Sort function + * + * @returns + */ +function retrieveMetadataFiltered( + dicomWebClient, + StudyInstanceUID, + enableStudyLazyLoad, + filters, + sortCriteria, + sortFunction +) { + const { SeriesInstanceUIDs } = filters; + + return new Promise((resolve, reject) => { + const promises = SeriesInstanceUIDs.map(uid => { + const seriesSpecificFilters = Object.assign({}, filters, { + seriesInstanceUID: uid, + }); + + return RetrieveMetadata( + dicomWebClient, + StudyInstanceUID, + enableStudyLazyLoad, + seriesSpecificFilters, + sortCriteria, + sortFunction + ); + }); + + Promise.all(promises).then(results => { + const aggregatedResult = { preLoadData: [], promises: [] }; + + results.forEach(({ preLoadData, promises }) => { + aggregatedResult.preLoadData = aggregatedResult.preLoadData.concat(preLoadData); + aggregatedResult.promises = aggregatedResult.promises.concat(promises); + }); + + resolve(aggregatedResult); + }, reject); + }); +} + +export default retrieveMetadataFiltered; diff --git a/extensions/default/src/DicomWebDataSource/wado/retrieveMetadata.js b/extensions/default/src/DicomWebDataSource/wado/retrieveMetadata.js index d70a2cdccd0..7bed25a1a61 100644 --- a/extensions/default/src/DicomWebDataSource/wado/retrieveMetadata.js +++ b/extensions/default/src/DicomWebDataSource/wado/retrieveMetadata.js @@ -5,28 +5,31 @@ import RetrieveMetadataLoaderAsync from './retrieveMetadataLoaderAsync'; * Retrieve Study metadata from a DICOM server. If the server is configured to use lazy load, only the first series * will be loaded and the property "studyLoader" will be set to let consumer load remaining series as needed. * - * @param {Object} dicomWebClient The dicomweb-client. - * @param {string} studyInstanceUid The Study Instance UID of the study which needs to be loaded - * @param {Object} [filters] - Object containing filters to be applied on retrieve metadata process - * @param {string} [filter.seriesInstanceUID] - series instance uid to filter results against - * @returns {Object} A study descriptor object + * @param {*} dicomWebClient The DICOMWebClient instance to be used for series load + * @param {*} StudyInstanceUID The UID of the Study to be retrieved + * @param {*} enableStudyLazyLoad Whether the study metadata should be loaded asynchronously + * @param {object} filters Object containing filters to be applied on retrieve metadata process + * @param {string} [filters.seriesInstanceUID] Series instance uid to filter results against + * @param {array} [filters.SeriesInstanceUIDs] Series instance uids to filter results against + * @param {function} [sortCriteria] Sort criteria function + * @param {function} [sortFunction] Sort function + * + * @returns {Promise} A promises that resolves the study descriptor object */ async function RetrieveMetadata( dicomWebClient, - studyInstanceUid, + StudyInstanceUID, enableStudyLazyLoad, filters = {}, sortCriteria, sortFunction ) { const RetrieveMetadataLoader = - enableStudyLazyLoad !== false - ? RetrieveMetadataLoaderAsync - : RetrieveMetadataLoaderSync; + enableStudyLazyLoad !== false ? RetrieveMetadataLoaderAsync : RetrieveMetadataLoaderSync; const retrieveMetadataLoader = new RetrieveMetadataLoader( dicomWebClient, - studyInstanceUid, + StudyInstanceUID, filters, sortCriteria, sortFunction diff --git a/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoader.js b/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoader.js index 49302f7a835..e0143000cd0 100644 --- a/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoader.js +++ b/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoader.js @@ -11,15 +11,16 @@ export default class RetrieveMetadataLoader { * @param {Object} client The dicomweb-client. * @param {Array} studyInstanceUID Study instance ui to be retrieved * @param {Object} [filters] - Object containing filters to be applied on retrieve metadata process - * @param {string} [filter.seriesInstanceUID] - series instance uid to filter results against - * @param {Function} [sortSeries] - Custom sort function for series + * @param {string} [filters.seriesInstanceUID] - series instance uid to filter results against + * @param {Object} [sortCriteria] - Custom sort criteria used for series + * @param {Function} [sortFunction] - Custom sort function for series */ constructor( client, studyInstanceUID, filters = {}, - sortCriteria, - sortFunction + sortCriteria = undefined, + sortFunction = undefined ) { this.client = client; this.studyInstanceUID = studyInstanceUID; @@ -32,7 +33,6 @@ export default class RetrieveMetadataLoader { const preLoadData = await this.preLoad(); const loadData = await this.load(preLoadData); const postLoadData = await this.posLoad(loadData); - return postLoadData; } @@ -43,13 +43,9 @@ export default class RetrieveMetadataLoader { async runLoaders(loaders) { let result; for (const loader of loaders) { - try { - result = await loader(); - if (result && result.length) { - break; // closes iterator in case data is retrieved successfully - } - } catch (e) { - throw e; + result = await loader(); + if (result && result.length) { + break; // closes iterator in case data is retrieved successfully } } diff --git a/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoaderAsync.js b/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoaderAsync.js index 096c3c8183f..0c9a656039f 100644 --- a/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoaderAsync.js +++ b/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoaderAsync.js @@ -1,32 +1,83 @@ import dcmjs from 'dcmjs'; -import { - sortStudySeries, - sortingCriteria, -} from '@ohif/core/src/utils/sortStudy'; +import { sortStudySeries } from '@ohif/core/src/utils/sortStudy'; import RetrieveMetadataLoader from './retrieveMetadataLoader'; +// Series Date, Series Time, Series Description and Series Number to be included +// in the series metadata query result +const includeField = ['00080021', '00080031', '0008103E', '00200011'].join(','); + +export class DeferredPromise { + metadata = undefined; + processFunction = undefined; + internalPromise = undefined; + thenFunction = undefined; + rejectFunction = undefined; + + setMetadata(metadata) { + this.metadata = metadata; + } + setProcessFunction(func) { + this.processFunction = func; + } + getPromise() { + return this.start(); + } + start() { + if (this.internalPromise) { + return this.internalPromise; + } + this.internalPromise = this.processFunction(); + // in case then and reject functions called before start + if (this.thenFunction) { + this.then(this.thenFunction); + this.thenFunction = undefined; + } + if (this.rejectFunction) { + this.reject(this.rejectFunction); + this.rejectFunction = undefined; + } + return this.internalPromise; + } + then(func) { + if (this.internalPromise) { + return this.internalPromise.then(func); + } else { + this.thenFunction = func; + } + } + reject(func) { + if (this.internalPromise) { + return this.internalPromise.reject(func); + } else { + this.rejectFunction = func; + } + } +} /** - * Creates an immutable series loader object which loads each series sequentially using the iterator interface + * Creates an immutable series loader object which loads each series sequentially using the iterator interface. + * * @param {DICOMWebClient} dicomWebClient The DICOMWebClient instance to be used for series load * @param {string} studyInstanceUID The Study Instance UID from which series will be loaded * @param {Array} seriesInstanceUIDList A list of Series Instance UIDs + * * @returns {Object} Returns an object which supports loading of instances from each of given Series Instance UID */ -function makeSeriesAsyncLoader( - client, - studyInstanceUID, - seriesInstanceUIDList -) { +function makeSeriesAsyncLoader(client, studyInstanceUID, seriesInstanceUIDList) { return Object.freeze({ hasNext() { return seriesInstanceUIDList.length > 0; }, - async next() { - const seriesInstanceUID = seriesInstanceUIDList.shift(); - return client.retrieveSeriesMetadata({ - studyInstanceUID, - seriesInstanceUID, + next() { + const { seriesInstanceUID, metadata } = seriesInstanceUIDList.shift(); + const promise = new DeferredPromise(); + promise.setMetadata(metadata); + promise.setProcessFunction(() => { + return client.retrieveSeriesMetadata({ + studyInstanceUID, + seriesInstanceUID, + }); }); + return promise; }, }); } @@ -43,21 +94,24 @@ export default class RetrieveMetadataLoaderAsync extends RetrieveMetadataLoader */ *getPreLoaders() { const preLoaders = []; - const { + const { studyInstanceUID, filters: { seriesInstanceUID } = {}, client } = this; + + // asking to include Series Date, Series Time, Series Description + // and Series Number in the series metadata returned to better sort series + // in preLoad function + let options = { studyInstanceUID, - filters: { seriesInstanceUID } = {}, - client, - } = this; + queryParams: { + includefield: includeField, + }, + }; if (seriesInstanceUID) { - const options = { - studyInstanceUID, - queryParams: { SeriesInstanceUID: seriesInstanceUID }, - }; + options.queryParams.SeriesInstanceUID = seriesInstanceUID; preLoaders.push(client.searchForSeries.bind(client, options)); } // Fallback preloader - preLoaders.push(client.searchForSeries.bind(client, { studyInstanceUID })); + preLoaders.push(client.searchForSeries.bind(client, options)); yield* preLoaders; } @@ -71,29 +125,23 @@ export default class RetrieveMetadataLoaderAsync extends RetrieveMetadataLoader const { naturalizeDataset } = dcmjs.data.DicomMetaDictionary; const naturalized = result.map(naturalizeDataset); - return sortStudySeries( - naturalized, - sortCriteria || - sortingCriteria.seriesSortCriteria.seriesInfoSortingCriteria, - sortFunction - ); + return sortStudySeries(naturalized, sortCriteria, sortFunction); } async load(preLoadData) { const { client, studyInstanceUID } = this; - const seriesInstanceUIDs = preLoadData.map(s => s.SeriesInstanceUID); + const seriesInstanceUIDs = preLoadData.map(seriesMetadata => { + return { seriesInstanceUID: seriesMetadata.SeriesInstanceUID, metadata: seriesMetadata }; + }); - const seriesAsyncLoader = makeSeriesAsyncLoader( - client, - studyInstanceUID, - seriesInstanceUIDs - ); + const seriesAsyncLoader = makeSeriesAsyncLoader(client, studyInstanceUID, seriesInstanceUIDs); const promises = []; while (seriesAsyncLoader.hasNext()) { - promises.push(seriesAsyncLoader.next()); + const promise = seriesAsyncLoader.next(); + promises.push(promise); } return { diff --git a/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoaderSync.js b/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoaderSync.js index 6ca47ce9db2..1a7cd9d500b 100644 --- a/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoaderSync.js +++ b/extensions/default/src/DicomWebDataSource/wado/retrieveMetadataLoaderSync.js @@ -31,11 +31,7 @@ export default class RetrieveMetadataLoaderSync extends RetrieveMetadataLoader { */ *getLoaders() { const loaders = []; - const { - studyInstanceUID, - filters: { seriesInstanceUID } = {}, - client, - } = this; + const { studyInstanceUID, filters: { seriesInstanceUID } = {}, client } = this; if (seriesInstanceUID) { loaders.push( @@ -46,9 +42,7 @@ export default class RetrieveMetadataLoaderSync extends RetrieveMetadataLoader { ); } - loaders.push( - client.retrieveStudyMetadata.bind(client, { studyInstanceUID }) - ); + loaders.push(client.retrieveStudyMetadata.bind(client, { studyInstanceUID })); yield* loaders; } diff --git a/extensions/default/src/DicomWebProxyDataSource/index.js b/extensions/default/src/DicomWebProxyDataSource/index.js index 326c1314473..99007de3e05 100644 --- a/extensions/default/src/DicomWebProxyDataSource/index.js +++ b/extensions/default/src/DicomWebProxyDataSource/index.js @@ -9,10 +9,7 @@ import { createDicomWebApi } from '../DicomWebDataSource/index'; * dicomWeb configuration array * */ -function createDicomWebProxyApi( - dicomWebProxyConfig, - UserAuthenticationService -) { +function createDicomWebProxyApi(dicomWebProxyConfig, servicesManager) { const { name } = dicomWebProxyConfig; let dicomWebDelegate = undefined; @@ -31,7 +28,7 @@ function createDicomWebProxyApi( dicomWebDelegate = createDicomWebApi( data.servers.dicomWeb[0].configuration, - UserAuthenticationService + servicesManager ); dicomWebDelegate.initialize({ params, query }); } @@ -45,28 +42,21 @@ function createDicomWebProxyApi( }, instances: { search: (studyInstanceUid, queryParameters) => - dicomWebDelegate.query.instances.search( - studyInstanceUid, - queryParameters - ), + dicomWebDelegate.query.instances.search(studyInstanceUid, queryParameters), }, }, retrieve: { directURL: (...args) => dicomWebDelegate.retrieve.directURL(...args), series: { - metadata: (...args) => - dicomWebDelegate.retrieve.series.metadata(...args), + metadata: async (...args) => dicomWebDelegate.retrieve.series.metadata(...args), }, }, store: { dicom: (...args) => dicomWebDelegate.store(...args), }, - deleteStudyMetadataPromise: (...args) => - dicomWebDelegate.deleteStudyMetadataPromise(...args), - getImageIdsForDisplaySet: (...args) => - dicomWebDelegate.getImageIdsForDisplaySet(...args), - getImageIdsForInstance: (...args) => - dicomWebDelegate.getImageIdsForInstance(...args), + deleteStudyMetadataPromise: (...args) => dicomWebDelegate.deleteStudyMetadataPromise(...args), + getImageIdsForDisplaySet: (...args) => dicomWebDelegate.getImageIdsForDisplaySet(...args), + getImageIdsForInstance: (...args) => dicomWebDelegate.getImageIdsForInstance(...args), getStudyInstanceUIDs({ params, query }) { let studyInstanceUIDs = []; diff --git a/extensions/default/src/MergeDataSource/index.test.js b/extensions/default/src/MergeDataSource/index.test.js new file mode 100644 index 00000000000..2b915e32968 --- /dev/null +++ b/extensions/default/src/MergeDataSource/index.test.js @@ -0,0 +1,203 @@ +import { DicomMetadataStore, IWebApiDataSource } from '@ohif/core'; +import { + mergeMap, + callForAllDataSourcesAsync, + callForAllDataSources, + callForDefaultDataSource, + callByRetrieveAETitle, + createMergeDataSourceApi, +} from './index'; + +jest.mock('@ohif/core'); + +describe('MergeDataSource', () => { + let path, + sourceName, + mergeConfig, + extensionManager, + series1, + series2, + series3, + series4, + mergeKey, + tagFunc, + dataSourceAndSeriesMap, + dataSourceAndUIDsMap, + dataSourceAndDSMap, + pathSync; + + beforeAll(() => { + path = 'query.series.search'; + pathSync = 'getImageIdsForInstance'; + tagFunc = jest.fn((data, sourceName) => + data.map(item => ({ ...item, RetrieveAETitle: sourceName })) + ); + sourceName = 'dicomweb1'; + mergeKey = 'seriesInstanceUid'; + series1 = { [mergeKey]: '123' }; + series2 = { [mergeKey]: '234' }; + series3 = { [mergeKey]: '345' }; + series4 = { [mergeKey]: '456' }; + mergeConfig = { + seriesMerge: { + dataSourceNames: ['dicomweb1', 'dicomweb2'], + defaultDataSourceName: 'dicomweb1', + }, + }; + dataSourceAndSeriesMap = { + dataSource1: series1, + dataSource2: series2, + dataSource3: series3, + }; + dataSourceAndUIDsMap = { + dataSource1: ['123'], + dataSource2: ['234'], + dataSource3: ['345'], + }; + dataSourceAndDSMap = { + dataSource1: { + displaySet: { + StudyInstanceUID: '123', + SeriesInstanceUID: '123', + }, + }, + dataSource2: { + displaySet: { + StudyInstanceUID: '234', + SeriesInstanceUID: '234', + }, + }, + dataSource3: { + displaySet: { + StudyInstanceUID: '345', + SeriesInstanceUID: '345', + }, + }, + }; + extensionManager = { + dataSourceDefs: { + dataSource1: { + sourceName: 'dataSource1', + configuration: {}, + }, + dataSource2: { + sourceName: 'dataSource2', + configuration: {}, + }, + dataSource3: { + sourceName: 'dataSource3', + configuration: {}, + }, + }, + getDataSources: jest.fn(dataSourceName => [ + { + [path]: jest.fn().mockResolvedValue([dataSourceAndSeriesMap[dataSourceName]]), + }, + ]), + }; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('callForAllDataSourcesAsync', () => { + it('should call the correct functions and return the merged data', async () => { + /** Arrange */ + extensionManager.getDataSources = jest.fn(dataSourceName => [ + { + [path]: jest.fn().mockResolvedValue([dataSourceAndSeriesMap[dataSourceName]]), + }, + ]); + + /** Act */ + const data = await callForAllDataSourcesAsync({ + mergeMap, + path, + args: [], + extensionManager, + dataSourceNames: ['dataSource1', 'dataSource2'], + }); + + /** Assert */ + expect(extensionManager.getDataSources).toHaveBeenCalledTimes(2); + expect(extensionManager.getDataSources).toHaveBeenCalledWith('dataSource1'); + expect(extensionManager.getDataSources).toHaveBeenCalledWith('dataSource2'); + expect(data).toEqual([series1, series2]); + }); + }); + + describe('callForAllDataSources', () => { + it('should call the correct functions and return the merged data', () => { + /** Arrange */ + extensionManager.getDataSources = jest.fn(dataSourceName => [ + { + [pathSync]: () => dataSourceAndUIDsMap[dataSourceName], + }, + ]); + + /** Act */ + const data = callForAllDataSources({ + path: pathSync, + args: [], + extensionManager, + dataSourceNames: ['dataSource2', 'dataSource3'], + }); + + /** Assert */ + expect(extensionManager.getDataSources).toHaveBeenCalledTimes(2); + expect(extensionManager.getDataSources).toHaveBeenCalledWith('dataSource2'); + expect(extensionManager.getDataSources).toHaveBeenCalledWith('dataSource3'); + expect(data).toEqual(['234', '345']); + }); + }); + + describe('callForDefaultDataSource', () => { + it('should call the correct function and return the data', () => { + /** Arrange */ + extensionManager.getDataSources = jest.fn(dataSourceName => [ + { + [pathSync]: () => dataSourceAndUIDsMap[dataSourceName], + }, + ]); + + /** Act */ + const data = callForDefaultDataSource({ + path: pathSync, + args: [], + extensionManager, + defaultDataSourceName: 'dataSource2', + }); + + /** Assert */ + expect(extensionManager.getDataSources).toHaveBeenCalledTimes(1); + expect(extensionManager.getDataSources).toHaveBeenCalledWith('dataSource2'); + expect(data).toEqual(['234']); + }); + }); + + describe('callByRetrieveAETitle', () => { + it('should call the correct function and return the data', () => { + /** Arrange */ + DicomMetadataStore.getSeries.mockImplementationOnce(() => [series2]); + extensionManager.getDataSources = jest.fn(dataSourceName => [ + { + [pathSync]: () => dataSourceAndUIDsMap[dataSourceName], + }, + ]); + + /** Act */ + const data = callByRetrieveAETitle({ + path: pathSync, + args: [dataSourceAndDSMap['dataSource2']], + extensionManager, + defaultDataSourceName: 'dataSource2', + }); + + /** Assert */ + expect(extensionManager.getDataSources).toHaveBeenCalledTimes(1); + expect(extensionManager.getDataSources).toHaveBeenCalledWith('dataSource2'); + expect(data).toEqual(['234']); + }); + }); +}); diff --git a/extensions/default/src/MergeDataSource/index.ts b/extensions/default/src/MergeDataSource/index.ts new file mode 100644 index 00000000000..42e49c69418 --- /dev/null +++ b/extensions/default/src/MergeDataSource/index.ts @@ -0,0 +1,293 @@ +import { DicomMetadataStore, IWebApiDataSource } from '@ohif/core'; +import { get, uniqBy } from 'lodash'; +import { + MergeConfig, + CallForAllDataSourcesAsyncOptions, + CallForAllDataSourcesOptions, + CallForDefaultDataSourceOptions, + CallByRetrieveAETitleOptions, + MergeMap, +} from './types'; + +export const mergeMap: MergeMap = { + 'query.studies.search': { + mergeKey: 'studyInstanceUid', + tagFunc: x => x, + }, + 'query.series.search': { + mergeKey: 'seriesInstanceUid', + tagFunc: (series, sourceName) => { + series.forEach(series => { + series.RetrieveAETitle = sourceName; + DicomMetadataStore.updateSeriesMetadata(series); + }); + return series; + }, + }, +}; + +/** + * Calls all data sources asynchronously and merges the results. + * @param {CallForAllDataSourcesAsyncOptions} options - The options for calling all data sources. + * @param {string} options.path - The path to the function to be called on each data source. + * @param {unknown[]} options.args - The arguments to be passed to the function. + * @param {ExtensionManager} options.extensionManager - The extension manager. + * @param {string[]} options.dataSourceNames - The names of the data sources to be called. + * @param {string} options.defaultDataSourceName - The name of the default data source. + * @returns {Promise} - A promise that resolves to the merged data from all data sources. + */ +export const callForAllDataSourcesAsync = async ({ + mergeMap, + path, + args, + extensionManager, + dataSourceNames, + defaultDataSourceName, +}: CallForAllDataSourcesAsyncOptions) => { + const { mergeKey, tagFunc } = mergeMap[path] || { tagFunc: x => x }; + + /** Sort by default data source */ + const defs = Object.values(extensionManager.dataSourceDefs); + const defaultDataSourceDef = defs.find(def => def.sourceName === defaultDataSourceName); + const dataSourceDefs = defs.filter(def => def.sourceName !== defaultDataSourceName); + if (defaultDataSourceDef) { + dataSourceDefs.unshift(defaultDataSourceDef); + } + + const promises = []; + const sourceNames = []; + + for (const dataSourceDef of dataSourceDefs) { + const { configuration, sourceName } = dataSourceDef; + if (!!configuration && dataSourceNames.includes(sourceName)) { + const [dataSource] = extensionManager.getDataSources(sourceName); + const func = get(dataSource, path); + const promise = func.apply(dataSource, args); + promises.push(promise); + sourceNames.push(sourceName); + } + } + + const data = await Promise.allSettled(promises); + const mergedData = data.map((data, i) => tagFunc(data.value, sourceNames[i])); + + let results = []; + if (mergeKey) { + results = uniqBy(mergedData.flat(), obj => get(obj, mergeKey)); + } else { + results = mergedData.flat(); + } + + return results; +}; + +/** + * Calls all data sources that match the provided names and merges their data. + * @param options - The options for calling all data sources. + * @param options.path - The path to the function to be called on each data source. + * @param options.args - The arguments to be passed to the function. + * @param options.extensionManager - The extension manager instance. + * @param options.dataSourceNames - The names of the data sources to be called. + * @param options.defaultDataSourceName - The name of the default data source. + * @returns The merged data from all the matching data sources. + */ +export const callForAllDataSources = ({ + path, + args, + extensionManager, + dataSourceNames, + defaultDataSourceName, +}: CallForAllDataSourcesOptions) => { + /** Sort by default data source */ + const defs = Object.values(extensionManager.dataSourceDefs); + const defaultDataSourceDef = defs.find(def => def.sourceName === defaultDataSourceName); + const dataSourceDefs = defs.filter(def => def.sourceName !== defaultDataSourceName); + if (defaultDataSourceDef) { + dataSourceDefs.unshift(defaultDataSourceDef); + } + + const mergedData = []; + for (const dataSourceDef of dataSourceDefs) { + const { configuration, sourceName } = dataSourceDef; + if (!!configuration && dataSourceNames.includes(sourceName)) { + const [dataSource] = extensionManager.getDataSources(sourceName); + const func = get(dataSource, path); + const data = func.apply(dataSource, args); + mergedData.push(data); + } + } + + return mergedData.flat(); +}; + +/** + * Calls the default data source function specified by the given path with the provided arguments. + * @param {CallForDefaultDataSourceOptions} options - The options for calling the default data source. + * @param {string} options.path - The path to the function within the default data source. + * @param {unknown[]} options.args - The arguments to pass to the function. + * @param {string} options.defaultDataSourceName - The name of the default data source. + * @param {ExtensionManager} options.extensionManager - The extension manager instance. + * @returns {unknown} - The result of calling the default data source function. + */ +export const callForDefaultDataSource = ({ + path, + args, + defaultDataSourceName, + extensionManager, +}: CallForDefaultDataSourceOptions) => { + const [dataSource] = extensionManager.getDataSources(defaultDataSourceName); + const func = get(dataSource, path); + return func.apply(dataSource, args); +}; + +/** + * Calls the data source specified by the RetrieveAETitle of the given display set. + * @typedef {Object} CallByRetrieveAETitleOptions + * @property {string} path - The path of the method to call on the data source. + * @property {any[]} args - The arguments to pass to the method. + * @property {string} defaultDataSourceName - The name of the default data source. + * @property {ExtensionManager} extensionManager - The extension manager. + */ +export const callByRetrieveAETitle = ({ + path, + args, + defaultDataSourceName, + extensionManager, +}: CallByRetrieveAETitleOptions) => { + const [displaySet] = args; + const seriesMetadata = DicomMetadataStore.getSeries( + displaySet.StudyInstanceUID, + displaySet.SeriesInstanceUID + ); + const [dataSource] = extensionManager.getDataSources( + seriesMetadata.RetrieveAETitle || defaultDataSourceName + ); + return dataSource[path](...args); +}; + +function createMergeDataSourceApi( + mergeConfig: MergeConfig, + servicesManager: unknown, + extensionManager +) { + const { seriesMerge } = mergeConfig; + const { dataSourceNames, defaultDataSourceName } = seriesMerge; + + const implementation = { + initialize: (...args: unknown[]) => + callForAllDataSources({ + path: 'initialize', + args, + extensionManager, + dataSourceNames, + defaultDataSourceName, + }), + query: { + studies: { + search: (...args: unknown[]) => + callForAllDataSourcesAsync({ + mergeMap, + path: 'query.studies.search', + args, + extensionManager, + dataSourceNames, + defaultDataSourceName, + }), + }, + series: { + search: (...args: unknown[]) => + callForAllDataSourcesAsync({ + mergeMap, + path: 'query.series.search', + args, + extensionManager, + dataSourceNames, + defaultDataSourceName, + }), + }, + instances: { + search: (...args: unknown[]) => + callForAllDataSourcesAsync({ + mergeMap, + path: 'query.instances.search', + args, + extensionManager, + dataSourceNames, + defaultDataSourceName, + }), + }, + }, + retrieve: { + bulkDataURI: (...args: unknown[]) => + callForAllDataSourcesAsync({ + mergeMap, + path: 'retrieve.bulkDataURI', + args, + extensionManager, + dataSourceNames, + defaultDataSourceName, + }), + directURL: (...args: unknown[]) => + callForDefaultDataSource({ + path: 'retrieve.directURL', + args, + defaultDataSourceName, + extensionManager, + }), + series: { + metadata: (...args: unknown[]) => + callForAllDataSourcesAsync({ + mergeMap, + path: 'retrieve.series.metadata', + args, + extensionManager, + dataSourceNames, + defaultDataSourceName, + }), + }, + }, + store: { + dicom: (...args: unknown[]) => + callForDefaultDataSource({ + path: 'store.dicom', + args, + defaultDataSourceName, + extensionManager, + }), + }, + deleteStudyMetadataPromise: (...args: unknown[]) => + callForAllDataSources({ + path: 'deleteStudyMetadataPromise', + args, + extensionManager, + dataSourceNames, + defaultDataSourceName, + }), + getImageIdsForDisplaySet: (...args: unknown[]) => + callByRetrieveAETitle({ + path: 'getImageIdsForDisplaySet', + args, + defaultDataSourceName, + extensionManager, + }), + getImageIdsForInstance: (...args: unknown[]) => + callByRetrieveAETitle({ + path: 'getImageIdsForDisplaySet', + args, + defaultDataSourceName, + extensionManager, + }), + getStudyInstanceUIDs: (...args: unknown[]) => + callForAllDataSources({ + path: 'getStudyInstanceUIDs', + args, + extensionManager, + dataSourceNames, + defaultDataSourceName, + }), + }; + + return IWebApiDataSource.create(implementation); +} + +export { createMergeDataSourceApi }; diff --git a/extensions/default/src/MergeDataSource/types.ts b/extensions/default/src/MergeDataSource/types.ts new file mode 100644 index 00000000000..ef47b4ed2b2 --- /dev/null +++ b/extensions/default/src/MergeDataSource/types.ts @@ -0,0 +1,46 @@ +import { ExtensionManager } from '@ohif/core'; + +export type MergeMap = { + [key: string]: { + mergeKey: string; + tagFunc: (data: unknown[], sourceName: string) => unknown[]; + }; +}; + +export type CallForAllDataSourcesAsyncOptions = { + mergeMap: object; + path: string; + args: unknown[]; + dataSourceNames: string[]; + extensionManager: ExtensionManager; + defaultDataSourceName: string; +}; + +export type CallForAllDataSourcesOptions = { + path: string; + args: unknown[]; + dataSourceNames: string[]; + extensionManager: ExtensionManager; + defaultDataSourceName: string; +}; + +export type CallForDefaultDataSourceOptions = { + path: string; + args: unknown[]; + defaultDataSourceName: string; + extensionManager: ExtensionManager; +}; + +export type CallByRetrieveAETitleOptions = { + path: string; + args: unknown[]; + defaultDataSourceName: string; + extensionManager: ExtensionManager; +}; + +export type MergeConfig = { + seriesMerge: { + dataSourceNames: string[]; + defaultDataSourceName: string; + }; +}; diff --git a/extensions/default/src/Panels/ActionButtons.tsx b/extensions/default/src/Panels/ActionButtons.tsx index 133f2b81409..c21f8b6b652 100644 --- a/extensions/default/src/Panels/ActionButtons.tsx +++ b/extensions/default/src/Panels/ActionButtons.tsx @@ -2,16 +2,22 @@ import React from 'react'; import PropTypes from 'prop-types'; import { useTranslation } from 'react-i18next'; -import { LegacyButton, ButtonGroup } from '@ohif/ui'; +import { LegacyButton, LegacyButtonGroup } from '@ohif/ui'; function ActionButtons({ onExportClick, onCreateReportClick }) { const { t } = useTranslation('MeasurementTable'); return ( - - {/* TODO Revisit design of ButtonGroup later - for now use LegacyButton for its children.*/} - + + {/* TODO Revisit design of LegacyButtonGroup later - for now use LegacyButton for its children.*/} + {t('Export CSV')} {t('Create Report')} - + ); } diff --git a/extensions/default/src/Panels/DataSourceSelector.tsx b/extensions/default/src/Panels/DataSourceSelector.tsx index 1aa0fbd032d..dcb62c28399 100644 --- a/extensions/default/src/Panels/DataSourceSelector.tsx +++ b/extensions/default/src/Panels/DataSourceSelector.tsx @@ -15,20 +15,16 @@ function DataSourceSelector() { return (
-
-
+
+
OHIF -
+
{dsConfigs - .filter( - it => - it.sourceName !== 'dicomjson' && - it.sourceName !== 'dicomlocal' - ) + .filter(it => it.sourceName !== 'dicomjson' && it.sourceName !== 'dicomlocal') .map(ds => (

diff --git a/extensions/default/src/Panels/PanelMeasurementTable.tsx b/extensions/default/src/Panels/PanelMeasurementTable.tsx index 7d6be4aabbc..9b5d90543ba 100644 --- a/extensions/default/src/Panels/PanelMeasurementTable.tsx +++ b/extensions/default/src/Panels/PanelMeasurementTable.tsx @@ -1,13 +1,8 @@ import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; +import { useTranslation } from 'react-i18next'; import { utils, ServicesManager } from '@ohif/core'; -import { - MeasurementTable, - Dialog, - Input, - useViewportGrid, - ButtonEnums, -} from '@ohif/ui'; +import { MeasurementTable, Dialog, Input, useViewportGrid, ButtonEnums } from '@ohif/ui'; import ActionButtons from './ActionButtons'; import debounce from 'lodash.debounce'; @@ -24,21 +19,17 @@ export default function PanelMeasurementTable({ commandsManager, extensionManager, }): React.FunctionComponent { + const { t } = useTranslation('MeasurementTable'); + const [viewportGrid, viewportGridService] = useViewportGrid(); - const { activeViewportIndex, viewports } = viewportGrid; - const { - measurementService, - uiDialogService, - uiNotificationService, - displaySetService, - } = (servicesManager as ServicesManager).services; + const { activeViewportId, viewports } = viewportGrid; + const { measurementService, uiDialogService, uiNotificationService, displaySetService } = ( + servicesManager as ServicesManager + ).services; const [displayMeasurements, setDisplayMeasurements] = useState([]); useEffect(() => { - const debouncedSetDisplayMeasurements = debounce( - setDisplayMeasurements, - 100 - ); + const debouncedSetDisplayMeasurements = debounce(setDisplayMeasurements, 100); // ~~ Initial setDisplayMeasurements(_getMappedMeasurements(measurementService)); @@ -53,9 +44,7 @@ export default function PanelMeasurementTable({ [added, addedRaw, updated, removed, cleared].forEach(evt => { subscriptions.push( measurementService.subscribe(evt, () => { - debouncedSetDisplayMeasurements( - _getMappedMeasurements(measurementService) - ); + debouncedSetDisplayMeasurements(_getMappedMeasurements(measurementService)); }).unsubscribe ); }); @@ -80,7 +69,7 @@ export default function PanelMeasurementTable({ async function createReport(): Promise { // filter measurements that are added to the active study - const activeViewport = viewports[activeViewportIndex]; + const activeViewport = viewports.get(activeViewportId); const measurements = measurementService.getMeasurements(); const displaySet = displaySetService.getDisplaySetByUID( activeViewport.displaySetInstanceUIDs[0] @@ -104,9 +93,7 @@ export default function PanelMeasurementTable({ }); if (promptResult.action === CREATE_REPORT_DIALOG_RESPONSE.CREATE_REPORT) { - const dataSources = extensionManager.getDataSources( - promptResult.dataSourceName - ); + const dataSources = extensionManager.getDataSources(promptResult.dataSourceName); const dataSource = dataSources[0]; const SeriesDescription = @@ -115,25 +102,29 @@ export default function PanelMeasurementTable({ ? 'Research Derived Series' // default : promptResult.value; // provided value - // Re-use an existing series having the same series description to avoid + // Reuse an existing series having the same series description to avoid // creating too many series instances. - const options = findSRWithSameSeriesDescription( - SeriesDescription, - displaySetService - ); - - return createReportAsync( - servicesManager, - commandsManager, - dataSource, - trackedMeasurements, - options - ); + const options = findSRWithSameSeriesDescription(SeriesDescription, displaySetService); + + const getReport = async () => { + return commandsManager.runCommand( + 'storeMeasurements', + { + measurementData: trackedMeasurements, + dataSource, + additionalFindingTypes: ['ArrowAnnotate'], + options, + }, + 'CORNERSTONE_STRUCTURED_REPORT' + ); + }; + + return createReportAsync({ servicesManager, getReport }); } } const jumpToImage = ({ uid, isActive }) => { - measurementService.jumpToMeasurement(viewportGrid.activeViewportIndex, uid); + measurementService.jumpToMeasurement(viewportGrid.activeViewportId, uid); onMeasurementItemClickHandler({ uid, isActive }); }; @@ -186,7 +177,7 @@ export default function PanelMeasurementTable({ labelClassName="text-white text-[14px] leading-[1.2]" autoFocus id="annotation" - className="bg-black border-primary-main" + className="border-primary-main bg-black" type="text" value={value.label} onChange={onChangeHandler} @@ -217,11 +208,11 @@ export default function PanelMeasurementTable({ return ( <>
Studies --> DisplaySets --> Thumbnails const { StudyInstanceUIDs } = useImageViewer(); - const [ - { activeViewportIndex, viewports }, - viewportGridService, - ] = useViewportGrid(); + const [{ activeViewportId, viewports }, viewportGridService] = useViewportGrid(); const [activeTabName, setActiveTabName] = useState('primary'); const [expandedStudyInstanceUIDs, setExpandedStudyInstanceUIDs] = useState([ ...StudyInstanceUIDs, @@ -36,22 +33,20 @@ function PanelStudyBrowser({ const [studyDisplayList, setStudyDisplayList] = useState([]); const [displaySets, setDisplaySets] = useState([]); const [thumbnailImageSrcMap, setThumbnailImageSrcMap] = useState({}); - const isMounted = useRef(true); const onDoubleClickThumbnailHandler = displaySetInstanceUID => { let updatedViewports = []; - const viewportIndex = activeViewportIndex; + const viewportId = activeViewportId; try { updatedViewports = hangingProtocolService.getViewportsRequireUpdate( - viewportIndex, + viewportId, displaySetInstanceUID ); } catch (error) { console.warn(error); uiNotificationService.show({ title: 'Thumbnail Double Click', - message: - 'The selected display sets could not be added to the viewport.', + message: 'The selected display sets could not be added to the viewport.', type: 'info', duration: 3000, }); @@ -69,14 +64,17 @@ function PanelStudyBrowser({ studyInstanceUid: StudyInstanceUID, }); + if (!qidoForStudyUID?.length) { + navigate('/notfoundstudy', '_self'); + throw new Error('Invalid study URL'); + } + let qidoStudiesForPatient = qidoForStudyUID; // try to fetch the prior studies based on the patientID if the // server can respond. try { - qidoStudiesForPatient = await getStudiesForPatientByMRN( - qidoForStudyUID - ); + qidoStudiesForPatient = await getStudiesForPatientByMRN(qidoForStudyUID); } catch (error) { console.warn(error); } @@ -95,11 +93,7 @@ function PanelStudyBrowser({ setStudyDisplayList(prevArray => { const ret = [...prevArray]; for (const study of actuallyMappedStudies) { - if ( - !prevArray.find( - it => it.studyInstanceUid === study.studyInstanceUid - ) - ) { + if (!prevArray.find(it => it.studyInstanceUid === study.studyInstanceUid)) { ret.push(study); } } @@ -108,52 +102,39 @@ function PanelStudyBrowser({ } StudyInstanceUIDs.forEach(sid => fetchStudiesForPatient(sid)); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [StudyInstanceUIDs, getStudiesForPatientByMRN]); + }, [StudyInstanceUIDs, dataSource, getStudiesForPatientByMRN, navigate]); // // ~~ Initial Thumbnails useEffect(() => { const currentDisplaySets = displaySetService.activeDisplaySets; currentDisplaySets.forEach(async dSet => { const newImageSrcEntry = {}; - const displaySet = displaySetService.getDisplaySetByUID( - dSet.displaySetInstanceUID - ); + const displaySet = displaySetService.getDisplaySetByUID(dSet.displaySetInstanceUID); const imageIds = dataSource.getImageIdsForDisplaySet(displaySet); const imageId = imageIds[Math.floor(imageIds.length / 2)]; // TODO: Is it okay that imageIds are not returned here for SR displaySets? - if (imageId) { - // When the image arrives, render it and store the result in the thumbnailImgSrcMap - newImageSrcEntry[dSet.displaySetInstanceUID] = await getImageSrc( - imageId - ); - if (isMounted.current) { - setThumbnailImageSrcMap(prevState => { - return { ...prevState, ...newImageSrcEntry }; - }); - } + if (!imageId || displaySet?.unsupported) { + return; } + // When the image arrives, render it and store the result in the thumbnailImgSrcMap + newImageSrcEntry[dSet.displaySetInstanceUID] = await getImageSrc(imageId); + + setThumbnailImageSrcMap(prevState => { + return { ...prevState, ...newImageSrcEntry }; + }); }); - return () => { - isMounted.current = false; - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [StudyInstanceUIDs, dataSource, displaySetService, getImageSrc]); // ~~ displaySets useEffect(() => { // TODO: Are we sure `activeDisplaySets` will always be accurate? const currentDisplaySets = displaySetService.activeDisplaySets; - const mappedDisplaySets = _mapDisplaySets( - currentDisplaySets, - thumbnailImageSrcMap - ); + const mappedDisplaySets = _mapDisplaySets(currentDisplaySets, thumbnailImageSrcMap); sortStudyInstances(mappedDisplaySets); setDisplaySets(mappedDisplaySets); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [thumbnailImageSrcMap]); + }, [StudyInstanceUIDs, thumbnailImageSrcMap, displaySetService]); // ~~ subscriptions --> displaySets useEffect(() => { @@ -161,86 +142,87 @@ function PanelStudyBrowser({ const SubscriptionDisplaySetsAdded = displaySetService.subscribe( displaySetService.EVENTS.DISPLAY_SETS_ADDED, data => { - const { displaySetsAdded } = data; + const { displaySetsAdded, options } = data; displaySetsAdded.forEach(async dSet => { const newImageSrcEntry = {}; - const displaySet = displaySetService.getDisplaySetByUID( - dSet.displaySetInstanceUID - ); + const displaySet = displaySetService.getDisplaySetByUID(dSet.displaySetInstanceUID); + if (displaySet?.unsupported) { + return; + } + const imageIds = dataSource.getImageIdsForDisplaySet(displaySet); const imageId = imageIds[Math.floor(imageIds.length / 2)]; // TODO: Is it okay that imageIds are not returned here for SR displaysets? - if (imageId) { - // When the image arrives, render it and store the result in the thumbnailImgSrcMap - newImageSrcEntry[dSet.displaySetInstanceUID] = await getImageSrc( - imageId, - dSet.initialViewport - ); - if (isMounted.current) { - setThumbnailImageSrcMap(prevState => { - return { ...prevState, ...newImageSrcEntry }; - }); - } + if (!imageId) { + return; } + // When the image arrives, render it and store the result in the thumbnailImgSrcMap + newImageSrcEntry[dSet.displaySetInstanceUID] = await getImageSrc( + imageId, + dSet.initialViewport + ); + + setThumbnailImageSrcMap(prevState => { + return { ...prevState, ...newImageSrcEntry }; + }); }); } ); + return () => { + SubscriptionDisplaySetsAdded.unsubscribe(); + }; + }, [getImageSrc, dataSource, displaySetService]); + + useEffect(() => { // TODO: Will this always hold _all_ the displaySets we care about? // DISPLAY_SETS_CHANGED returns `DisplaySerService.activeDisplaySets` const SubscriptionDisplaySetsChanged = displaySetService.subscribe( displaySetService.EVENTS.DISPLAY_SETS_CHANGED, changedDisplaySets => { + const mappedDisplaySets = _mapDisplaySets(changedDisplaySets, thumbnailImageSrcMap); + setDisplaySets(mappedDisplaySets); + } + ); + + const SubscriptionDisplaySetMetaDataInvalidated = displaySetService.subscribe( + displaySetService.EVENTS.DISPLAY_SET_SERIES_METADATA_INVALIDATED, + () => { const mappedDisplaySets = _mapDisplaySets( - changedDisplaySets, + displaySetService.getActiveDisplaySets(), thumbnailImageSrcMap ); + setDisplaySets(mappedDisplaySets); } ); return () => { - SubscriptionDisplaySetsAdded.unsubscribe(); SubscriptionDisplaySetsChanged.unsubscribe(); + SubscriptionDisplaySetMetaDataInvalidated.unsubscribe(); }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [StudyInstanceUIDs, thumbnailImageSrcMap, displaySetService]); - const tabs = _createStudyBrowserTabs( - StudyInstanceUIDs, - studyDisplayList, - displaySets - ); + const tabs = _createStudyBrowserTabs(StudyInstanceUIDs, studyDisplayList, displaySets); // TODO: Should not fire this on "close" function _handleStudyClick(StudyInstanceUID) { - const shouldCollapseStudy = expandedStudyInstanceUIDs.includes( - StudyInstanceUID - ); + const shouldCollapseStudy = expandedStudyInstanceUIDs.includes(StudyInstanceUID); const updatedExpandedStudyInstanceUIDs = shouldCollapseStudy ? // eslint-disable-next-line prettier/prettier - [ - ...expandedStudyInstanceUIDs.filter( - stdyUid => stdyUid !== StudyInstanceUID - ), - ] + [...expandedStudyInstanceUIDs.filter(stdyUid => stdyUid !== StudyInstanceUID)] : [...expandedStudyInstanceUIDs, StudyInstanceUID]; setExpandedStudyInstanceUIDs(updatedExpandedStudyInstanceUIDs); if (!shouldCollapseStudy) { const madeInClient = true; - requestDisplaySetCreationForStudy( - displaySetService, - StudyInstanceUID, - madeInClient - ); + requestDisplaySetCreationForStudy(displaySetService, StudyInstanceUID, madeInClient); } } - const activeDisplaySetInstanceUIDs = - viewports[activeViewportIndex]?.displaySetInstanceUIDs; + const activeDisplaySetInstanceUIDs = viewports.get(activeViewportId)?.displaySetInstanceUIDs; return ( !ds.excludeFromThumbnailBrowser) .forEach(ds => { const imageSrc = thumbnailImageSrcMap[ds.displaySetInstanceUID]; - const componentType = _getComponentType(ds.Modality); + const componentType = _getComponentType(ds); const array = - componentType === 'thumbnail' - ? thumbnailDisplaySets - : thumbnailNoImageDisplaySets; + componentType === 'thumbnail' ? thumbnailDisplaySets : thumbnailNoImageDisplaySets; array.push({ displaySetInstanceUID: ds.displaySetInstanceUID, @@ -317,6 +297,7 @@ function _mapDisplaySets(displaySets, thumbnailImageSrcMap) { numInstances: ds.numImageFrames, countIcon: ds.countIcon, StudyInstanceUID: ds.StudyInstanceUID, + messages: ds.messages, componentType, imageSrc, dragData: { @@ -324,23 +305,17 @@ function _mapDisplaySets(displaySets, thumbnailImageSrcMap) { displaySetInstanceUID: ds.displaySetInstanceUID, // .. Any other data to pass }, + isHydratedForDerivedDisplaySet: ds.isHydrated, }); }); return [...thumbnailDisplaySets, ...thumbnailNoImageDisplaySets]; } -const thumbnailNoImageModalities = [ - 'SR', - 'SEG', - 'SM', - 'RTSTRUCT', - 'RTPLAN', - 'RTDOSE', -]; - -function _getComponentType(Modality) { - if (thumbnailNoImageModalities.includes(Modality)) { +const thumbnailNoImageModalities = ['SR', 'SEG', 'SM', 'RTSTRUCT', 'RTPLAN', 'RTDOSE']; + +function _getComponentType(ds) { + if (thumbnailNoImageModalities.includes(ds.Modality) || ds?.unsupported) { // TODO probably others. return 'thumbnailNoImage'; } @@ -360,11 +335,7 @@ function _getComponentType(Modality) { * @param {object[]} displaySets * @returns tabs - The prop object expected by the StudyBrowser component */ -function _createStudyBrowserTabs( - primaryStudyInstanceUIDs, - studyDisplayList, - displaySets -) { +function _createStudyBrowserTabs(primaryStudyInstanceUIDs, studyDisplayList, displaySets) { const primaryStudies = []; const recentStudies = []; const allStudies = []; diff --git a/extensions/default/src/Panels/WrappedPanelStudyBrowser.tsx b/extensions/default/src/Panels/WrappedPanelStudyBrowser.tsx index b935ebfc767..9be92e25ed0 100644 --- a/extensions/default/src/Panels/WrappedPanelStudyBrowser.tsx +++ b/extensions/default/src/Panels/WrappedPanelStudyBrowser.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useCallback } from 'react'; import PropTypes from 'prop-types'; // import PanelStudyBrowser from './PanelStudyBrowser'; @@ -13,20 +13,14 @@ import requestDisplaySetCreationForStudy from './requestDisplaySetCreationForStu * @param {object} commandsManager * @param {object} extensionManager */ -function WrappedPanelStudyBrowser({ - commandsManager, - extensionManager, - servicesManager, -}) { +function WrappedPanelStudyBrowser({ commandsManager, extensionManager, servicesManager }) { // TODO: This should be made available a different way; route should have // already determined our datasource const dataSource = extensionManager.getDataSources()[0]; - const _getStudiesForPatientByMRN = getStudiesForPatientByMRN.bind( - null, - dataSource - ); - const _getImageSrcFromImageId = _createGetImageSrcFromImageIdFn( - extensionManager + const _getStudiesForPatientByMRN = getStudiesForPatientByMRN.bind(null, dataSource); + const _getImageSrcFromImageId = useCallback( + _createGetImageSrcFromImageIdFn(extensionManager), + [] ); const _requestDisplaySetCreationForStudy = requestDisplaySetCreationForStudy.bind( null, diff --git a/extensions/default/src/Panels/createReportDialogPrompt.tsx b/extensions/default/src/Panels/createReportDialogPrompt.tsx index 166b4b0c4bc..55c92150253 100644 --- a/extensions/default/src/Panels/createReportDialogPrompt.tsx +++ b/extensions/default/src/Panels/createReportDialogPrompt.tsx @@ -1,5 +1,5 @@ -/* eslint-disable react/display-name */ import React from 'react'; + import { ButtonEnums, Dialog, Input, Select } from '@ohif/ui'; export const CREATE_REPORT_DIALOG_RESPONSE = { @@ -7,11 +7,8 @@ export const CREATE_REPORT_DIALOG_RESPONSE = { CREATE_REPORT: 1, }; -export default function createReportDialogPrompt( - uiDialogService, - { extensionManager } -) { - return new Promise(function(resolve, reject) { +export default function CreateReportDialogPrompt(uiDialogService, { extensionManager }) { + return new Promise(function (resolve, reject) { let dialogId = undefined; const _handleClose = () => { @@ -52,10 +49,8 @@ export default function createReportDialogPrompt( const dataSourcesOpts = Object.keys(extensionManager.dataSourceMap) .filter(ds => { - const configuration = - extensionManager.dataSourceDefs[ds]?.configuration; - const supportsStow = - configuration?.supportsStow ?? configuration?.wadoRoot; + const configuration = extensionManager.dataSourceDefs[ds]?.configuration; + const supportsStow = configuration?.supportsStow ?? configuration?.wadoRoot; return supportsStow; }) .map(ds => { @@ -102,34 +97,38 @@ export default function createReportDialogPrompt( }; return ( <> - {dataSourcesOpts.length > 1 && ( - option.value === value.dataSourceName) + .placeHolder + } + value={value.dataSourceName} + onChange={evt => { + setValue(v => ({ ...v, dataSourceName: evt.value })); + }} + isClearable={false} + /> +
)} - +
+ +
); }, diff --git a/extensions/default/src/Panels/debounce.js b/extensions/default/src/Panels/debounce.js index d71793c0fc7..7a5b2bdf3c7 100644 --- a/extensions/default/src/Panels/debounce.js +++ b/extensions/default/src/Panels/debounce.js @@ -4,10 +4,10 @@ // leading edge, instead of the trailing. function debounce(func, wait, immediate) { var timeout; - return function() { + return function () { var context = this, args = arguments; - var later = function() { + var later = function () { timeout = null; if (!immediate) { func.apply(context, args); diff --git a/extensions/default/src/Panels/index.js b/extensions/default/src/Panels/index.js index a5a81473ca7..e81fdc304ac 100644 --- a/extensions/default/src/Panels/index.js +++ b/extensions/default/src/Panels/index.js @@ -1,5 +1,11 @@ import PanelStudyBrowser from './PanelStudyBrowser'; import WrappedPanelStudyBrowser from './WrappedPanelStudyBrowser'; import PanelMeasurementTable from './PanelMeasurementTable'; +import createReportDialogPrompt from './createReportDialogPrompt'; -export { PanelStudyBrowser, WrappedPanelStudyBrowser, PanelMeasurementTable }; +export { + PanelStudyBrowser, + WrappedPanelStudyBrowser, + PanelMeasurementTable, + createReportDialogPrompt, +}; diff --git a/extensions/default/src/Toolbar/Toolbar.tsx b/extensions/default/src/Toolbar/Toolbar.tsx index e9238791bc1..f3812ff5b6c 100644 --- a/extensions/default/src/Toolbar/Toolbar.tsx +++ b/extensions/default/src/Toolbar/Toolbar.tsx @@ -1,61 +1,55 @@ -import React, { useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import classnames from 'classnames'; +import { useViewportGrid } from '@ohif/ui'; -export default function Toolbar({ servicesManager }) { +export default function Toolbar({ + servicesManager, +}: Types.Extensions.ExtensionParams): React.ReactElement { const { toolbarService } = servicesManager.services; + + const [viewportGrid, viewportGridService] = useViewportGrid(); + const [toolbarButtons, setToolbarButtons] = useState([]); - const [buttonState, setButtonState] = useState({ - primaryToolId: '', - toggles: {}, - groups: {}, - }); - // Could track buttons and state separately...? useEffect(() => { - const { unsubscribe: unsub1 } = toolbarService.subscribe( + const updateToolbar = () => { + const toolGroupId = + viewportGridService.getActiveViewportOptionByKey('toolGroupId') ?? 'default'; + setToolbarButtons(toolbarService.getButtonSection(toolGroupId)); + }; + + const { unsubscribe } = toolbarService.subscribe( toolbarService.EVENTS.TOOL_BAR_MODIFIED, - () => setToolbarButtons(toolbarService.getButtonSection('primary')) - ); - const { unsubscribe: unsub2 } = toolbarService.subscribe( - toolbarService.EVENTS.TOOL_BAR_STATE_MODIFIED, - () => setButtonState({ ...toolbarService.state }) + updateToolbar ); + updateToolbar(); + return () => { - unsub1(); - unsub2(); + unsubscribe(); }; - }, [toolbarService]); + }, [toolbarService, viewportGrid]); + + const onInteraction = useCallback( + args => toolbarService.recordInteraction(args), + [toolbarService] + ); return ( <> {toolbarButtons.map(toolDef => { const { id, Component, componentProps } = toolDef; - // TODO: ... - - // isActive if: - // - id is primary? - // - id is in list of "toggled on"? - let isActive; - if (componentProps.type === 'toggle') { - isActive = buttonState.toggles[id]; - } - // Also need... to filter list for splitButton, and set primary based on most recently clicked - // Also need to kill the radioGroup button's magic logic - // Everything should be reactive off these props, so commands can inform ToolbarService - - // These can... Trigger toolbar events based on updates? - // Then sync using useEffect, or simply modify the state here? return ( // The margin for separating the tools on the toolbar should go here and NOT in each individual component (button) item. // This allows for the individual items to be included in other UI components where perhaps alternative margins are desired. -
+
toolbarService.recordInteraction(args)} + onInteraction={onInteraction} servicesManager={servicesManager} />
diff --git a/extensions/default/src/Toolbar/ToolbarButtonWithServices.tsx b/extensions/default/src/Toolbar/ToolbarButtonWithServices.tsx new file mode 100644 index 00000000000..32b7fb51e98 --- /dev/null +++ b/extensions/default/src/Toolbar/ToolbarButtonWithServices.tsx @@ -0,0 +1,75 @@ +import { ToolbarButton } from '@ohif/ui'; +import React, { useEffect, useState } from 'react'; +import PropTypes from 'prop-types'; + +function ToolbarButtonWithServices({ + id, + type, + commands, + onInteraction, + servicesManager, + ...props +}) { + const { toolbarService } = servicesManager?.services || {}; + + const [buttonsState, setButtonState] = useState({ + primaryToolId: '', + toggles: {}, + groups: {}, + }); + const { primaryToolId } = buttonsState; + + const isActive = + (type === 'tool' && id === primaryToolId) || + (type === 'toggle' && buttonsState.toggles[id] === true); + + useEffect(() => { + const { unsubscribe } = toolbarService.subscribe( + toolbarService.EVENTS.TOOL_BAR_STATE_MODIFIED, + state => { + setButtonState({ ...state }); + } + ); + + return () => { + unsubscribe(); + }; + }, [toolbarService]); + + return ( + + ); +} + +ToolbarButtonWithServices.propTypes = { + id: PropTypes.string.isRequired, + type: PropTypes.oneOf(['tool', 'action', 'toggle']).isRequired, + commands: PropTypes.arrayOf( + PropTypes.shape({ + commandName: PropTypes.string.isRequired, + context: PropTypes.string, + }) + ), + onInteraction: PropTypes.func.isRequired, + servicesManager: PropTypes.shape({ + services: PropTypes.shape({ + toolbarService: PropTypes.shape({ + subscribe: PropTypes.func.isRequired, + state: PropTypes.shape({ + primaryToolId: PropTypes.string, + toggles: PropTypes.objectOf(PropTypes.bool), + groups: PropTypes.objectOf(PropTypes.any), + }).isRequired, + }).isRequired, + }).isRequired, + }).isRequired, +}; + +export default ToolbarButtonWithServices; diff --git a/extensions/default/src/Toolbar/ToolbarDivider.tsx b/extensions/default/src/Toolbar/ToolbarDivider.tsx index 5800f75a2c2..30a7cffcfe2 100644 --- a/extensions/default/src/Toolbar/ToolbarDivider.tsx +++ b/extensions/default/src/Toolbar/ToolbarDivider.tsx @@ -1,7 +1,5 @@ import React from 'react'; export default function ToolbarDivider() { - return ( - - ); + return ; } diff --git a/extensions/default/src/Toolbar/ToolbarLayoutSelector.tsx b/extensions/default/src/Toolbar/ToolbarLayoutSelector.tsx index b8e3723c9e6..430cd5abae0 100644 --- a/extensions/default/src/Toolbar/ToolbarLayoutSelector.tsx +++ b/extensions/default/src/Toolbar/ToolbarLayoutSelector.tsx @@ -1,22 +1,37 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useCallback } from 'react'; import PropTypes from 'prop-types'; import { LayoutSelector as OHIFLayoutSelector, ToolbarButton } from '@ohif/ui'; - import { ServicesManager } from '@ohif/core'; -function LayoutSelector({ - rows, - columns, - className, - servicesManager, - ...rest -}) { - const [isOpen, setIsOpen] = useState(false); +function ToolbarLayoutSelectorWithServices({ servicesManager, ...props }) { + const { toolbarService } = servicesManager.services; + + const onSelection = useCallback( + props => { + toolbarService.recordInteraction({ + interactionType: 'action', + commands: [ + { + commandName: 'setViewportGridLayout', + commandOptions: { ...props }, + context: 'DEFAULT', + }, + ], + }); + }, + [toolbarService] + ); + + return ( + + ); +} - const { - hangingProtocolService, - toolbarService, - } = (servicesManager as ServicesManager).services; +function LayoutSelector({ rows, columns, className, onSelection, ...rest }) { + const [isOpen, setIsOpen] = useState(false); const closeOnOutsideClick = () => { if (isOpen) { @@ -24,19 +39,6 @@ function LayoutSelector({ } }; - useEffect(() => { - const { unsubscribe } = hangingProtocolService.subscribe( - hangingProtocolService.EVENTS.PROTOCOL_CHANGED, - evt => { - const { protocol } = evt; - } - ); - - return () => { - unsubscribe(); - }; - }, [hangingProtocolService]); - useEffect(() => { window.addEventListener('click', closeOnOutsideClick); return () => { @@ -47,19 +49,6 @@ function LayoutSelector({ const onInteractionHandler = () => setIsOpen(!isOpen); const DropdownContent = isOpen ? OHIFLayoutSelector : null; - const onSelectionHandler = props => { - toolbarService.recordInteraction({ - interactionType: 'action', - commands: [ - { - commandName: 'setViewportGridLayout', - commandOptions: { ...props }, - context: 'DEFAULT', - }, - ], - }); - }; - return ( ) } @@ -96,4 +85,4 @@ LayoutSelector.defaultProps = { onLayoutChange: () => {}, }; -export default LayoutSelector; +export default ToolbarLayoutSelectorWithServices; diff --git a/extensions/default/src/Toolbar/ToolbarSplitButton.tsx b/extensions/default/src/Toolbar/ToolbarSplitButton.tsx deleted file mode 100644 index 484e8f97dca..00000000000 --- a/extensions/default/src/Toolbar/ToolbarSplitButton.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import { SplitButton } from '@ohif/ui'; - -export default SplitButton; diff --git a/extensions/default/src/Toolbar/ToolbarSplitButtonWithServices.tsx b/extensions/default/src/Toolbar/ToolbarSplitButtonWithServices.tsx new file mode 100644 index 00000000000..a8943b16fad --- /dev/null +++ b/extensions/default/src/Toolbar/ToolbarSplitButtonWithServices.tsx @@ -0,0 +1,186 @@ +import { SplitButton, Icon, ToolbarButton } from '@ohif/ui'; +import React, { useEffect, useState } from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; + +function ToolbarSplitButtonWithServices({ + isRadio, + isAction, + groupId, + primary, + secondary, + items, + renderer, + onInteraction, + servicesManager, +}) { + const { toolbarService } = servicesManager?.services; + + const handleItemClick = (item, index) => { + const { id, type, commands } = item; + onInteraction({ + groupId, + itemId: id, + interactionType: type, + commands, + }); + + setState(state => ({ + ...state, + primary: !isAction && isRadio ? { ...item, index } : state.primary, + isExpanded: false, + items: getSplitButtonItems(items).filter(item => + isRadio && !isAction ? item.index !== index : true + ), + })); + }; + + /* Bubbles up individual item clicks */ + const getSplitButtonItems = items => + items.map((item, index) => ({ + ...item, + index, + onClick: () => handleItemClick(item, index), + })); + + const [buttonsState, setButtonState] = useState({ + primaryToolId: '', + toggles: {}, + groups: {}, + }); + + const [state, setState] = useState({ + primary, + items: getSplitButtonItems(items).filter(item => + isRadio && !isAction ? item.id !== primary.id : true + ), + }); + + const { primaryToolId, toggles } = buttonsState; + + const isPrimaryToggle = state.primary.type === 'toggle'; + + const isPrimaryActive = + (state.primary.type === 'tool' && primaryToolId === state.primary.id) || + (isPrimaryToggle && toggles[state.primary.id] === true); + + const PrimaryButtonComponent = + toolbarService?.getButtonComponentForUIType(state.primary.uiType) ?? ToolbarButton; + + useEffect(() => { + const { unsubscribe } = toolbarService.subscribe( + toolbarService.EVENTS.TOOL_BAR_STATE_MODIFIED, + state => { + setButtonState({ ...state }); + } + ); + + return () => { + unsubscribe(); + }; + }, [toolbarService]); + + const updatedItems = state.items.map(item => { + const isActive = item.type === 'tool' && primaryToolId === item.id; + + // We could have added the + // item.type === 'toggle' && toggles[item.id] === true + // too but that makes the button active when the toggle is active under it + // which feels weird + return { + ...item, + isActive, + }; + }); + + const DefaultListItemRenderer = ({ type, icon, label, t, id }) => { + const isActive = type === 'toggle' && toggles[id] === true; + + return ( +
+ {icon && ( + + + + )} + {t(label)} +
+ ); + }; + + const listItemRenderer = renderer || DefaultListItemRenderer; + + return ( + item.isActive)} + isToggle={isPrimaryToggle} + onInteraction={onInteraction} + Component={props => ( + + )} + /> + ); +} + +ToolbarSplitButtonWithServices.propTypes = { + isRadio: PropTypes.bool, + isAction: PropTypes.bool, + groupId: PropTypes.string, + primary: PropTypes.shape({ + id: PropTypes.string.isRequired, + type: PropTypes.oneOf(['tool', 'action', 'toggle']).isRequired, + uiType: PropTypes.string, + }), + secondary: PropTypes.shape({ + id: PropTypes.string, + icon: PropTypes.string.isRequired, + label: PropTypes.string, + tooltip: PropTypes.string.isRequired, + isActive: PropTypes.bool, + }), + items: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string.isRequired, + type: PropTypes.oneOf(['tool', 'action', 'toggle']).isRequired, + icon: PropTypes.string, + label: PropTypes.string, + tooltip: PropTypes.string, + }) + ), + renderer: PropTypes.func, + onInteraction: PropTypes.func.isRequired, + servicesManager: PropTypes.shape({ + services: PropTypes.shape({ + toolbarService: PropTypes.object, + }), + }), +}; + +ToolbarSplitButtonWithServices.defaultProps = { + isRadio: false, + isAction: false, +}; + +export default ToolbarSplitButtonWithServices; diff --git a/extensions/default/src/ViewerLayout/ViewerHeader.tsx b/extensions/default/src/ViewerLayout/ViewerHeader.tsx new file mode 100644 index 00000000000..00ec7b4796b --- /dev/null +++ b/extensions/default/src/ViewerLayout/ViewerHeader.tsx @@ -0,0 +1,118 @@ +import React from 'react'; +import { useNavigate } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; +import { useLocation } from 'react-router'; + +import { ErrorBoundary, UserPreferences, AboutModal, Header, useModal } from '@ohif/ui'; +import i18n from '@ohif/i18n'; +import { hotkeys } from '@ohif/core'; +import { useAppConfig } from '@state'; +import Toolbar from '../Toolbar/Toolbar'; + +const { availableLanguages, defaultLanguage, currentLanguage } = i18n; + +function ViewerHeader({ hotkeysManager, extensionManager, servicesManager }) { + const [appConfig] = useAppConfig(); + const navigate = useNavigate(); + const location = useLocation(); + + const onClickReturnButton = () => { + const { pathname } = location; + const dataSourceIdx = pathname.indexOf('/', 1); + const query = new URLSearchParams(window.location.search); + const configUrl = query.get('configUrl'); + + const dataSourceName = pathname.substring(dataSourceIdx + 1); + const existingDataSource = extensionManager.getDataSources(dataSourceName); + + const searchQuery = new URLSearchParams(); + if (dataSourceIdx !== -1 && existingDataSource) { + searchQuery.append('datasources', pathname.substring(dataSourceIdx + 1)); + } + + if (configUrl) { + searchQuery.append('configUrl', configUrl); + } + + navigate({ + pathname: '/', + search: decodeURIComponent(searchQuery.toString()), + }); + }; + + const { t } = useTranslation(); + const { show, hide } = useModal(); + const { hotkeyDefinitions, hotkeyDefaults } = hotkeysManager; + const versionNumber = process.env.VERSION_NUMBER; + const commitHash = process.env.COMMIT_HASH; + + const menuOptions = [ + { + title: t('Header:About'), + icon: 'info', + onClick: () => + show({ + content: AboutModal, + title: t('AboutModal:About OHIF Viewer'), + contentProps: { versionNumber, commitHash }, + }), + }, + { + title: t('Header:Preferences'), + icon: 'settings', + onClick: () => + show({ + title: t('UserPreferencesModal:User preferences'), + content: UserPreferences, + contentProps: { + hotkeyDefaults: hotkeysManager.getValidHotkeyDefinitions(hotkeyDefaults), + hotkeyDefinitions, + currentLanguage: currentLanguage(), + availableLanguages, + defaultLanguage, + onCancel: () => { + hotkeys.stopRecord(); + hotkeys.unpause(); + hide(); + }, + onSubmit: ({ hotkeyDefinitions, language }) => { + if (language.value !== currentLanguage().value) { + i18n.changeLanguage(language.value); + } + hotkeysManager.setHotkeys(hotkeyDefinitions); + hide(); + }, + onReset: () => hotkeysManager.restoreDefaultBindings(), + hotkeysModule: hotkeys, + }, + }), + }, + ]; + + if (appConfig.oidc) { + menuOptions.push({ + title: t('Header:Logout'), + icon: 'power-off', + onClick: async () => { + navigate(`/logout?redirect_uri=${encodeURIComponent(window.location.href)}`); + }, + }); + } + + return ( +
+ +
+ +
+
+
+ ); +} + +export default ViewerHeader; diff --git a/extensions/default/src/ViewerLayout/index.tsx b/extensions/default/src/ViewerLayout/index.tsx index aafb74bd8e4..e045bca33c4 100644 --- a/extensions/default/src/ViewerLayout/index.tsx +++ b/extensions/default/src/ViewerLayout/index.tsx @@ -1,29 +1,11 @@ import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; -import { useNavigate } from 'react-router-dom'; -import { useTranslation } from 'react-i18next'; -import { useLocation } from 'react-router'; -import { - SidePanel, - ErrorBoundary, - UserPreferences, - AboutModal, - Header, - useModal, - LoadingIndicatorProgress, -} from '@ohif/ui'; -import i18n from '@ohif/i18n'; -import { - ServicesManager, - HangingProtocolService, - hotkeys, - CommandsManager, -} from '@ohif/core'; +import { SidePanel, ErrorBoundary, LoadingIndicatorProgress } from '@ohif/ui'; +import { ServicesManager, HangingProtocolService, CommandsManager } from '@ohif/core'; import { useAppConfig } from '@state'; -import Toolbar from '../Toolbar/Toolbar'; - -const { availableLanguages, defaultLanguage, currentLanguage } = i18n; +import ViewerHeader from './ViewerHeader'; +import SidePanelWithServices from '../Components/SidePanelWithServices'; function ViewerLayout({ // From Extension Module Params @@ -40,106 +22,9 @@ function ViewerLayout({ rightPanelDefaultClosed = false, }): React.FunctionComponent { const [appConfig] = useAppConfig(); - const navigate = useNavigate(); - const location = useLocation(); - - const onClickReturnButton = () => { - const { pathname } = location; - const dataSourceIdx = pathname.indexOf('/', 1); - // const search = - // dataSourceIdx === -1 - // ? undefined - // : `datasources=${pathname.substring(dataSourceIdx + 1)}`; - - // Todo: Handle parameters in a better way. - const query = new URLSearchParams(window.location.search); - const configUrl = query.get('configUrl'); - - const dataSourceName = pathname.substring(dataSourceIdx + 1); - const existingDataSource = extensionManager.getDataSources(dataSourceName); - - const searchQuery = new URLSearchParams(); - if (dataSourceIdx !== -1 && existingDataSource) { - searchQuery.append('datasources', pathname.substring(dataSourceIdx + 1)); - } - - if (configUrl) { - searchQuery.append('configUrl', configUrl); - } - - navigate({ - pathname: '/', - search: decodeURIComponent(searchQuery.toString()), - }); - }; - - const { t } = useTranslation(); - const { show, hide } = useModal(); - - const [showLoadingIndicator, setShowLoadingIndicator] = useState( - appConfig.showLoadingIndicator - ); const { hangingProtocolService } = servicesManager.services; - - const { hotkeyDefinitions, hotkeyDefaults } = hotkeysManager; - const versionNumber = process.env.VERSION_NUMBER; - const commitHash = process.env.COMMIT_HASH; - - const menuOptions = [ - { - title: t('Header:About'), - icon: 'info', - onClick: () => - show({ - content: AboutModal, - title: 'About OHIF Viewer', - contentProps: { versionNumber, commitHash }, - }), - }, - { - title: t('Header:Preferences'), - icon: 'settings', - onClick: () => - show({ - title: t('UserPreferencesModal:User Preferences'), - content: UserPreferences, - contentProps: { - hotkeyDefaults: hotkeysManager.getValidHotkeyDefinitions( - hotkeyDefaults - ), - hotkeyDefinitions, - currentLanguage: currentLanguage(), - availableLanguages, - defaultLanguage, - onCancel: () => { - hotkeys.stopRecord(); - hotkeys.unpause(); - hide(); - }, - onSubmit: ({ hotkeyDefinitions, language }) => { - i18n.changeLanguage(language.value); - hotkeysManager.setHotkeys(hotkeyDefinitions); - hide(); - }, - onReset: () => hotkeysManager.restoreDefaultBindings(), - hotkeysModule: hotkeys, - }, - }), - }, - ]; - - if (appConfig.oidc) { - menuOptions.push({ - title: t('Header:Logout'), - icon: 'power-off', - onClick: async () => { - navigate( - `/logout?redirect_uri=${encodeURIComponent(window.location.href)}` - ); - }, - }); - } + const [showLoadingIndicator, setShowLoadingIndicator] = useState(appConfig.showLoadingIndicator); /** * Set body classes (tailwindcss) that don't allow vertical @@ -160,7 +45,7 @@ function ViewerLayout({ if (!entry) { throw new Error( - `${id} is not a valid entry for an extension module, please check your configuration or make sure the extension is registered.` + `${id} is not valid for an extension module. Please verify your configuration or ensure that the extension is properly registered. It's also possible that your mode is utilizing a module from an extension that hasn't been included in its dependencies (add the extension to the "extensionDependencies" array in your mode's index.js file)` ); } @@ -221,30 +106,21 @@ function ViewerLayout({ return (
-
- -
- -
-
-
+
- {showLoadingIndicator && ( - - )} + {showLoadingIndicator && } {/* LEFT SIDEPANELS */} {leftPanelComponents.length ? ( - ) : null} {/* TOOLBAR + GRID */} -
-
+
+
{rightPanelComponents.length ? ( - command && - (command.commandName === 'setHangingProtocol' || - command.commandName === 'toggleHangingProtocol'); + (command.commandName === 'setHangingProtocol' || command.commandName === 'toggleHangingProtocol'); const commandsModule = ({ servicesManager, @@ -54,10 +50,7 @@ const commandsModule = ({ } = (servicesManager as ServicesManager).services; // Define a context menu controller for use with any context menus - const contextMenuController = new ContextMenuController( - servicesManager, - commandsManager - ); + const contextMenuController = new ContextMenuController(servicesManager, commandsManager); const actions = { /** @@ -95,11 +88,7 @@ const commandsModule = ({ ...selectorProps, }; - contextMenuController.showContextMenu( - optionsToUse, - element, - defaultPointsPosition - ); + contextMenuController.showContextMenu(optionsToUse, element, defaultPointsPosition); }, /** Close a context menu currently displayed */ @@ -132,7 +121,10 @@ const commandsModule = ({ if (!button.id) { return; } - const { commands, items } = button.props || button; + const { commands, items, primary } = button.props || button; + if (primary) { + enableListener(primary); + } if (items) { items.forEach(enableListener); } @@ -145,7 +137,7 @@ const commandsModule = ({ (!protocolId || protocolId === protocol.id) && (stageIndex === undefined || stageIndex === toggleStageIndex) && (!stageId || stageId === stage.id); - toolbarService.setActive(button.id, isActive); + toolbarService.setToggled(button.id, isActive); }; Object.values(toolbarService.getButtons()).forEach(enableListener); }, @@ -181,36 +173,27 @@ const commandsModule = ({ stageIndex, reset = false, }: HangingProtocolParams): boolean => { + const primaryToolBeforeHPChange = toolbarService.getActivePrimaryTool(); try { // Stores in the state the display set selector id to displaySetUID mapping // Pass in viewportId for the active viewport. This item will get set as // the activeViewportId const state = viewportGridService.getState(); const hpInfo = hangingProtocolService.getState(); - const { - protocol: oldProtocol, - } = hangingProtocolService.getActiveProtocol(); - const stateSyncReduce = reuseCachedLayouts( - state, - hangingProtocolService, - stateSyncService - ); - const { - hangingProtocolStageIndexMap, - viewportGridStore, - displaySetSelectorMap, - } = stateSyncReduce; + const { protocol: oldProtocol } = hangingProtocolService.getActiveProtocol(); + const stateSyncReduce = reuseCachedLayouts(state, hangingProtocolService, stateSyncService); + const { hangingProtocolStageIndexMap, viewportGridStore, displaySetSelectorMap } = + stateSyncReduce; if (!protocolId) { - // Re-use the previous protocol id, and optionally stage + // Reuse the previous protocol id, and optionally stage protocolId = hpInfo.protocolId; if (stageId === undefined && stageIndex === undefined) { stageIndex = hpInfo.stageIndex; } } else if (stageIndex === undefined && stageId === undefined) { // Re-set the same stage as was previously used - const hangingId = `${activeStudyUID || - hpInfo.activeStudyUID}:${protocolId}`; + const hangingId = `${activeStudyUID || hpInfo.activeStudyUID}:${protocolId}`; stageIndex = hangingProtocolStageIndexMap[hangingId]?.stageIndex; } @@ -225,9 +208,9 @@ const commandsModule = ({ hangingProtocolService.setActiveStudyUID(activeStudyUID); } - const storedHanging = `${ - hangingProtocolService.getState().activeStudyUID - }:${protocolId}:${useStageIdx || 0}`; + const storedHanging = `${hangingProtocolService.getState().activeStudyUID}:${protocolId}:${ + useStageIdx || 0 + }`; const restoreProtocol = !reset && viewportGridStore[storedHanging]; @@ -261,20 +244,32 @@ const commandsModule = ({ ]; stateSyncService.store(stateSyncReduce); // This is a default action applied - actions.toggleHpTools(hangingProtocolService.getActiveProtocol()); - // Send the notification about updating the state - if (protocolId !== hpInfo.protocolId) { - const { protocol } = hangingProtocolService.getActiveProtocol(); - // The old protocol callbacks are used for turning off things - // like crosshairs when moving to the new HP - commandsManager.run(oldProtocol.callbacks?.onProtocolExit); - // The new protocol callback is used for things like - // activating modes etc. - commandsManager.run(protocol.callbacks?.onProtocolEnter); + actions.toggleHpTools(); + + // try to use the same tool in the new hanging protocol stage + const primaryButton = toolbarService.getButton(primaryToolBeforeHPChange); + if (primaryButton) { + // is there any type of interaction on this button, if not it might be in the + // items. This is a bit of a hack, but it works for now. + + let interactionType = primaryButton.props?.interactionType; + + if (!interactionType && primaryButton.props?.items) { + const firstItem = primaryButton.props.items[0]; + interactionType = firstItem.props?.interactionType || firstItem.props?.type; + } + + if (interactionType) { + toolbarService.recordInteraction({ + interactionType, + ...primaryButton.props, + }); + } } return true; } catch (e) { - actions.toggleHpTools(hangingProtocolService.getActiveProtocol()); + console.error(e); + actions.toggleHpTools(); uiNotificationService.show({ title: 'Apply Hanging Protocol', message: 'The hanging protocol could not be applied.', @@ -285,19 +280,14 @@ const commandsModule = ({ } }, - toggleHangingProtocol: ({ - protocolId, - stageIndex, - }: HangingProtocolParams): boolean => { + toggleHangingProtocol: ({ protocolId, stageIndex }: HangingProtocolParams): boolean => { const { protocol, stageIndex: desiredStageIndex, activeStudy, } = hangingProtocolService.getActiveProtocol(); const { toggleHangingProtocol } = stateSyncService.getState(); - const storedHanging = `${ - activeStudy.StudyInstanceUID - }:${protocolId}:${stageIndex | 0}`; + const storedHanging = `${activeStudy.StudyInstanceUID}:${protocolId}:${stageIndex | 0}`; if ( protocol.id === protocolId && (stageIndex === undefined || stageIndex === desiredStageIndex) @@ -326,10 +316,7 @@ const commandsModule = ({ }, deltaStage: ({ direction }) => { - const { - protocolId, - stageIndex: oldStageIndex, - } = hangingProtocolService.getState(); + const { protocolId, stageIndex: oldStageIndex } = hangingProtocolService.getState(); const { protocol } = hangingProtocolService.getActiveProtocol(); for ( let stageIndex = oldStageIndex + direction; @@ -358,23 +345,14 @@ const commandsModule = ({ const { protocol } = hangingProtocolService.getActiveProtocol(); const onLayoutChange = protocol.callbacks?.onLayoutChange; if (commandsManager.run(onLayoutChange, { numRows, numCols }) === false) { - console.log( - 'setViewportGridLayout running', - onLayoutChange, - numRows, - numCols - ); + console.log('setViewportGridLayout running', onLayoutChange, numRows, numCols); // Don't apply the layout if the run command returns false return; } const completeLayout = () => { const state = viewportGridService.getState(); - const stateReduce = findViewportsByPosition( - state, - { numRows, numCols }, - stateSyncService - ); + const stateReduce = findViewportsByPosition(state, { numRows, numCols }, stateSyncService); const findOrCreateViewport = layoutFindOrCreate.bind( null, hangingProtocolService, @@ -394,12 +372,9 @@ const commandsModule = ({ toggleOneUp() { const viewportGridState = viewportGridService.getState(); - const { activeViewportIndex, viewports, layout } = viewportGridState; - const { - displaySetInstanceUIDs, - displaySetOptions, - viewportOptions, - } = viewports[activeViewportIndex]; + const { activeViewportId, viewports, layout } = viewportGridState; + const { displaySetInstanceUIDs, displaySetOptions, viewportOptions } = + viewports.get(activeViewportId); if (layout.numCols === 1 && layout.numRows === 1) { // The viewer is in one-up. Check if there is a state to restore/toggle back to. @@ -410,36 +385,44 @@ const commandsModule = ({ } // There is a state to toggle back to. The viewport that was // originally toggled to one up was the former active viewport. - const viewportIndexToUpdate = - toggleOneUpViewportGridStore.activeViewportIndex; - - // Determine which viewports need to be updated. This is particularly - // important when MPR is toggled to one up and a different reconstructable - // is swapped in. Note that currently HangingProtocolService.getViewportsRequireUpdate - // does not support viewport with multiple display sets. - const updatedViewports = + const viewportIdToUpdate = toggleOneUpViewportGridStore.activeViewportId; + + // We are restoring the previous layout but taking into the account that + // the current one up viewport might have a new displaySet dragged and dropped on it. + // updatedViewportsViaHP below contains the viewports applicable to the HP that existed + // prior to the toggle to one-up - including the updated viewports if a display + // set swap were to have occurred. + const updatedViewportsViaHP = displaySetInstanceUIDs.length > 1 ? [] : displaySetInstanceUIDs .map(displaySetInstanceUID => hangingProtocolService.getViewportsRequireUpdate( - viewportIndexToUpdate, + viewportIdToUpdate, displaySetInstanceUID ) ) .flat(); - // This findOrCreateViewport returns either one of the updatedViewports + // findOrCreateViewport returns either one of the updatedViewportsViaHP // returned from the HP service OR if there is not one from the HP service then - // simply returns what was in the previous state. - const findOrCreateViewport = (viewportIndex: number) => { - const viewport = updatedViewports.find( - viewport => viewport.viewportIndex === viewportIndex + // simply returns what was in the previous state for a given position in the layout. + const findOrCreateViewport = (position: number, positionId: string) => { + // Find the viewport for the given position prior to the toggle to one-up. + const preOneUpViewport = Array.from(toggleOneUpViewportGridStore.viewports.values()).find( + viewport => viewport.positionId === positionId + ); + + // Use the viewport id from before the toggle to one-up to find any updates to the viewport. + const viewport = updatedViewportsViaHP.find( + viewport => viewport.viewportId === preOneUpViewport.viewportId ); return viewport - ? { viewportOptions, displaySetOptions, ...viewport } - : toggleOneUpViewportGridStore.viewports[viewportIndex]; + ? // Use the applicable viewport from the HP updated viewports + { viewportOptions, displaySetOptions, ...viewport } + : // Use the previous viewport for the given position + preOneUpViewport; }; const layoutOptions = viewportGridService.getLayoutOptionsFromState( @@ -450,7 +433,7 @@ const commandsModule = ({ viewportGridService.setLayout({ numRows: toggleOneUpViewportGridStore.layout.numRows, numCols: toggleOneUpViewportGridStore.layout.numCols, - activeViewportIndex: viewportIndexToUpdate, + activeViewportId: viewportIdToUpdate, layoutOptions, findOrCreateViewport, }); @@ -490,10 +473,7 @@ const commandsModule = ({ }); }; - subscribeToNextViewportGridChange( - viewportGridService, - clearToggleOneUpViewportGridStore - ); + subscribeToNextViewportGridChange(viewportGridService, clearToggleOneUpViewportGridStore); } }, @@ -520,8 +500,8 @@ const commandsModule = ({ }, openDICOMTagViewer() { - const { activeViewportIndex, viewports } = viewportGridService.getState(); - const activeViewportSpecificData = viewports[activeViewportIndex]; + const { activeViewportId, viewports } = viewportGridService.getState(); + const activeViewportSpecificData = viewports.get(activeViewportId); const { displaySetInstanceUIDs } = activeViewportSpecificData; const displaySets = displaySetService.activeDisplaySets; @@ -552,19 +532,10 @@ const commandsModule = ({ }, scrollActiveThumbnailIntoView: () => { - const { activeViewportIndex, viewports } = viewportGridService.getState(); + const { activeViewportId, viewports } = viewportGridService.getState(); - if ( - !viewports || - activeViewportIndex < 0 || - activeViewportIndex > viewports.length - 1 - ) { - return; - } - - const activeViewport = viewports[activeViewportIndex]; - const activeDisplaySetInstanceUID = - activeViewport.displaySetInstanceUIDs[0]; + const activeViewport = viewports.get(activeViewportId); + const activeDisplaySetInstanceUID = activeViewport.displaySetInstanceUIDs[0]; const thumbnailList = document.querySelector('#ohif-thumbnail-list'); @@ -574,9 +545,7 @@ const commandsModule = ({ const thumbnailListBounds = thumbnailList.getBoundingClientRect(); - const thumbnail = document.querySelector( - `#thumbnail-${activeDisplaySetInstanceUID}` - ); + const thumbnail = document.querySelector(`#thumbnail-${activeDisplaySetInstanceUID}`); if (!thumbnail) { return; @@ -599,14 +568,7 @@ const commandsModule = ({ direction, excludeNonImageModalities, }: UpdateViewportDisplaySetParams) => { - const nonImageModalities = [ - 'SR', - 'SEG', - 'SM', - 'RTSTRUCT', - 'RTPLAN', - 'RTDOSE', - ]; + const nonImageModalities = ['SR', 'SEG', 'SM', 'RTSTRUCT', 'RTPLAN', 'RTDOSE']; // Sort the display sets as per the hanging protocol service viewport/display set scoring system. // The thumbnail list uses the same sorting. @@ -615,9 +577,9 @@ const commandsModule = ({ currentDisplaySets.sort(dsSortFn); - const { activeViewportIndex, viewports } = viewportGridService.getState(); + const { activeViewportId, viewports } = viewportGridService.getState(); - const { displaySetInstanceUIDs } = viewports[activeViewportIndex]; + const { displaySetInstanceUIDs } = viewports.get(activeViewportId); const activeDisplaySetIndex = currentDisplaySets.findIndex(displaySet => displaySetInstanceUIDs.includes(displaySet.displaySetInstanceUID) @@ -627,36 +589,28 @@ const commandsModule = ({ for ( displaySetIndexToShow = activeDisplaySetIndex + direction; - displaySetIndexToShow > -1 && - displaySetIndexToShow < currentDisplaySets.length; + displaySetIndexToShow > -1 && displaySetIndexToShow < currentDisplaySets.length; displaySetIndexToShow += direction ) { if ( !excludeNonImageModalities || - !nonImageModalities.includes( - currentDisplaySets[displaySetIndexToShow].Modality - ) + !nonImageModalities.includes(currentDisplaySets[displaySetIndexToShow].Modality) ) { break; } } - if ( - displaySetIndexToShow < 0 || - displaySetIndexToShow >= currentDisplaySets.length - ) { + if (displaySetIndexToShow < 0 || displaySetIndexToShow >= currentDisplaySets.length) { return; } - const { displaySetInstanceUID } = currentDisplaySets[ - displaySetIndexToShow - ]; + const { displaySetInstanceUID } = currentDisplaySets[displaySetIndexToShow]; let updatedViewports = []; try { updatedViewports = hangingProtocolService.getViewportsRequireUpdate( - activeViewportIndex, + activeViewportId, displaySetInstanceUID ); } catch (error) { diff --git a/extensions/default/src/findViewportsByPosition.ts b/extensions/default/src/findViewportsByPosition.ts index 5132290a47e..6b8b54ab7bd 100644 --- a/extensions/default/src/findViewportsByPosition.ts +++ b/extensions/default/src/findViewportsByPosition.ts @@ -1,13 +1,13 @@ -import { StateSyncService, Types } from '@ohif/core'; +import { StateSyncService } from '@ohif/core'; /** * This find or create viewport is paired with the reduce results from * below, and the action of this viewport is to look for previously filled - * viewports, and to re-use by position id. If there is no filled viewport, + * viewports, and to reuse by position id. If there is no filled viewport, * then one can be re-used from the display set if it isn't going to be displayed. * @param hangingProtocolService - bound parameter supplied before using this * @param viewportsByPosition - bound parameter supplied before using this - * @param viewportIndex - the index to retrieve + * @param position - the position in the grid to retrieve * @param positionId - the current position on screen to retrieve * @param options - the set of options used, so that subsequent calls can * store state that is reset by the setLayout. @@ -17,7 +17,7 @@ import { StateSyncService, Types } from '@ohif/core'; export const findOrCreateViewport = ( hangingProtocolService, viewportsByPosition, - viewportIndex: number, + position: number, positionId: string, options: Record ) => { @@ -32,21 +32,13 @@ export const findOrCreateViewport = ( options.inDisplay = [...viewportsByPosition.initialInDisplay]; } // See if there is a default viewport for new views. - const missing = hangingProtocolService.getMissingViewport( - protocolId, - stageIndex, - options - ); + const missing = hangingProtocolService.getMissingViewport(protocolId, stageIndex, options); if (missing) { - const displaySetInstanceUIDs = missing.displaySetsInfo.map( - it => it.displaySetInstanceUID - ); + const displaySetInstanceUIDs = missing.displaySetsInfo.map(it => it.displaySetInstanceUID); options.inDisplay.push(...displaySetInstanceUIDs); return { displaySetInstanceUIDs, - displaySetOptions: missing.displaySetsInfo.map( - it => it.displaySetOptions - ), + displaySetOptions: missing.displaySetsInfo.map(it => it.displaySetOptions), viewportOptions: { ...missing.viewportOptions, }, @@ -74,24 +66,19 @@ const findViewportsByPosition = ( const viewportsByPosition = { ...syncState.viewportsByPosition }; const initialInDisplay = []; - for (const viewport of viewports) { + viewports.forEach(viewport => { if (viewport.positionId) { const storedViewport = { ...viewport, viewportOptions: { ...viewport.viewportOptions }, }; viewportsByPosition[viewport.positionId] = storedViewport; - // The cache doesn't store the viewport options - it is only useful - // for remembering the type of viewport and UIDs - delete storedViewport.viewportId; - delete storedViewport.viewportOptions.viewportId; } - } + }); for (let row = 0; row < numRows; row++) { for (let col = 0; col < numCols; col++) { - const pos = col + row * numCols; - const positionId = viewports?.[pos]?.positionId || `${col}-${row}`; + const positionId = `${col}-${row}`; const viewport = viewportsByPosition[positionId]; if (viewport?.displaySetInstanceUIDs) { initialInDisplay.push(...viewport.displaySetInstanceUIDs); diff --git a/extensions/default/src/getCustomizationModule.tsx b/extensions/default/src/getCustomizationModule.tsx index 6d03e278602..1da3f1871a5 100644 --- a/extensions/default/src/getCustomizationModule.tsx +++ b/extensions/default/src/getCustomizationModule.tsx @@ -1,6 +1,8 @@ import { CustomizationService } from '@ohif/core'; import React from 'react'; import DataSourceSelector from './Panels/DataSourceSelector'; +import DataSourceConfigurationComponent from './Components/DataSourceConfigurationComponent'; +import { GoogleCloudDataSourceConfigurationAPI } from './DataSourceConfigurationAPI/GoogleCloudDataSourceConfigurationAPI'; /** * @@ -11,7 +13,7 @@ import DataSourceSelector from './Panels/DataSourceSelector'; * custom page for the user to view their profile, or to add a custom * page for login etc. */ -export default function getCustomizationModule() { +export default function getCustomizationModule({ servicesManager, extensionManager }) { return [ { name: 'helloPage', @@ -20,9 +22,7 @@ export default function getCustomizationModule() { routes: [ { path: '/custom', - children: () => ( -

Hello Custom Route

- ), + children: () =>

Hello Custom Route

, }, ], }, @@ -83,7 +83,7 @@ export default function getCustomizationModule() { */ { id: 'ohif.overlayItem', - content: function(props) { + content: function (props) { if (this.condition && !this.condition(props)) { return null; } @@ -105,9 +105,7 @@ export default function getCustomizationModule() { style={{ color: this.color || undefined }} title={this.title || ''} > - {this.label && ( - {this.label} - )} + {this.label && {this.label}} {value} ); @@ -121,7 +119,7 @@ export default function getCustomizationModule() { * This function clones the object and child objects to prevent * changes to the original customization object. */ - transform: function(customizationService: CustomizationService) { + transform: function (customizationService: CustomizationService) { // Don't modify the children, as those are copied by reference const clonedObject = { ...this }; clonedObject.menus = this.menus.map(menu => ({ ...menu })); @@ -136,6 +134,26 @@ export default function getCustomizationModule() { return clonedObject; }, }, + + { + // the generic GUI component to configure a data source using an instance of a BaseDataSourceConfigurationAPI + id: 'ohif.dataSourceConfigurationComponent', + component: DataSourceConfigurationComponent.bind(null, { + servicesManager, + extensionManager, + }), + }, + + { + // The factory for creating an instance of a BaseDataSourceConfigurationAPI for Google Cloud Healthcare + id: 'ohif.dataSourceConfigurationAPI.google', + factory: (dataSourceName: string) => + new GoogleCloudDataSourceConfigurationAPI( + dataSourceName, + servicesManager, + extensionManager + ), + }, ], }, ]; diff --git a/extensions/default/src/getDataSourcesModule.js b/extensions/default/src/getDataSourcesModule.js index 6eab56a2fad..8c918e0c6ac 100644 --- a/extensions/default/src/getDataSourcesModule.js +++ b/extensions/default/src/getDataSourcesModule.js @@ -6,6 +6,7 @@ import { createDicomWebApi } from './DicomWebDataSource/index.js'; import { createDicomJSONApi } from './DicomJSONDataSource/index.js'; import { createDicomLocalApi } from './DicomLocalDataSource/index.js'; import { createDicomWebProxyApi } from './DicomWebProxyDataSource/index.js'; +import { createMergeDataSourceApi } from './MergeDataSource/index'; /** * @@ -32,6 +33,11 @@ function getDataSourcesModule() { type: 'localApi', createDataSource: createDicomLocalApi, }, + { + name: 'merge', + type: 'mergeApi', + createDataSource: createMergeDataSourceApi, + }, ]; } diff --git a/extensions/default/src/getDisplaySetMessages.ts b/extensions/default/src/getDisplaySetMessages.ts new file mode 100644 index 00000000000..842c0bcd124 --- /dev/null +++ b/extensions/default/src/getDisplaySetMessages.ts @@ -0,0 +1,48 @@ +import sortInstancesByPosition from '@ohif/core/src/utils/sortInstancesByPosition'; +import { constructableModalities } from '@ohif/core/src/utils/isDisplaySetReconstructable'; +import { DisplaySetMessage, DisplaySetMessageList } from '@ohif/core'; +import checkMultiFrame from './utils/validations/checkMultiframe'; +import checkSingleFrames from './utils/validations/checkSingleFrames'; +/** + * Checks if a series is reconstructable to a 3D volume. + * + * @param {Object[]} instances An array of `OHIFInstanceMetadata` objects. + */ +export default function getDisplaySetMessages( + instances: Array, + isReconstructable: boolean +): DisplaySetMessageList { + const messages = new DisplaySetMessageList(); + if (!instances.length) { + messages.addMessage(DisplaySetMessage.CODES.NO_VALID_INSTANCES); + return; + } + + const firstInstance = instances[0]; + const { Modality, ImageType, NumberOfFrames } = firstInstance; + // Due to current requirements, LOCALIZER series doesn't have any messages + if (ImageType?.includes('LOCALIZER')) { + return messages; + } + + if (!constructableModalities.includes(Modality)) { + return messages; + } + + const isMultiframe = NumberOfFrames > 1; + // Can't reconstruct if all instances don't have the ImagePositionPatient. + if (!isMultiframe && !instances.every(instance => instance.ImagePositionPatient)) { + messages.addMessage(DisplaySetMessage.CODES.NO_POSITION_INFORMATION); + } + + const sortedInstances = sortInstancesByPosition(instances); + + isMultiframe + ? checkMultiFrame(sortedInstances[0], messages) + : checkSingleFrames(sortedInstances, messages); + + if (!isReconstructable) { + messages.addMessage(DisplaySetMessage.CODES.NOT_RECONSTRUCTABLE); + } + return messages; +} diff --git a/extensions/default/src/getDisplaySetsFromUnsupportedSeries.js b/extensions/default/src/getDisplaySetsFromUnsupportedSeries.js new file mode 100644 index 00000000000..d2da6f5bb7c --- /dev/null +++ b/extensions/default/src/getDisplaySetsFromUnsupportedSeries.js @@ -0,0 +1,30 @@ +import ImageSet from '@ohif/core/src/classes/ImageSet'; +import { DisplaySetMessage, DisplaySetMessageList } from '@ohif/core'; +/** + * Default handler for a instance list with an unsupported sopClassUID + */ +export default function getDisplaySetsFromUnsupportedSeries(instances) { + const imageSet = new ImageSet(instances); + const messages = new DisplaySetMessageList(); + messages.addMessage(DisplaySetMessage.CODES.UNSUPPORTED_DISPLAYSET); + const instance = instances[0]; + + imageSet.setAttributes({ + displaySetInstanceUID: imageSet.uid, // create a local alias for the imageSet UID + SeriesDate: instance.SeriesDate, + SeriesTime: instance.SeriesTime, + SeriesInstanceUID: instance.SeriesInstanceUID, + StudyInstanceUID: instance.StudyInstanceUID, + SeriesNumber: instance.SeriesNumber || 0, + FrameRate: instance.FrameTime, + SOPClassUID: instance.SOPClassUID, + SeriesDescription: instance.SeriesDescription || '', + Modality: instance.Modality, + numImageFrames: instances.length, + unsupported: true, + SOPClassHandlerId: 'unsupported', + isReconstructable: false, + messages, + }); + return [imageSet]; +} diff --git a/extensions/default/src/getHangingProtocolModule.js b/extensions/default/src/getHangingProtocolModule.js index 5875e8e764d..f9ad9bbbb31 100644 --- a/extensions/default/src/getHangingProtocolModule.js +++ b/extensions/default/src/getHangingProtocolModule.js @@ -1,4 +1,5 @@ import hpMNGrid from './hpMNGrid'; +import hpMNCompare from './hpCompare'; const defaultProtocol = { id: 'default', @@ -6,7 +7,6 @@ const defaultProtocol = { // Don't store this hanging protocol as it applies to the currently active // display set by default // cacheId: null, - hasUpdatedPriorsInformation: false, name: 'Default', createdDate: '2021-02-23T19:22:08.894Z', modifiedDate: '2023-04-01', @@ -14,6 +14,7 @@ const defaultProtocol = { editableBy: {}, protocolMatchingRules: [], toolGroupIds: ['default'], + hpInitiationCriteria: { minSeriesLoaded: 1 }, // -1 would be used to indicate active only, whereas other values are // the number of required priors referenced - so 0 means active with // 0 or more priors. @@ -73,6 +74,7 @@ const defaultProtocol = { { viewportOptions: { viewportType: 'stack', + viewportId: 'default', toolGroupId: 'default', // This will specify the initial image options index if it matches in the URL // and will otherwise not specify anything. @@ -108,6 +110,11 @@ function getHangingProtocolModule() { name: hpMNGrid.id, protocol: hpMNGrid, }, + // Create a MxN comparison hanging protocol available by default + { + name: hpMNCompare.id, + protocol: hpMNCompare, + }, ]; } diff --git a/extensions/default/src/getLayoutTemplateModule.js b/extensions/default/src/getLayoutTemplateModule.js index 856d8d12ae1..df89b1076b5 100644 --- a/extensions/default/src/getLayoutTemplateModule.js +++ b/extensions/default/src/getLayoutTemplateModule.js @@ -5,12 +5,7 @@ import ViewerLayout from './ViewerLayout'; - Init layout based on the displaySets and the objects. */ -export default function({ - servicesManager, - extensionManager, - commandsManager, - hotkeysManager, -}) { +export default function ({ servicesManager, extensionManager, commandsManager, hotkeysManager }) { function ViewerLayoutWithServices(props) { return ViewerLayout({ servicesManager, diff --git a/extensions/default/src/getPTImageIdInstanceMetadata.ts b/extensions/default/src/getPTImageIdInstanceMetadata.ts index 0c9acc6197f..bacad3dbfc3 100644 --- a/extensions/default/src/getPTImageIdInstanceMetadata.ts +++ b/extensions/default/src/getPTImageIdInstanceMetadata.ts @@ -1,15 +1,10 @@ import OHIF from '@ohif/core'; -import { - InstanceMetadata, - PhilipsPETPrivateGroup, -} from '@cornerstonejs/calculate-suv/src/types'; +import { InstanceMetadata, PhilipsPETPrivateGroup } from '@cornerstonejs/calculate-suv/src/types'; const metadataProvider = OHIF.classes.MetadataProvider; -export default function getPTImageIdInstanceMetadata( - imageId: string -): InstanceMetadata { +export default function getPTImageIdInstanceMetadata(imageId: string): InstanceMetadata { const dicomMetaData = metadataProvider.get('instance', imageId); if (!dicomMetaData) { @@ -19,40 +14,37 @@ export default function getPTImageIdInstanceMetadata( if ( dicomMetaData.SeriesDate === undefined || dicomMetaData.SeriesTime === undefined || - dicomMetaData.PatientWeight === undefined || dicomMetaData.CorrectedImage === undefined || dicomMetaData.Units === undefined || !dicomMetaData.RadiopharmaceuticalInformationSequence || - dicomMetaData.RadiopharmaceuticalInformationSequence[0] - .RadionuclideHalfLife === undefined || - dicomMetaData.RadiopharmaceuticalInformationSequence[0] - .RadionuclideTotalDose === undefined || + dicomMetaData.RadiopharmaceuticalInformationSequence[0].RadionuclideHalfLife === undefined || + dicomMetaData.RadiopharmaceuticalInformationSequence[0].RadionuclideTotalDose === undefined || dicomMetaData.DecayCorrection === undefined || dicomMetaData.AcquisitionDate === undefined || dicomMetaData.AcquisitionTime === undefined || - (dicomMetaData.RadiopharmaceuticalInformationSequence[0] - .RadiopharmaceuticalStartDateTime === undefined && - dicomMetaData.RadiopharmaceuticalInformationSequence[0] - .RadiopharmaceuticalStartTime === undefined) + (dicomMetaData.RadiopharmaceuticalInformationSequence[0].RadiopharmaceuticalStartDateTime === + undefined && + dicomMetaData.RadiopharmaceuticalInformationSequence[0].RadiopharmaceuticalStartTime === + undefined) ) { throw new Error('required metadata are missing'); } + if (dicomMetaData.PatientWeight === undefined) { + console.warn('PatientWeight missing from PT instance metadata'); + } + const instanceMetadata: InstanceMetadata = { CorrectedImage: dicomMetaData.CorrectedImage, Units: dicomMetaData.Units, RadionuclideHalfLife: - dicomMetaData.RadiopharmaceuticalInformationSequence[0] - .RadionuclideHalfLife, + dicomMetaData.RadiopharmaceuticalInformationSequence[0].RadionuclideHalfLife, RadionuclideTotalDose: - dicomMetaData.RadiopharmaceuticalInformationSequence[0] - .RadionuclideTotalDose, + dicomMetaData.RadiopharmaceuticalInformationSequence[0].RadionuclideTotalDose, RadiopharmaceuticalStartDateTime: - dicomMetaData.RadiopharmaceuticalInformationSequence[0] - .RadiopharmaceuticalStartDateTime, + dicomMetaData.RadiopharmaceuticalInformationSequence[0].RadiopharmaceuticalStartDateTime, RadiopharmaceuticalStartTime: - dicomMetaData.RadiopharmaceuticalInformationSequence[0] - .RadiopharmaceuticalStartTime, + dicomMetaData.RadiopharmaceuticalInformationSequence[0].RadiopharmaceuticalStartTime, DecayCorrection: dicomMetaData.DecayCorrection, PatientWeight: dicomMetaData.PatientWeight, SeriesDate: dicomMetaData.SeriesDate, @@ -78,17 +70,11 @@ export default function getPTImageIdInstanceMetadata( instanceMetadata.GEPrivatePostInjectionDateTime = dicomMetaData['0009100d']; } - if ( - dicomMetaData.FrameReferenceTime && - dicomMetaData.FrameReferenceTime !== undefined - ) { + if (dicomMetaData.FrameReferenceTime && dicomMetaData.FrameReferenceTime !== undefined) { instanceMetadata.FrameReferenceTime = dicomMetaData.FrameReferenceTime; } - if ( - dicomMetaData.ActualFrameDuration && - dicomMetaData.ActualFrameDuration !== undefined - ) { + if (dicomMetaData.ActualFrameDuration && dicomMetaData.ActualFrameDuration !== undefined) { instanceMetadata.ActualFrameDuration = dicomMetaData.ActualFrameDuration; } @@ -108,10 +94,7 @@ function convertInterfaceTimeToString(time): string { const minutes = `${time.minutes || '00'}`.padStart(2, '0'); const seconds = `${time.seconds || '00'}`.padStart(2, '0'); - const fractionalSeconds = `${time.fractionalSeconds || '000000'}`.padEnd( - 6, - '0' - ); + const fractionalSeconds = `${time.fractionalSeconds || '000000'}`.padEnd(6, '0'); const timeString = `${hours}${minutes}${seconds}.${fractionalSeconds}`; return timeString; diff --git a/extensions/default/src/getPanelModule.tsx b/extensions/default/src/getPanelModule.tsx index 6f733e49620..e294805dfbd 100644 --- a/extensions/default/src/getPanelModule.tsx +++ b/extensions/default/src/getPanelModule.tsx @@ -1,16 +1,13 @@ import React from 'react'; import { WrappedPanelStudyBrowser, PanelMeasurementTable } from './Panels'; +import i18n from 'i18next'; // TODO: // - No loading UI exists yet // - cancel promises when component is destroyed // - show errors in UI for thumbnails if promise fails -function getPanelModule({ - commandsManager, - extensionManager, - servicesManager, -}) { +function getPanelModule({ commandsManager, extensionManager, servicesManager }) { const wrappedMeasurementPanel = () => { return ( { const instance = instances[0]; const imageSet = new ImageSet(instances); - const { - value: isReconstructable, - averageSpacingBetweenFrames, - } = isDisplaySetReconstructable(instances); - + const { value: isReconstructable, averageSpacingBetweenFrames } = + isDisplaySetReconstructable(instances); // set appropriate attributes to image set... + const messages = getDisplaySetMessages(instances, isReconstructable); + imageSet.setAttributes({ displaySetInstanceUID: imageSet.uid, // create a local alias for the imageSet UID SeriesDate: instance.SeriesDate, @@ -36,6 +37,7 @@ const makeDisplaySet = instances => { numImageFrames: instances.length, SOPClassHandlerId: `${id}.sopClassHandlerModule.${sopClassHandlerName}`, isReconstructable, + messages, averageSpacingBetweenFrames: averageSpacingBetweenFrames || null, }); @@ -44,9 +46,7 @@ const makeDisplaySet = instances => { if (shallSort) { imageSet.sortBy((a, b) => { // Sort by InstanceNumber (0020,0013) - return ( - (parseInt(a.InstanceNumber) || 0) - (parseInt(b.InstanceNumber) || 0) - ); + return (parseInt(a.InstanceNumber) || 0) - (parseInt(b.InstanceNumber) || 0); }); } @@ -210,6 +210,11 @@ function getSopClassHandlerModule() { sopClassUids, getDisplaySetsFromSeries, }, + { + name: 'not-supported-display-sets-handler', + sopClassUids: [], + getDisplaySetsFromSeries: getDisplaySetsFromUnsupportedSeries, + }, ]; } diff --git a/extensions/default/src/getToolbarModule.tsx b/extensions/default/src/getToolbarModule.tsx index 60dd94ddc65..eacdffd7b9c 100644 --- a/extensions/default/src/getToolbarModule.tsx +++ b/extensions/default/src/getToolbarModule.tsx @@ -1,7 +1,7 @@ -import { ToolbarButton } from '@ohif/ui'; -import ToolbarDivider from './Toolbar/ToolbarDivider.tsx'; -import ToolbarLayoutSelector from './Toolbar/ToolbarLayoutSelector.tsx'; -import ToolbarSplitButton from './Toolbar/ToolbarSplitButton.tsx'; +import ToolbarDivider from './Toolbar/ToolbarDivider'; +import ToolbarLayoutSelectorWithServices from './Toolbar/ToolbarLayoutSelector'; +import ToolbarSplitButtonWithServices from './Toolbar/ToolbarSplitButtonWithServices'; +import ToolbarButtonWithServices from './Toolbar/ToolbarButtonWithServices'; export default function getToolbarModule({ commandsManager, servicesManager }) { return [ @@ -12,27 +12,27 @@ export default function getToolbarModule({ commandsManager, servicesManager }) { }, { name: 'ohif.action', - defaultComponent: ToolbarButton, + defaultComponent: ToolbarButtonWithServices, clickHandler: () => {}, }, { name: 'ohif.radioGroup', - defaultComponent: ToolbarButton, + defaultComponent: ToolbarButtonWithServices, clickHandler: () => {}, }, { name: 'ohif.splitButton', - defaultComponent: ToolbarSplitButton, + defaultComponent: ToolbarSplitButtonWithServices, clickHandler: () => {}, }, { name: 'ohif.layoutSelector', - defaultComponent: ToolbarLayoutSelector, + defaultComponent: ToolbarLayoutSelectorWithServices, clickHandler: (evt, clickedBtn, btnSectionName) => {}, }, { name: 'ohif.toggle', - defaultComponent: ToolbarButton, + defaultComponent: ToolbarButtonWithServices, clickHandler: () => {}, }, ]; diff --git a/extensions/default/src/hpCompare.ts b/extensions/default/src/hpCompare.ts new file mode 100644 index 00000000000..7bd934be116 --- /dev/null +++ b/extensions/default/src/hpCompare.ts @@ -0,0 +1,188 @@ +import { Types } from '@ohif/core'; + +const defaultDisplaySetSelector = { + studyMatchingRules: [ + { + // The priorInstance is a study counter that indicates what position this study is in + // and the value comes from the options parameter. + attribute: 'studyInstanceUIDsIndex', + from: 'options', + required: true, + constraint: { + equals: { value: 0 }, + }, + }, + ], + seriesMatchingRules: [ + { + attribute: 'numImageFrames', + constraint: { + greaterThan: { value: 0 }, + }, + }, + // This display set will select the specified items by preference + // It has no affect if nothing is specified in the URL. + { + attribute: 'isDisplaySetFromUrl', + weight: 10, + constraint: { + equals: true, + }, + }, + ], +}; + +const priorDisplaySetSelector = { + studyMatchingRules: [ + { + // The priorInstance is a study counter that indicates what position this study is in + // and the value comes from the options parameter. + attribute: 'studyInstanceUIDsIndex', + from: 'options', + required: true, + constraint: { + equals: { value: 1 }, + }, + }, + ], + seriesMatchingRules: [ + { + attribute: 'numImageFrames', + constraint: { + greaterThan: { value: 0 }, + }, + }, + // This display set will select the specified items by preference + // It has no affect if nothing is specified in the URL. + { + attribute: 'isDisplaySetFromUrl', + weight: 10, + constraint: { + equals: true, + }, + }, + ], +}; + +const currentDisplaySet = { + id: 'defaultDisplaySetId', +}; + +const priorDisplaySet = { + id: 'priorDisplaySetId', +}; + +const currentViewport0 = { + viewportOptions: { + toolGroupId: 'default', + allowUnmatchedView: true, + }, + displaySets: [currentDisplaySet], +}; + +const currentViewport1 = { + ...currentViewport0, + displaySets: [ + { + ...currentDisplaySet, + matchedDisplaySetsIndex: 1, + }, + ], +}; + +const priorViewport0 = { + ...currentViewport0, + displaySets: [priorDisplaySet], +}; + +const priorViewport1 = { + ...priorViewport0, + displaySets: [ + { + ...priorDisplaySet, + matchedDisplaySetsIndex: 1, + }, + ], +}; + +/** + * This hanging protocol can be activated on the primary mode by directly + * referencing it in a URL or by directly including it within a mode, e.g.: + * `&hangingProtocolId=@ohif/mnGrid` added to the viewer URL + * It is not included in the viewer mode by default. + */ +const hpMNCompare: Types.HangingProtocol.Protocol = { + id: '@ohif/hpCompare', + description: 'Compare two studies in various layouts', + name: 'Compare Two Studies', + numberOfPriorsReferenced: 1, + protocolMatchingRules: [ + { + id: 'Two Studies', + weight: 1000, + attribute: 'StudyInstanceUID', + // The 'from' attribute says where to get the 'attribute' value from. In this case + // prior means the second study in the study list. + from: 'prior', + required: true, + constraint: { + notNull: true, + }, + }, + ], + toolGroupIds: ['default'], + displaySetSelectors: { + defaultDisplaySetId: defaultDisplaySetSelector, + priorDisplaySetId: priorDisplaySetSelector, + }, + defaultViewport: { + viewportOptions: { + viewportType: 'stack', + toolGroupId: 'default', + allowUnmatchedView: true, + }, + displaySets: [ + { + id: 'defaultDisplaySetId', + matchedDisplaySetsIndex: -1, + }, + ], + }, + stages: [ + { + name: '2x2', + stageActivation: { + enabled: { + minViewportsMatched: 4, + }, + }, + viewportStructure: { + layoutType: 'grid', + properties: { + rows: 2, + columns: 2, + }, + }, + viewports: [currentViewport0, priorViewport0, currentViewport1, priorViewport1], + }, + + { + name: '2x1', + stageActivation: { + enabled: { + minViewportsMatched: 2, + }, + }, + viewportStructure: { + layoutType: 'grid', + properties: { + rows: 1, + columns: 2, + }, + }, + viewports: [currentViewport0, priorViewport0], + }, + ], +}; + +export default hpMNCompare; diff --git a/extensions/default/src/hpMNGrid.ts b/extensions/default/src/hpMNGrid.ts index 2e3206615c0..b65cc0b70ed 100644 --- a/extensions/default/src/hpMNGrid.ts +++ b/extensions/default/src/hpMNGrid.ts @@ -7,7 +7,6 @@ import { Types } from '@ohif/core'; * It is not included in the viewer mode by default. */ const hpMN: Types.HangingProtocol.Protocol = { - hasUpdatedPriorsInformation: false, id: '@ohif/mnGrid', description: 'Has various hanging protocol grid layouts', name: '2x2', @@ -30,6 +29,7 @@ const hpMN: Types.HangingProtocol.Protocol = { constraint: { greaterThan: { value: 0 }, }, + required: true, }, // This display set will select the specified items by preference // It has no affect if nothing is specified in the URL. diff --git a/extensions/default/src/index.ts b/extensions/default/src/index.ts index 2b023d41517..aa466244366 100644 --- a/extensions/default/src/index.ts +++ b/extensions/default/src/index.ts @@ -11,11 +11,10 @@ import getStudiesForPatientByMRN from './Panels/getStudiesForPatientByMRN'; import getCustomizationModule from './getCustomizationModule'; import { id } from './id.js'; import preRegistration from './init'; -import { - ContextMenuController, - CustomizableContextMenuTypes, -} from './CustomizableContextMenu'; +import { ContextMenuController, CustomizableContextMenuTypes } from './CustomizableContextMenu'; import * as dicomWebUtils from './DicomWebDataSource/utils'; +import { createReportDialogPrompt } from './Panels'; +import createReportAsync from './Actions/createReportAsync'; const defaultExtension: Types.Extensions.Extension = { /** @@ -51,4 +50,6 @@ export { CustomizableContextMenuTypes, getStudiesForPatientByMRN, dicomWebUtils, + createReportDialogPrompt, + createReportAsync, }; diff --git a/extensions/default/src/init.ts b/extensions/default/src/init.ts index b979bb4f668..f77bc7ed06a 100644 --- a/extensions/default/src/init.ts +++ b/extensions/default/src/init.ts @@ -13,17 +13,11 @@ const metadataProvider = classes.MetadataProvider; export default function init({ servicesManager, configuration = {} }): void { const { stateSyncService } = servicesManager.services; // Add - DicomMetadataStore.subscribe( - DicomMetadataStore.EVENTS.INSTANCES_ADDED, - handlePETImageMetadata - ); + DicomMetadataStore.subscribe(DicomMetadataStore.EVENTS.INSTANCES_ADDED, handlePETImageMetadata); // If the metadata for PET has changed by the user (e.g. manually changing the PatientWeight) // we need to recalculate the SUV Scaling Factors - DicomMetadataStore.subscribe( - DicomMetadataStore.EVENTS.SERIES_UPDATED, - handlePETImageMetadata - ); + DicomMetadataStore.subscribe(DicomMetadataStore.EVENTS.SERIES_UPDATED, handlePETImageMetadata); // viewportGridStore is a sync state which stores the entire // ViewportGridService getState, by the keys `::` @@ -55,45 +49,43 @@ export default function init({ servicesManager, configuration = {} }): void { } const handlePETImageMetadata = ({ SeriesInstanceUID, StudyInstanceUID }) => { - const { instances } = DicomMetadataStore.getSeries( - StudyInstanceUID, - SeriesInstanceUID - ); + const { instances } = DicomMetadataStore.getSeries(StudyInstanceUID, SeriesInstanceUID); - const modality = instances[0].Modality; - if (modality !== 'PT') { + if (!instances?.length) { return; } - const imageIds = instances.map(instance => instance.imageId); - const instanceMetadataArray = []; - imageIds.forEach(imageId => { - const instanceMetadata = getPTImageIdInstanceMetadata(imageId); - if (instanceMetadata) { - instanceMetadataArray.push(instanceMetadata); - } - }); - if (!instanceMetadataArray.length) { + const modality = instances[0].Modality; + + if (!modality || modality !== 'PT') { return; } + const imageIds = instances.map(instance => instance.imageId); + const instanceMetadataArray = []; + // try except block to prevent errors when the metadata is not correct - let suvScalingFactors; try { - suvScalingFactors = calculateSUVScalingFactors(instanceMetadataArray); + imageIds.forEach(imageId => { + const instanceMetadata = getPTImageIdInstanceMetadata(imageId); + if (instanceMetadata) { + instanceMetadataArray.push(instanceMetadata); + } + }); + + if (!instanceMetadataArray.length) { + return; + } + + const suvScalingFactors = calculateSUVScalingFactors(instanceMetadataArray); + instanceMetadataArray.forEach((instanceMetadata, index) => { + metadataProvider.addCustomMetadata( + imageIds[index], + 'scalingModule', + suvScalingFactors[index] + ); + }); } catch (error) { console.log(error); } - - if (!suvScalingFactors) { - return; - } - - instanceMetadataArray.forEach((instanceMetadata, index) => { - metadataProvider.addCustomMetadata( - imageIds[index], - 'scalingModule', - suvScalingFactors[index] - ); - }); }; diff --git a/extensions/default/src/utils/calculateScanAxisNormal.ts b/extensions/default/src/utils/calculateScanAxisNormal.ts new file mode 100644 index 00000000000..fe278f4b2b3 --- /dev/null +++ b/extensions/default/src/utils/calculateScanAxisNormal.ts @@ -0,0 +1,20 @@ +import { vec3 } from 'gl-matrix'; + +/** + * Calculates the scanAxisNormal based on a image orientation vector extract from a frame + * @param {*} imageOrientation + * @returns + */ +export default function calculateScanAxisNormal(imageOrientation) { + const rowCosineVec = vec3.fromValues( + imageOrientation[0], + imageOrientation[1], + imageOrientation[2] + ); + const colCosineVec = vec3.fromValues( + imageOrientation[3], + imageOrientation[4], + imageOrientation[5] + ); + return vec3.cross(vec3.create(), rowCosineVec, colCosineVec); +} diff --git a/extensions/default/src/utils/findSRWithSameSeriesDescription.ts b/extensions/default/src/utils/findSRWithSameSeriesDescription.ts index d50c76976cd..af565c9aadc 100644 --- a/extensions/default/src/utils/findSRWithSameSeriesDescription.ts +++ b/extensions/default/src/utils/findSRWithSameSeriesDescription.ts @@ -19,20 +19,12 @@ export default function findSRWithSameSeriesDescription( ): Types.SeriesMetadata { const activeDisplaySets = displaySetService.getActiveDisplaySets(); const srDisplaySets = activeDisplaySets.filter(ds => ds.Modality === 'SR'); - const sameSeries = srDisplaySets.find( - ds => ds.SeriesDescription === SeriesDescription - ); + const sameSeries = srDisplaySets.find(ds => ds.SeriesDescription === SeriesDescription); if (sameSeries) { console.log('Storing to same series', sameSeries); const { instance } = sameSeries; - const { - SeriesInstanceUID, - SeriesDescription, - SeriesDate, - SeriesTime, - SeriesNumber, - Modality, - } = instance; + const { SeriesInstanceUID, SeriesDescription, SeriesDate, SeriesTime, SeriesNumber, Modality } = + instance; return { SeriesInstanceUID, SeriesDescription, diff --git a/extensions/default/src/utils/getDirectURL.js b/extensions/default/src/utils/getDirectURL.js index 40b320dfbe9..823d7eaef07 100644 --- a/extensions/default/src/utils/getDirectURL.js +++ b/extensions/default/src/utils/getDirectURL.js @@ -35,15 +35,14 @@ const getDirectURL = (config, params) => { value.DirectRetrieveURL = URL.createObjectURL(blob); return value.DirectRetrieveURL; } - if ( - !singlepart || - (singlepart !== true && singlepart.indexOf(fetchPart) === -1) - ) { + if (!singlepart || (singlepart !== true && singlepart.indexOf(fetchPart) === -1)) { if (value.retrieveBulkData) { - return value.retrieveBulkData().then(arr => { - value.DirectRetrieveURL = URL.createObjectURL( - new Blob([arr], { type: defaultType }) - ); + // Try the specified retrieve type. + const options = { + mediaType: defaultType, + }; + return value.retrieveBulkData(options).then(arr => { + value.DirectRetrieveURL = URL.createObjectURL(new Blob([arr], { type: defaultType })); return value.DirectRetrieveURL; }); } @@ -58,8 +57,7 @@ const getDirectURL = (config, params) => { const hasQuery = BulkDataURI.indexOf('?') !== -1; const hasAccept = BulkDataURI.indexOf('accept=') !== -1; const acceptUri = - BulkDataURI + - (hasAccept ? '' : (hasQuery ? '&' : '?') + `accept=${defaultType}`); + BulkDataURI + (hasAccept ? '' : (hasQuery ? '&' : '?') + `accept=${defaultType}`); if (tag === 'PixelData' || tag === 'EncapsulatedDocument') { return `${wadoRoot}/studies/${StudyInstanceUID}/series/${SeriesInstanceUID}/instances/${SOPInstanceUID}/rendered`; diff --git a/extensions/default/src/utils/reuseCachedLayouts.ts b/extensions/default/src/utils/reuseCachedLayouts.ts index 35bb1774fef..f6c2ed1f12d 100644 --- a/extensions/default/src/utils/reuseCachedLayouts.ts +++ b/extensions/default/src/utils/reuseCachedLayouts.ts @@ -19,22 +19,24 @@ const reuseCachedLayout = ( hangingProtocolService: HangingProtocolService, syncService: StateSyncService ): ReturnType => { - const { activeViewportIndex, viewports, layout } = state; + const { activeViewportId } = state; + const { protocol } = hangingProtocolService.getActiveProtocol(); const hpInfo = hangingProtocolService.getState(); const { protocolId, stageIndex, activeStudyUID } = hpInfo; - const { protocol } = hangingProtocolService.getActiveProtocol(); + + const syncState = syncService.getState(); + const viewportGridStore = { ...syncState.viewportGridStore }; + const displaySetSelectorMap = { ...syncState.displaySetSelectorMap }; + const stage = protocol.stages[stageIndex]; const storeId = `${activeStudyUID}:${protocolId}:${stageIndex}`; - const syncState = syncService.getState(); const cacheId = `${activeStudyUID}:${protocolId}`; - const viewportGridStore = { ...syncState.viewportGridStore }; const hangingProtocolStageIndexMap = { ...syncState.hangingProtocolStageIndexMap, }; - const displaySetSelectorMap = { ...syncState.displaySetSelectorMap }; const { rows, columns } = stage.viewportStructure.properties; const custom = - stage.viewports.length !== state.viewports.length || + stage.viewports.length !== state.viewports.size || state.layout.numRows !== rows || state.layout.numCols !== columns; @@ -44,30 +46,28 @@ const reuseCachedLayout = ( viewportGridStore[storeId] = { ...state }; } - for (let idx = 0; idx < state.viewports.length; idx++) { - const viewport = state.viewports[idx]; + state.viewports.forEach((viewport, viewportId) => { const { displaySetOptions, displaySetInstanceUIDs } = viewport; if (!displaySetOptions) { - continue; + return; } for (let i = 0; i < displaySetOptions.length; i++) { const displaySetUID = displaySetInstanceUIDs[i]; if (!displaySetUID) { continue; } - if (idx === activeViewportIndex && i === 0) { - displaySetSelectorMap[ - `${activeStudyUID}:activeDisplaySet:0` - ] = displaySetUID; + if (viewportId === activeViewportId && i === 0) { + displaySetSelectorMap[`${activeStudyUID}:activeDisplaySet:0`] = displaySetUID; } if (displaySetOptions[i]?.id) { displaySetSelectorMap[ - `${activeStudyUID}:${displaySetOptions[i].id}:${displaySetOptions[i] - .matchedDisplaySetsIndex || 0}` + `${activeStudyUID}:${displaySetOptions[i].id}:${ + displaySetOptions[i].matchedDisplaySetsIndex || 0 + }` ] = displaySetUID; } } - } + }); return { hangingProtocolStageIndexMap, diff --git a/extensions/default/src/utils/validations/areAllImageComponentsEqual.ts b/extensions/default/src/utils/validations/areAllImageComponentsEqual.ts new file mode 100644 index 00000000000..97040fbff58 --- /dev/null +++ b/extensions/default/src/utils/validations/areAllImageComponentsEqual.ts @@ -0,0 +1,24 @@ +import toNumber from '@ohif/core/src/utils/toNumber'; + +/** + * Check if all voxels in series images has same number of components (samplesPerPixel) + * @param {*} instances + * @returns + */ +export default function areAllImageComponentsEqual(instances: Array): boolean { + if (!instances?.length) { + return false; + } + const firstImage = instances[0]; + const firstImageSamplesPerPixel = toNumber(firstImage.SamplesPerPixel); + + for (let i = 1; i < instances.length; i++) { + const instance = instances[i]; + const { SamplesPerPixel } = instance; + + if (SamplesPerPixel !== firstImageSamplesPerPixel) { + return false; + } + } + return true; +} diff --git a/extensions/default/src/utils/validations/areAllImageDimensionsEqual.ts b/extensions/default/src/utils/validations/areAllImageDimensionsEqual.ts new file mode 100644 index 00000000000..93a350a894e --- /dev/null +++ b/extensions/default/src/utils/validations/areAllImageDimensionsEqual.ts @@ -0,0 +1,25 @@ +import toNumber from '@ohif/core/src/utils/toNumber'; + +/** + * Check if the frames in a series has different dimensions + * @param {*} instances + * @returns + */ +export default function areAllImageDimensionsEqual(instances: Array): boolean { + if (!instances?.length) { + return false; + } + const firstImage = instances[0]; + const firstImageRows = toNumber(firstImage.Rows); + const firstImageColumns = toNumber(firstImage.Columns); + + for (let i = 1; i < instances.length; i++) { + const instance = instances[i]; + const { Rows, Columns } = instance; + + if (Rows !== firstImageRows || Columns !== firstImageColumns) { + return false; + } + } + return true; +} diff --git a/extensions/default/src/utils/validations/areAllImageOrientationsEqual.ts b/extensions/default/src/utils/validations/areAllImageOrientationsEqual.ts new file mode 100644 index 00000000000..310bd84488a --- /dev/null +++ b/extensions/default/src/utils/validations/areAllImageOrientationsEqual.ts @@ -0,0 +1,25 @@ +import toNumber from '@ohif/core/src/utils/toNumber'; +import { _isSameOrientation } from '@ohif/core/src/utils/isDisplaySetReconstructable'; + +/** + * Check is the series has frames with different orientations + * @param {*} instances + * @returns + */ +export default function areAllImageOrientationsEqual(instances: Array): boolean { + if (!instances?.length) { + return false; + } + const firstImage = instances[0]; + const firstImageOrientationPatient = toNumber(firstImage.ImageOrientationPatient); + + for (let i = 1; i < instances.length; i++) { + const instance = instances[i]; + const imageOrientationPatient = toNumber(instance.ImageOrientationPatient); + + if (!_isSameOrientation(imageOrientationPatient, firstImageOrientationPatient)) { + return false; + } + } + return true; +} diff --git a/extensions/default/src/utils/validations/areAllImagePositionsEqual.ts b/extensions/default/src/utils/validations/areAllImagePositionsEqual.ts new file mode 100644 index 00000000000..723aa7877ec --- /dev/null +++ b/extensions/default/src/utils/validations/areAllImagePositionsEqual.ts @@ -0,0 +1,69 @@ +import { vec3 } from 'gl-matrix'; +import toNumber from '@ohif/core/src/utils/toNumber'; +import { _getPerpendicularDistance } from '@ohif/core/src/utils/isDisplaySetReconstructable'; +import calculateScanAxisNormal from '../calculateScanAxisNormal'; + +/** + * Checks if there is a position shift between consecutive frames + * @param {*} previousPosition + * @param {*} actualPosition + * @param {*} scanAxisNormal + * @param {*} averageSpacingBetweenFrames + * @returns + */ +function _checkSeriesPositionShift( + previousPosition, + actualPosition, + scanAxisNormal, + averageSpacingBetweenFrames +) { + // predicted position should be the previous position added by the multiplication + // of the scanAxisNormal and the average spacing between frames + const predictedPosition = vec3.scaleAndAdd( + vec3.create(), + previousPosition, + scanAxisNormal, + averageSpacingBetweenFrames + ); + return vec3.distance(actualPosition, predictedPosition) > averageSpacingBetweenFrames; +} + +/** + * Checks if a series has position shifts between consecutive frames + * @param {*} instances + * @returns + */ +export default function areAllImagePositionsEqual(instances: Array): boolean { + if (!instances?.length) { + return false; + } + const firstImageOrientationPatient = toNumber(instances[0].ImageOrientationPatient); + if (!firstImageOrientationPatient) { + return false; + } + const scanAxisNormal = calculateScanAxisNormal(firstImageOrientationPatient); + const firstImagePositionPatient = toNumber(instances[0].ImagePositionPatient); + const lastIpp = toNumber(instances[instances.length - 1].ImagePositionPatient); + + const averageSpacingBetweenFrames = + _getPerpendicularDistance(firstImagePositionPatient, lastIpp) / (instances.length - 1); + + let previousImagePositionPatient = firstImagePositionPatient; + for (let i = 1; i < instances.length; i++) { + const instance = instances[i]; + const imagePositionPatient = toNumber(instance.ImagePositionPatient); + + if ( + _checkSeriesPositionShift( + previousImagePositionPatient, + imagePositionPatient, + scanAxisNormal, + averageSpacingBetweenFrames + ) + ) { + return false; + } + previousImagePositionPatient = imagePositionPatient; + } + return true; +} diff --git a/extensions/default/src/utils/validations/areAllImageSpacingEqual.ts b/extensions/default/src/utils/validations/areAllImageSpacingEqual.ts new file mode 100644 index 00000000000..49906c3c22d --- /dev/null +++ b/extensions/default/src/utils/validations/areAllImageSpacingEqual.ts @@ -0,0 +1,64 @@ +import { + _getPerpendicularDistance, + _getSpacingIssue, + reconstructionIssues, +} from '@ohif/core/src/utils/isDisplaySetReconstructable'; +import { DisplaySetMessage } from '@ohif/core'; +import toNumber from '@ohif/core/src/utils/toNumber'; +import { DisplaySetMessageList } from '@ohif/core'; + +/** + * Checks if series has spacing issues + * @param {*} instances + * @param {*} warnings + */ +export default function areAllImageSpacingEqual( + instances: Array, + messages: DisplaySetMessageList +): void { + if (!instances?.length) { + return; + } + const firstImagePositionPatient = toNumber(instances[0].ImagePositionPatient); + if (!firstImagePositionPatient) { + return; + } + const lastIpp = toNumber(instances[instances.length - 1].ImagePositionPatient); + + const averageSpacingBetweenFrames = + _getPerpendicularDistance(firstImagePositionPatient, lastIpp) / (instances.length - 1); + + let previousImagePositionPatient = firstImagePositionPatient; + + const issuesFound = []; + for (let i = 1; i < instances.length; i++) { + const instance = instances[i]; + const imagePositionPatient = toNumber(instance.ImagePositionPatient); + + const spacingBetweenFrames = _getPerpendicularDistance( + imagePositionPatient, + previousImagePositionPatient + ); + + const spacingIssue = _getSpacingIssue(spacingBetweenFrames, averageSpacingBetweenFrames); + + if (spacingIssue) { + const issue = spacingIssue.issue; + + // avoid multiple warning of the same thing + if (!issuesFound.includes(issue)) { + issuesFound.push(issue); + if (issue === reconstructionIssues.MISSING_FRAMES) { + messages.addMessage(DisplaySetMessage.CODES.MISSING_FRAMES); + } else if (issue === reconstructionIssues.IRREGULAR_SPACING) { + messages.addMessage(DisplaySetMessage.CODES.IRREGULAR_SPACING); + } + } + // we just want to find issues not how many + if (issuesFound.length > 1) { + break; + } + } + previousImagePositionPatient = imagePositionPatient; + } +} diff --git a/extensions/default/src/utils/validations/checkMultiframe.ts b/extensions/default/src/utils/validations/checkMultiframe.ts new file mode 100644 index 00000000000..f4f4a2090f2 --- /dev/null +++ b/extensions/default/src/utils/validations/checkMultiframe.ts @@ -0,0 +1,25 @@ +import { + hasPixelMeasurements, + hasOrientation, + hasPosition, +} from '@ohif/core/src/utils/isDisplaySetReconstructable'; +import { DisplaySetMessage, DisplaySetMessageList } from '@ohif/core'; + +/** + * Check various multi frame issues. It calls OHIF core functions + * @param {*} multiFrameInstance + * @param {*} warnings + */ +export default function checkMultiFrame(multiFrameInstance, messages: DisplaySetMessageList): void { + if (!hasPixelMeasurements(multiFrameInstance)) { + messages.addMessage(DisplaySetMessage.CODES.MULTIFRAME_NO_PIXEL_MEASUREMENTS); + } + + if (!hasOrientation(multiFrameInstance)) { + messages.addMessage(DisplaySetMessage.CODES.MULTIFRAME_NO_ORIENTATION); + } + + if (!hasPosition(multiFrameInstance)) { + messages.addMessage(DisplaySetMessage.CODES.MULTIFRAME_NO_POSITION_INFORMATION); + } +} diff --git a/extensions/default/src/utils/validations/checkSingleFrames.ts b/extensions/default/src/utils/validations/checkSingleFrames.ts new file mode 100644 index 00000000000..677d66f8480 --- /dev/null +++ b/extensions/default/src/utils/validations/checkSingleFrames.ts @@ -0,0 +1,35 @@ +import areAllImageDimensionsEqual from './areAllImageDimensionsEqual'; +import areAllImageComponentsEqual from './areAllImageComponentsEqual'; +import areAllImageOrientationsEqual from './areAllImageOrientationsEqual'; +import areAllImagePositionsEqual from './areAllImagePositionsEqual'; +import areAllImageSpacingEqual from './areAllImageSpacingEqual'; +import { DisplaySetMessage, DisplaySetMessageList } from '@ohif/core'; + +/** + * Runs various checks in a single frame series + * @param {*} instances + * @param {*} warnings + */ +export default function checkSingleFrames( + instances: Array, + messages: DisplaySetMessageList +): void { + if (instances.length > 2) { + if (!areAllImageDimensionsEqual(instances)) { + messages.addMessage(DisplaySetMessage.CODES.INCONSISTENT_DIMENSIONS); + } + + if (!areAllImageComponentsEqual(instances)) { + messages.addMessage(DisplaySetMessage.CODES.INCONSISTENT_COMPONENTS); + } + + if (!areAllImageOrientationsEqual(instances)) { + messages.addMessage(DisplaySetMessage.CODES.INCONSISTENT_ORIENTATIONS); + } + + if (!areAllImagePositionsEqual(instances)) { + messages.addMessage(DisplaySetMessage.CODES.INCONSISTENT_POSITION_INFORMATION); + } + areAllImageSpacingEqual(instances, messages); + } +} diff --git a/extensions/dicom-microscopy/.prettierrc b/extensions/dicom-microscopy/.prettierrc index b80ec6b3474..ef83baaef93 100644 --- a/extensions/dicom-microscopy/.prettierrc +++ b/extensions/dicom-microscopy/.prettierrc @@ -1,8 +1,11 @@ { + "plugins": ["prettier-plugin-tailwindcss"], "trailingComma": "es5", - "printWidth": 80, + "printWidth": 100, "proseWrap": "always", "tabWidth": 2, "semi": true, - "singleQuote": true + "singleQuote": true, + "arrowParens": "avoid", + "endOfLine": "auto" } diff --git a/extensions/dicom-microscopy/.webpack/webpack.dev.js b/extensions/dicom-microscopy/.webpack/webpack.dev.js index 96d68096d9d..6aea859ca74 100644 --- a/extensions/dicom-microscopy/.webpack/webpack.dev.js +++ b/extensions/dicom-microscopy/.webpack/webpack.dev.js @@ -7,7 +7,6 @@ const ENTRY = { app: `${SRC_DIR}/index.tsx`, }; - module.exports = (env, argv) => { return webpackCommon(env, argv, { SRC_DIR, DIST_DIR, ENTRY }); }; diff --git a/extensions/dicom-microscopy/CHANGELOG.md b/extensions/dicom-microscopy/CHANGELOG.md new file mode 100644 index 00000000000..c8318c4de36 --- /dev/null +++ b/extensions/dicom-microscopy/CHANGELOG.md @@ -0,0 +1,938 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [3.8.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.59...v3.8.0-beta.60) (2024-03-15) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.58...v3.8.0-beta.59) (2024-03-08) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.57...v3.8.0-beta.58) (2024-03-05) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.56...v3.8.0-beta.57) (2024-02-28) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.55...v3.8.0-beta.56) (2024-02-22) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.54...v3.8.0-beta.55) (2024-02-21) + + +### Features + +* **resize:** Optimize resizing process and maintain zoom level ([#3889](https://github.com/OHIF/Viewers/issues/3889)) ([b3a0faf](https://github.com/OHIF/Viewers/commit/b3a0faf5f5f0a1993b2b017eb4cc1216164ea2c6)) + + + + + +# [3.8.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.53...v3.8.0-beta.54) (2024-02-14) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.52...v3.8.0-beta.53) (2024-02-05) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.51...v3.8.0-beta.52) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.50...v3.8.0-beta.51) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.49...v3.8.0-beta.50) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.48...v3.8.0-beta.49) (2024-01-19) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.47...v3.8.0-beta.48) (2024-01-17) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.46...v3.8.0-beta.47) (2024-01-12) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.45...v3.8.0-beta.46) (2024-01-12) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.44...v3.8.0-beta.45) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.43...v3.8.0-beta.44) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.42...v3.8.0-beta.43) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.41...v3.8.0-beta.42) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.40...v3.8.0-beta.41) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.39...v3.8.0-beta.40) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.38...v3.8.0-beta.39) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.37...v3.8.0-beta.38) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.36...v3.8.0-beta.37) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.35...v3.8.0-beta.36) (2023-12-15) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.34...v3.8.0-beta.35) (2023-12-14) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.33...v3.8.0-beta.34) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.32...v3.8.0-beta.33) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.31...v3.8.0-beta.32) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.30...v3.8.0-beta.31) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.29...v3.8.0-beta.30) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.28...v3.8.0-beta.29) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.27...v3.8.0-beta.28) (2023-12-08) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.26...v3.8.0-beta.27) (2023-12-06) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.25...v3.8.0-beta.26) (2023-11-28) + + +### Bug Fixes + +* **SM:** drag and drop is now fixed for SM ([#3813](https://github.com/OHIF/Viewers/issues/3813)) ([f1a6764](https://github.com/OHIF/Viewers/commit/f1a67647aed635437b188cea7cf5d5a8fb974bbe)) + + + + + +# [3.8.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.24...v3.8.0-beta.25) (2023-11-27) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.23...v3.8.0-beta.24) (2023-11-24) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.22...v3.8.0-beta.23) (2023-11-24) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.21...v3.8.0-beta.22) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.20...v3.8.0-beta.21) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.19...v3.8.0-beta.20) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.18...v3.8.0-beta.19) (2023-11-18) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.17...v3.8.0-beta.18) (2023-11-15) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.16...v3.8.0-beta.17) (2023-11-13) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.15...v3.8.0-beta.16) (2023-11-13) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.14...v3.8.0-beta.15) (2023-11-10) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.14](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.13...v3.8.0-beta.14) (2023-11-10) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.13](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.12...v3.8.0-beta.13) (2023-11-09) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.12](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.11...v3.8.0-beta.12) (2023-11-08) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.11](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.10...v3.8.0-beta.11) (2023-11-08) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.10](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.9...v3.8.0-beta.10) (2023-11-03) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.9](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.8...v3.8.0-beta.9) (2023-11-02) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.8](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.7...v3.8.0-beta.8) (2023-10-31) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.7](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.6...v3.8.0-beta.7) (2023-10-30) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.6](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.5...v3.8.0-beta.6) (2023-10-25) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.5](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.4...v3.8.0-beta.5) (2023-10-24) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.4](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.3...v3.8.0-beta.4) (2023-10-23) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.3](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.2...v3.8.0-beta.3) (2023-10-23) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.1...v3.8.0-beta.2) (2023-10-19) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.0...v3.8.0-beta.1) (2023-10-19) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.8.0-beta.0](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.110...v3.8.0-beta.0) (2023-10-12) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.110](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.109...v3.7.0-beta.110) (2023-10-11) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.109](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.108...v3.7.0-beta.109) (2023-10-11) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.107...v3.7.0-beta.108) (2023-10-10) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.106...v3.7.0-beta.107) (2023-10-10) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.105...v3.7.0-beta.106) (2023-10-10) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.104...v3.7.0-beta.105) (2023-10-10) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.103...v3.7.0-beta.104) (2023-10-09) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.102...v3.7.0-beta.103) (2023-10-09) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.101...v3.7.0-beta.102) (2023-10-06) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.100...v3.7.0-beta.101) (2023-10-06) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.99...v3.7.0-beta.100) (2023-10-06) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.98...v3.7.0-beta.99) (2023-10-04) + + +### Bug Fixes + +* **measurement and microscopy:** various small fixes for measurement and microscopy side panel ([#3696](https://github.com/OHIF/Viewers/issues/3696)) ([c1d5ee7](https://github.com/OHIF/Viewers/commit/c1d5ee7e3f7f4c0c6bed9ae81eba5519741c5155)) + + + + + +# [3.7.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.97...v3.7.0-beta.98) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.96...v3.7.0-beta.97) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.95...v3.7.0-beta.96) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.94...v3.7.0-beta.95) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.93...v3.7.0-beta.94) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.92...v3.7.0-beta.93) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.91...v3.7.0-beta.92) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.90...v3.7.0-beta.91) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.89...v3.7.0-beta.90) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.88...v3.7.0-beta.89) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.87...v3.7.0-beta.88) (2023-10-03) + + +### Bug Fixes + +* **config:** support more values for the useSharedArrayBuffer ([#3688](https://github.com/OHIF/Viewers/issues/3688)) ([1129c15](https://github.com/OHIF/Viewers/commit/1129c155d2c7d46c98a5df7c09879aa3d459fa7e)) + + + + + +# [3.7.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.86...v3.7.0-beta.87) (2023-09-29) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.85...v3.7.0-beta.86) (2023-09-29) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22) + + +### Features + +* **segmentation mode:** Add create, and export SEG with Brushes ([#3632](https://github.com/OHIF/Viewers/issues/3632)) ([48bbd62](https://github.com/OHIF/Viewers/commit/48bbd6281a497ea68670239f5426a10ee6c56dc1)) + + + + + +# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01) + + +### Features + +* **grid:** remove viewportIndex and only rely on viewportId ([#3591](https://github.com/OHIF/Viewers/issues/3591)) ([4c6ff87](https://github.com/OHIF/Viewers/commit/4c6ff873e887cc30ffc09223f5cb99e5f94c9cdd)) + + + + + +# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy + + + + + +# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23) + +**Note:** Version bump only for package @ohif/extension-dicom-microscopy diff --git a/extensions/dicom-microscopy/babel.config.js b/extensions/dicom-microscopy/babel.config.js index 92fbbdeaf95..a38ddda2127 100644 --- a/extensions/dicom-microscopy/babel.config.js +++ b/extensions/dicom-microscopy/babel.config.js @@ -10,7 +10,7 @@ module.exports = { modules: 'commonjs', debug: false, }, - "@babel/preset-typescript", + '@babel/preset-typescript', ], '@babel/preset-react', ], @@ -26,7 +26,7 @@ module.exports = { // WebPack handles ES6 --> Target Syntax ['@babel/preset-env', { modules: false }], '@babel/preset-react', - "@babel/preset-typescript", + '@babel/preset-typescript', ], ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'], }, @@ -35,7 +35,7 @@ module.exports = { // WebPack handles ES6 --> Target Syntax ['@babel/preset-env', { modules: false }], '@babel/preset-react', - "@babel/preset-typescript", + '@babel/preset-typescript', ], plugins: ['react-hot-loader/babel'], ignore: ['**/*.test.jsx', '**/*.test.js', '__snapshots__', '__tests__'], diff --git a/extensions/dicom-microscopy/package.json b/extensions/dicom-microscopy/package.json index 9e8915abb59..191183457d8 100644 --- a/extensions/dicom-microscopy/package.json +++ b/extensions/dicom-microscopy/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-dicom-microscopy", - "version": "3.7.0-beta.46", + "version": "3.8.0-beta.60", "description": "OHIF extension for DICOM microscopy", "author": "Bill Wallace, md-prog", "license": "MIT", @@ -21,6 +21,8 @@ "yarn": ">=1.18.0" }, "scripts": { + "clean": "shx rm -rf dist", + "clean:deep": "yarn run clean && shx rm -rf node_modules", "dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --output-pathinfo", "dev:dicom-pdf": "yarn run dev", "build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js", @@ -28,10 +30,10 @@ "start": "yarn run dev" }, "peerDependencies": { - "@ohif/core": "3.7.0-beta.46", - "@ohif/extension-default": "3.7.0-beta.46", - "@ohif/i18n": "3.7.0-beta.46", - "@ohif/ui": "3.7.0-beta.46", + "@ohif/core": "3.8.0-beta.60", + "@ohif/extension-default": "3.8.0-beta.60", + "@ohif/i18n": "3.8.0-beta.60", + "@ohif/ui": "3.8.0-beta.60", "prop-types": "^15.6.2", "react": "^17.0.2", "react-dom": "^17.0.2", diff --git a/extensions/dicom-microscopy/src/DicomMicroscopySRSopClassHandler.js b/extensions/dicom-microscopy/src/DicomMicroscopySRSopClassHandler.js index 9f5fb3979c3..a195a801e8a 100644 --- a/extensions/dicom-microscopy/src/DicomMicroscopySRSopClassHandler.js +++ b/extensions/dicom-microscopy/src/DicomMicroscopySRSopClassHandler.js @@ -17,32 +17,21 @@ function _getReferencedFrameOfReferenceUID(naturalizedDataset) { const { ContentSequence } = naturalizedDataset; const imagingMeasurementsContentItem = ContentSequence.find( - ci => - ci.ConceptNameCodeSequence.CodeValue === - DCM_CODE_VALUES.IMAGING_MEASUREMENTS + ci => ci.ConceptNameCodeSequence.CodeValue === DCM_CODE_VALUES.IMAGING_MEASUREMENTS ); const firstMeasurementGroupContentItem = toArray( imagingMeasurementsContentItem.ContentSequence - ).find( - ci => - ci.ConceptNameCodeSequence.CodeValue === DCM_CODE_VALUES.MEASUREMENT_GROUP - ); + ).find(ci => ci.ConceptNameCodeSequence.CodeValue === DCM_CODE_VALUES.MEASUREMENT_GROUP); - const imageRegionContentItem = toArray( - firstMeasurementGroupContentItem.ContentSequence - ).find( + const imageRegionContentItem = toArray(firstMeasurementGroupContentItem.ContentSequence).find( ci => ci.ConceptNameCodeSequence.CodeValue === DCM_CODE_VALUES.IMAGE_REGION ); return imageRegionContentItem.ReferencedFrameOfReferenceUID; } -function _getDisplaySetsFromSeries( - instances, - servicesManager, - extensionManager -) { +function _getDisplaySetsFromSeries(instances, servicesManager, extensionManager) { // If the series has no instances, stop here if (!instances || !instances.length) { throw new Error('No instances were provided'); @@ -53,14 +42,12 @@ function _getDisplaySetsFromSeries( const instance = instances[0]; // TODO ! Consumption of DICOMMicroscopySRSOPClassHandler to a derived dataset or normal dataset? - // TOOD -> Easy to swap this to a "non-derived" displaySet, but unfortunately need to put it in a different extension. + // TODO -> Easy to swap this to a "non-derived" displaySet, but unfortunately need to put it in a different extension. const naturalizedDataset = DicomMetadataStore.getSeries( instance.StudyInstanceUID, instance.SeriesInstanceUID ).instances[0]; - const ReferencedFrameOfReferenceUID = _getReferencedFrameOfReferenceUID( - naturalizedDataset - ); + const ReferencedFrameOfReferenceUID = _getReferencedFrameOfReferenceUID(naturalizedDataset); const { FrameOfReferenceUID, @@ -98,23 +85,19 @@ function _getDisplaySetsFromSeries( loadError: false, }; - displaySet.load = function(referencedDisplaySet) { - return loadSR(microscopyService, displaySet, referencedDisplaySet).catch( - error => { - displaySet.isLoaded = false; - displaySet.loadError = true; - throw new Error(error); - } - ); + displaySet.load = function (referencedDisplaySet) { + return loadSR(microscopyService, displaySet, referencedDisplaySet).catch(error => { + displaySet.isLoaded = false; + displaySet.loadError = true; + throw new Error(error); + }); }; - displaySet.getSourceDisplaySet = function() { + displaySet.getSourceDisplaySet = function () { let allDisplaySets = []; const studyMetadata = DicomMetadataStore.getStudy(StudyInstanceUID); studyMetadata.series.forEach(series => { - const displaySets = displaySetService.getDisplaySetsForSeries( - series.SeriesInstanceUID - ); + const displaySets = displaySetService.getDisplaySetsForSeries(series.SeriesInstanceUID); allDisplaySets = allDisplaySets.concat(displaySets); }); return getSourceDisplaySet(allDisplaySets, displaySet); @@ -123,16 +106,9 @@ function _getDisplaySetsFromSeries( return [displaySet]; } -export default function getDicomMicroscopySRSopClassHandler({ - servicesManager, - extensionManager, -}) { +export default function getDicomMicroscopySRSopClassHandler({ servicesManager, extensionManager }) { const getDisplaySetsFromSeries = instances => { - return _getDisplaySetsFromSeries( - instances, - servicesManager, - extensionManager - ); + return _getDisplaySetsFromSeries(instances, servicesManager, extensionManager); }; return { diff --git a/extensions/dicom-microscopy/src/DicomMicroscopySopClassHandler.js b/extensions/dicom-microscopy/src/DicomMicroscopySopClassHandler.js index d7c6af93e38..36e33fda30b 100644 --- a/extensions/dicom-microscopy/src/DicomMicroscopySopClassHandler.js +++ b/extensions/dicom-microscopy/src/DicomMicroscopySopClassHandler.js @@ -9,11 +9,7 @@ const SOP_CLASS_UIDS = { const SOPClassHandlerId = '@ohif/extension-dicom-microscopy.sopClassHandlerModule.DicomMicroscopySopClassHandler'; -function _getDisplaySetsFromSeries( - instances, - servicesManager, - extensionManager -) { +function _getDisplaySetsFromSeries(instances, servicesManager, extensionManager) { // If the series has no instances, stop here if (!instances || !instances.length) { throw new Error('No instances were provided'); @@ -112,16 +108,9 @@ function _getDisplaySetsFromSeries( return [displaySet]; } -export default function getDicomMicroscopySopClassHandler({ - servicesManager, - extensionManager, -}) { +export default function getDicomMicroscopySopClassHandler({ servicesManager, extensionManager }) { const getDisplaySetsFromSeries = instances => { - return _getDisplaySetsFromSeries( - instances, - servicesManager, - extensionManager - ); + return _getDisplaySetsFromSeries(instances, servicesManager, extensionManager); }; return { diff --git a/extensions/dicom-microscopy/src/DicomMicroscopyViewport.css b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.css index db3ee245591..9ab38920d3d 100644 --- a/extensions/dicom-microscopy/src/DicomMicroscopyViewport.css +++ b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.css @@ -62,7 +62,8 @@ font-size: 10px; z-index: 11; color: var(--ol-foreground-color); - text-shadow: -1.5px 0 var(--ol-partial-background-color), + text-shadow: + -1.5px 0 var(--ol-partial-background-color), 0 1.5px var(--ol-partial-background-color), 1.5px 0 var(--ol-partial-background-color), 0 -1.5px var(--ol-partial-background-color); @@ -74,7 +75,8 @@ text-align: center; bottom: 25px; color: var(--ol-foreground-color); - text-shadow: -1.5px 0 var(--ol-partial-background-color), + text-shadow: + -1.5px 0 var(--ol-partial-background-color), 0 1.5px var(--ol-partial-background-color), 1.5px 0 var(--ol-partial-background-color), 0 -1.5px var(--ol-partial-background-color); @@ -147,13 +149,17 @@ .DicomMicroscopyViewer .ol-rotate { top: 0.5em; right: 0.5em; - transition: opacity 0.25s linear, visibility 0s linear; + transition: + opacity 0.25s linear, + visibility 0s linear; } .DicomMicroscopyViewer .ol-rotate.ol-hidden { opacity: 0; visibility: hidden; - transition: opacity 0.25s linear, visibility 0s linear 0.25s; + transition: + opacity 0.25s linear, + visibility 0s linear 0.25s; } .DicomMicroscopyViewer .ol-zoom-extent { diff --git a/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx index de2220593a9..39b4873586a 100644 --- a/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx +++ b/extensions/dicom-microscopy/src/DicomMicroscopyViewport.tsx @@ -49,12 +49,12 @@ class DicomMicroscopyViewport extends Component { static propTypes = { viewportData: PropTypes.object, - activeViewportIndex: PropTypes.number, + activeViewportId: PropTypes.string, setViewportActive: PropTypes.func, // props from OHIF Viewport Grid displaySets: PropTypes.array, - viewportIndex: PropTypes.number, + viewportId: PropTypes.string, viewportLabel: PropTypes.string, dataSource: PropTypes.object, viewportOptions: PropTypes.object, @@ -103,10 +103,7 @@ class DicomMicroscopyViewport extends Component { // you should only do this once. async installOpenLayersRenderer(container, displaySet) { const loadViewer = async metadata => { - const { - viewer: DicomMicroscopyViewer, - metadata: metadataUtils, - } = await import( + const { viewer: DicomMicroscopyViewer, metadata: metadataUtils } = await import( /* webpackChunkName: "dicom-microscopy-viewer" */ 'dicom-microscopy-viewer' ); const microscopyViewer = DicomMicroscopyViewer.VolumeImageViewer; @@ -163,10 +160,7 @@ class DicomMicroscopyViewport extends Component { metadata.forEach(m => { // NOTE: depending on different data source, image.ImageType sometimes // is a string, not a string array. - m.ImageType = - typeof m.ImageType === 'string' - ? m.ImageType.split('\\') - : m.ImageType; + m.ImageType = typeof m.ImageType === 'string' ? m.ImageType.split('\\') : m.ImageType; const inst = cleanDenaturalizedDataset( dcmjs.data.DicomMetaDictionary.denaturalizeDataset(m), @@ -211,11 +205,7 @@ class DicomMicroscopyViewport extends Component { this.viewer = new microscopyViewer(options); - if ( - this.overlayElement && - this.overlayElement.current && - this.viewer.addViewportOverlay - ) { + if (this.overlayElement && this.overlayElement.current && this.viewer.addViewportOverlay) { this.viewer.addViewportOverlay({ element: this.overlayElement.current, coordinates: [0, 0], // TODO: dicom-microscopy-viewer documentation says this can be false to be automatically, but it is not. @@ -230,7 +220,7 @@ class DicomMicroscopyViewport extends Component { this.managedViewer = this.microscopyService.addViewer( this.viewer, - this.props.viewportIndex, + this.props.viewportId, container, StudyInstanceUID, SeriesInstanceUID @@ -259,24 +249,17 @@ class DicomMicroscopyViewport extends Component { } componentDidMount() { - const { displaySets, viewportIndex } = this.props; - const displaySet = displaySets[viewportIndex]; - this.installOpenLayersRenderer(this.container.current, displaySet).then( - () => { - this.setState({ isLoaded: true }); - } - ); + const { displaySets, viewportOptions } = this.props; + const { viewportId } = viewportOptions; + // Todo-rename: this is always getting the 0 + const displaySet = displaySets[0]; + this.installOpenLayersRenderer(this.container.current, displaySet).then(() => { + this.setState({ isLoaded: true }); + }); } - componentDidUpdate( - prevProps: Readonly<{}>, - prevState: Readonly<{}>, - snapshot?: any - ): void { - if ( - this.managedViewer && - prevProps.displaySets !== this.props.displaySets - ) { + componentDidUpdate(prevProps: Readonly<{}>, prevState: Readonly<{}>, snapshot?: any): void { + if (this.managedViewer && prevProps.displaySets !== this.props.displaySets) { const { displaySets } = this.props; const displaySet = displaySets[0]; @@ -295,14 +278,10 @@ class DicomMicroscopyViewport extends Component { } setViewportActiveHandler = () => { - const { - setViewportActive, - viewportIndex, - activeViewportIndex, - } = this.props; - - if (viewportIndex !== activeViewportIndex) { - setViewportActive(viewportIndex); + const { setViewportActive, viewportId, activeViewportId } = this.props; + + if (viewportId !== activeViewportId) { + setViewportActive(viewportId); } }; @@ -319,9 +298,7 @@ class DicomMicroscopyViewport extends Component { >
-
+
{displaySet && firstInstance.imageId && (
{ReactResizeDetector && ( - + )} {this.state.error ? (

{JSON.stringify(this.state.error)}

@@ -345,7 +318,7 @@ class DicomMicroscopyViewport extends Component {
)} {this.state.isLoaded ? null : ( - + )}
); diff --git a/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx b/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx index 88d5f43923b..79c0842e5c5 100644 --- a/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx +++ b/extensions/dicom-microscopy/src/components/MicroscopyPanel/MicroscopyPanel.tsx @@ -1,11 +1,6 @@ import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; -import { - ServicesManager, - ExtensionManager, - CommandsManager, - DicomMetadataStore, -} from '@ohif/core'; +import { ServicesManager, ExtensionManager, CommandsManager, DicomMetadataStore } from '@ohif/core'; import { MeasurementTable, Icon, ButtonGroup, Button } from '@ohif/ui'; import { withTranslation, WithTranslation } from 'react-i18next'; import { EVENTS as MicroscopyEvents } from '../../services/MicroscopyService'; @@ -52,7 +47,7 @@ const formatLength = (length, unit) => { interface IMicroscopyPanelProps extends WithTranslation { viewports: PropTypes.array; - activeViewportIndex: PropTypes.number; + activeViewportId: PropTypes.string; // onSaveComplete?: PropTypes.func; // callback when successfully saved annotations @@ -73,9 +68,7 @@ interface IMicroscopyPanelProps extends WithTranslation { function MicroscopyPanel(props: IMicroscopyPanelProps) { const { microscopyService } = props.servicesManager.services; - const [studyInstanceUID, setStudyInstanceUID] = useState( - null as string | null - ); + const [studyInstanceUID, setStudyInstanceUID] = useState(null as string | null); const [roiAnnotations, setRoiAnnotations] = useState([] as any[]); const [selectedAnnotation, setSelectedAnnotation] = useState(null as any); const { servicesManager, extensionManager } = props; @@ -83,22 +76,18 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) { const { uiDialogService, displaySetService } = servicesManager.services; useEffect(() => { - const viewport = props.viewports[props.activeViewportIndex]; - if (viewport.displaySetInstanceUIDs[0]) { - const displaySet = displaySetService.getDisplaySetByUID( - viewport.displaySetInstanceUIDs[0] - ); + const viewport = props.viewports.get(props.activeViewportId); + if (viewport?.displaySetInstanceUIDs[0]) { + const displaySet = displaySetService.getDisplaySetByUID(viewport.displaySetInstanceUIDs[0]); if (displaySet) { setStudyInstanceUID(displaySet.StudyInstanceUID); } } - }, [props.viewports, props.activeViewportIndex]); + }, [props.viewports, props.activeViewportId]); useEffect(() => { const onAnnotationUpdated = () => { - const roiAnnotations = microscopyService.getAnnotationsForStudy( - studyInstanceUID - ); + const roiAnnotations = microscopyService.getAnnotationsForStudy(studyInstanceUID); setRoiAnnotations(roiAnnotations); }; @@ -111,21 +100,15 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) { onAnnotationUpdated(); }; - const { - unsubscribe: unsubscribeAnnotationUpdated, - } = microscopyService.subscribe( + const { unsubscribe: unsubscribeAnnotationUpdated } = microscopyService.subscribe( MicroscopyEvents.ANNOTATION_UPDATED, onAnnotationUpdated ); - const { - unsubscribe: unsubscribeAnnotationSelected, - } = microscopyService.subscribe( + const { unsubscribe: unsubscribeAnnotationSelected } = microscopyService.subscribe( MicroscopyEvents.ANNOTATION_SELECTED, onAnnotationSelected ); - const { - unsubscribe: unsubscribeAnnotationRemoved, - } = microscopyService.subscribe( + const { unsubscribe: unsubscribeAnnotationRemoved } = microscopyService.subscribe( MicroscopyEvents.ANNOTATION_REMOVED, onAnnotationRemoved ); @@ -147,9 +130,7 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) { * @returns */ const promptSave = () => { - const annotations = microscopyService.getAnnotationsForStudy( - studyInstanceUID - ); + const annotations = microscopyService.getAnnotationsForStudy(studyInstanceUID); if (!annotations || saving) { return; @@ -172,9 +153,7 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) { const getAllDisplaySets = (studyMetadata: any) => { let allDisplaySets = [] as any[]; studyMetadata.series.forEach((series: any) => { - const displaySets = displaySetService.getDisplaySetsForSeries( - series.SeriesInstanceUID - ); + const displaySets = displaySetService.getDisplaySetsForSeries(series.SeriesInstanceUID); allDisplaySets = allDisplaySets.concat(displaySets); }); return allDisplaySets; @@ -189,9 +168,7 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) { const saveFunction = async (SeriesDescription: string) => { const dataSource = extensionManager.getActiveDataSource()[0]; const { onSaveComplete } = props; - const annotations = microscopyService.getAnnotationsForStudy( - studyInstanceUID - ); + const annotations = microscopyService.getAnnotationsForStudy(studyInstanceUID); saving = true; @@ -206,10 +183,7 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) { // Get the next available series number after 4700. const dsWithMetadata = displaySets.filter( - ds => - ds.metadata && - ds.metadata.SeriesNumber && - typeof ds.metadata.SeriesNumber === 'number' + ds => ds.metadata && ds.metadata.SeriesNumber && typeof ds.metadata.SeriesNumber === 'number' ); // Generate next series number @@ -220,11 +194,7 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) { const { instance: metadata } = smDisplaySet; // construct SR dataset - const dataset = constructSR( - metadata, - { SeriesDescription, SeriesNumber }, - annotations - ); + const dataset = constructSR(metadata, { SeriesDescription, SeriesNumber }, annotations); // Save in DICOM format try { @@ -243,7 +213,7 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) { } onSaveComplete({ title: 'SR Saved', - meassage: 'Measurements downloaded successfully', + message: 'Measurements downloaded successfully', type: 'success', }); } else { @@ -265,19 +235,17 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) { */ const onDeleteCurrentSRHandler = async () => { try { - const activeViewport = props.viewports[props.activeViewportIndex]; + const activeViewport = props.viewports[props.activeViewportId]; const { StudyInstanceUID } = activeViewport; // TODO: studies? const study = DicomMetadataStore.getStudy(StudyInstanceUID); - const lastDerivedDisplaySet = study.derivedDisplaySets.sort( - (ds1: any, ds2: any) => { - const dateTime1 = Number(`${ds1.SeriesDate}${ds1.SeriesTime}`); - const dateTime2 = Number(`${ds2.SeriesDate}${ds2.SeriesTime}`); - return dateTime1 > dateTime2; - } - )[study.derivedDisplaySets.length - 1]; + const lastDerivedDisplaySet = study.derivedDisplaySets.sort((ds1: any, ds2: any) => { + const dateTime1 = Number(`${ds1.SeriesDate}${ds1.SeriesTime}`); + const dateTime2 = Number(`${ds2.SeriesDate}${ds2.SeriesTime}`); + return dateTime1 > dateTime2; + })[study.derivedDisplaySets.length - 1]; // TODO: use dataSource.reject.dicom() // await DICOMSR.rejectMeasurements( @@ -307,20 +275,14 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) { const onMeasurementItemClickHandler = ({ uid }: { uid: string }) => { const roiAnnotation = microscopyService.getAnnotation(uid); microscopyService.selectAnnotation(roiAnnotation); - microscopyService.focusAnnotation(roiAnnotation, props.activeViewportIndex); + microscopyService.focusAnnotation(roiAnnotation, props.activeViewportId); }; /** * Handler for "Edit" action of an annotation item * @param param0 */ - const onMeasurementItemEditHandler = ({ - uid, - isActive, - }: { - uid: string; - isActive: boolean; - }) => { + const onMeasurementItemEditHandler = ({ uid, isActive }: { uid: string; isActive: boolean }) => { props.commandsManager.runCommand('setLabel', { uid }, 'MICROSCOPY'); }; @@ -344,10 +306,7 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) { } else if (length !== undefined) { displayText.push( shortAxisLength - ? `${formatLength(length, 'μm')} x ${formatLength( - shortAxisLength, - 'μm' - )}` + ? `${formatLength(length, 'μm')} x ${formatLength(shortAxisLength, 'μm')}` : `${formatLength(length, 'μm')}` ); } @@ -368,7 +327,7 @@ function MicroscopyPanel(props: IMicroscopyPanelProps) { return ( <>
-
- - {/* Let's hide the save button for now, as export SR for SM is a proof of concept */} - {/*{promptSave && ( - - )} */} - {/* */} - -
); } -const connectedMicroscopyPanel = withTranslation(['MicroscopyTable', 'Common'])( - MicroscopyPanel -); +const connectedMicroscopyPanel = withTranslation(['MicroscopyTable', 'Common'])(MicroscopyPanel); export default connectedMicroscopyPanel; diff --git a/extensions/dicom-microscopy/src/components/ViewportOverlay/ViewportOverlay.css b/extensions/dicom-microscopy/src/components/ViewportOverlay/ViewportOverlay.css index da46120c5b2..5e90b203466 100644 --- a/extensions/dicom-microscopy/src/components/ViewportOverlay/ViewportOverlay.css +++ b/extensions/dicom-microscopy/src/components/ViewportOverlay/ViewportOverlay.css @@ -84,4 +84,4 @@ .DicomMicroscopyViewer .top-viewport .flex span:not(.font-light) { flex-shrink: 0; -} \ No newline at end of file +} diff --git a/extensions/dicom-microscopy/src/components/ViewportOverlay/index.tsx b/extensions/dicom-microscopy/src/components/ViewportOverlay/index.tsx index 47d80c8731c..7d04f56d6cb 100644 --- a/extensions/dicom-microscopy/src/components/ViewportOverlay/index.tsx +++ b/extensions/dicom-microscopy/src/components/ViewportOverlay/index.tsx @@ -3,12 +3,7 @@ import classnames from 'classnames'; import listComponentGenerator from './listComponentGenerator'; import './ViewportOverlay.css'; -import { - formatDICOMDate, - formatDICOMTime, - formatNumberPrecision, - formatPN, -} from './utils'; +import { formatDICOMDate, formatDICOMTime, formatNumberPrecision, formatPN } from './utils'; interface OverlayItem { id: string; @@ -41,20 +36,15 @@ export const generateFromConfig = ({ }) => { return (props: any) => { const topLeftClass = 'top-viewport left-viewport text-primary-light'; - const topRightClass = - 'top-viewport right-viewport-scrollbar text-primary-light'; - const bottomRightClass = - 'bottom-viewport right-viewport-scrollbar text-primary-light'; + const topRightClass = 'top-viewport right-viewport-scrollbar text-primary-light'; + const bottomRightClass = 'bottom-viewport right-viewport-scrollbar text-primary-light'; const bottomLeftClass = 'bottom-viewport left-viewport text-primary-light'; const overlay = 'absolute pointer-events-none microscopy-viewport-overlay'; return ( <> {topLeft && topLeft.length > 0 && ( -
+
{listComponentGenerator({ ...props, list: topLeft, itemGenerator })}
)} diff --git a/extensions/dicom-microscopy/src/components/ViewportOverlay/utils.ts b/extensions/dicom-microscopy/src/components/ViewportOverlay/utils.ts index 81f56ad05f1..9dfa63aeff6 100644 --- a/extensions/dicom-microscopy/src/components/ViewportOverlay/utils.ts +++ b/extensions/dicom-microscopy/src/components/ViewportOverlay/utils.ts @@ -78,23 +78,16 @@ export function formatPN(name) { * Gets compression type * * @param {number} imageId - * @returns {string} comrpession type. + * @returns {string} compression type. */ export function getCompression(imageId) { - const generalImageModule = - cornerstone.metaData.get('generalImageModule', imageId) || {}; - const { - lossyImageCompression, - lossyImageCompressionRatio, - lossyImageCompressionMethod, - } = generalImageModule; + const generalImageModule = cornerstone.metaData.get('generalImageModule', imageId) || {}; + const { lossyImageCompression, lossyImageCompressionRatio, lossyImageCompressionMethod } = + generalImageModule; if (lossyImageCompression === '01' && lossyImageCompressionRatio !== '') { const compressionMethod = lossyImageCompressionMethod || 'Lossy: '; - const compressionRatio = formatNumberPrecision( - lossyImageCompressionRatio, - 2 - ); + const compressionRatio = formatNumberPrecision(lossyImageCompressionRatio, 2); return compressionMethod + compressionRatio + ' : 1'; } diff --git a/extensions/dicom-microscopy/src/getCommandsModule.ts b/extensions/dicom-microscopy/src/getCommandsModule.ts index 1a7a421877e..756c4fd5c16 100644 --- a/extensions/dicom-microscopy/src/getCommandsModule.ts +++ b/extensions/dicom-microscopy/src/getCommandsModule.ts @@ -11,11 +11,7 @@ export default function getCommandsModule({ commandsManager: CommandsManager; extensionManager: ExtensionManager; }) { - const { - viewportGridService, - uiDialogService, - microscopyService, - } = servicesManager.services; + const { viewportGridService, uiDialogService, microscopyService } = servicesManager.services; const actions = { // Measurement tool commands: @@ -63,15 +59,9 @@ export default function getCommandsModule({ }, ]; if ( - [ - 'line', - 'box', - 'circle', - 'point', - 'polygon', - 'freehandpolygon', - 'freehandline', - ].indexOf(toolName) >= 0 + ['line', 'box', 'circle', 'point', 'polygon', 'freehandpolygon', 'freehandline'].indexOf( + toolName + ) >= 0 ) { // TODO: read from configuration const options = { @@ -122,24 +112,9 @@ export default function getCommandsModule({ ]); } }, - - incrementActiveViewport: () => { - const { activeViewportIndex, viewports } = viewportGridService.getState(); - const nextViewportIndex = (activeViewportIndex + 1) % viewports.length; - viewportGridService.setActiveViewportIndex(nextViewportIndex); - }, - decrementActiveViewport: () => { - const { activeViewportIndex, viewports } = viewportGridService.getState(); - const nextViewportIndex = - (activeViewportIndex - 1 + viewports.length) % viewports.length; - viewportGridService.setActiveViewportIndex(nextViewportIndex); - }, - toggleOverlays: () => { // overlay - const overlays = document.getElementsByClassName( - 'microscopy-viewport-overlay' - ); + const overlays = document.getElementsByClassName('microscopy-viewport-overlay'); let onoff = false; // true if this will toggle on for (let i = 0; i < overlays.length; i++) { if (i === 0) { @@ -149,8 +124,8 @@ export default function getCommandsModule({ } // overview - const { activeViewportIndex, viewports } = viewportGridService.getState(); - microscopyService.toggleOverviewMap(activeViewportIndex); + const { activeViewportId, viewports } = viewportGridService.getState(); + microscopyService.toggleOverviewMap(activeViewportId); }, toggleAnnotations: () => { microscopyService.toggleROIsVisibility(); @@ -173,14 +148,6 @@ export default function getCommandsModule({ storeContexts: [] as any[], options: {}, }, - incrementActiveViewport: { - commandFn: actions.incrementActiveViewport, - storeContexts: [] as any[], - }, - decrementActiveViewport: { - commandFn: actions.decrementActiveViewport, - storeContexts: [] as any[], - }, toggleOverlays: { commandFn: actions.toggleOverlays, storeContexts: [] as any[], diff --git a/extensions/dicom-microscopy/src/getPanelModule.tsx b/extensions/dicom-microscopy/src/getPanelModule.tsx index ef49c9331ae..58e4f2f09c4 100644 --- a/extensions/dicom-microscopy/src/getPanelModule.tsx +++ b/extensions/dicom-microscopy/src/getPanelModule.tsx @@ -18,15 +18,12 @@ export default function getPanelModule({ extensionManager: ExtensionManager; }) { const wrappedMeasurementPanel = () => { - const [ - { activeViewportIndex, viewports }, - viewportGridService, - ] = useViewportGrid(); + const [{ activeViewportId, viewports }] = useViewportGrid(); return ( {}} onRejectComplete={() => {}} commandsManager={commandsManager} diff --git a/extensions/dicom-microscopy/src/index.tsx b/extensions/dicom-microscopy/src/index.tsx index 66dfeacd98c..486736407c9 100644 --- a/extensions/dicom-microscopy/src/index.tsx +++ b/extensions/dicom-microscopy/src/index.tsx @@ -1,5 +1,5 @@ import { id } from './id'; -import React, { Suspense } from 'react'; +import React, { Suspense, useMemo } from 'react'; import getPanelModule from './getPanelModule'; import getCommandsModule from './getCommandsModule'; @@ -30,15 +30,8 @@ export default { */ id, - async preRegistration({ - servicesManager, - commandsManager, - configuration = {}, - appConfig, - }) { - servicesManager.registerService( - MicroscopyService.REGISTRATION(servicesManager) - ); + async preRegistration({ servicesManager, commandsManager, configuration = {}, appConfig }) { + servicesManager.registerService(MicroscopyService.REGISTRATION(servicesManager)); }, /** @@ -52,7 +45,7 @@ export default { * * @param props {*} * @param props.displaySets - * @param props.viewportIndex + * @param props.viewportId * @param props.viewportLabel * @param props.dataSource * @param props.viewportOptions @@ -63,16 +56,25 @@ export default { const { viewportOptions } = props; const [viewportGrid, viewportGridService] = useViewportGrid(); - const { viewports, activeViewportIndex } = viewportGrid; + const { activeViewportId } = viewportGrid; + + // a unique identifier based on the contents of displaySets. + // since we changed our rendering pipeline and if there is no + // element size change nor viewportId change we won't re-render + // we need a way to force re-rendering when displaySets change. + const displaySetsKey = useMemo(() => { + return props.displaySets.map(ds => ds.displaySetInstanceUID).join('-'); + }, [props.displaySets]); return ( { - viewportGridService.setActiveViewportIndex(viewportIndex); + activeViewportId={activeViewportId} + setViewportActive={(viewportId: string) => { + viewportGridService.setActiveViewportId(viewportId); }} viewportData={viewportOptions} {...props} @@ -94,11 +96,7 @@ export default { * Each sop class handler is defined by a { name, sopClassUids, getDisplaySetsFromSeries}. * Examples include the default sop class handler provided by the default extension */ - getSopClassHandlerModule({ - servicesManager, - commandsManager, - extensionManager, - }) { + getSopClassHandlerModule({ servicesManager, commandsManager, extensionManager }) { return [ getDicomMicroscopySopClassHandler({ servicesManager, diff --git a/extensions/dicom-microscopy/src/services/MicroscopyService.ts b/extensions/dicom-microscopy/src/services/MicroscopyService.ts index a6730511c5a..78ba545c2e9 100644 --- a/extensions/dicom-microscopy/src/services/MicroscopyService.ts +++ b/extensions/dicom-microscopy/src/services/MicroscopyService.ts @@ -1,7 +1,5 @@ import ViewerManager, { EVENTS as ViewerEvents } from '../tools/viewerManager'; -import RoiAnnotation, { - EVENTS as AnnotationEvents, -} from '../utils/RoiAnnotation'; +import RoiAnnotation, { EVENTS as AnnotationEvents } from '../utils/RoiAnnotation'; import styles from '../utils/styles'; import { DicomMetadataStore, PubSubService } from '@ohif/core'; @@ -50,7 +48,7 @@ export default class MicroscopyService extends PubSubService { } /** - * Cleares all the annotations and managed viewers, setting the manager state + * Clears all the annotations and managed viewers, setting the manager state * to its initial state */ clear() { @@ -158,10 +156,7 @@ export default class MicroscopyService extends PubSubService { _onRoiUpdated(data) { const { roiGraphic, managedViewer } = data; this.synchronizeViewers(managedViewer); - this._broadcastEvent( - EVENTS.ANNOTATION_UPDATED, - this.getAnnotation(roiGraphic.uid) - ); + this._broadcastEvent(EVENTS.ANNOTATION_UPDATED, this.getAnnotation(roiGraphic.uid)); } /** @@ -175,10 +170,7 @@ export default class MicroscopyService extends PubSubService { _onRoiSelected(data) { const { roiGraphic } = data; const selectedAnnotation = this.getAnnotation(roiGraphic.uid); - if ( - selectedAnnotation && - selectedAnnotation !== this.getSelectedAnnotation() - ) { + if (selectedAnnotation && selectedAnnotation !== this.getSelectedAnnotation()) { if (this.selectedAnnotation) { this.clearSelection(); } @@ -221,16 +213,11 @@ export default class MicroscopyService extends PubSubService { * @param {ViewerManager} managedViewer The viewer being removed */ _removeManagedViewerSubscriptions(managedViewer) { - managedViewer._roiAddedSubscription && - managedViewer._roiAddedSubscription.unsubscribe(); - managedViewer._roiModifiedSubscription && - managedViewer._roiModifiedSubscription.unsubscribe(); - managedViewer._roiRemovedSubscription && - managedViewer._roiRemovedSubscription.unsubscribe(); - managedViewer._roiUpdatedSubscription && - managedViewer._roiUpdatedSubscription.unsubscribe(); - managedViewer._roiSelectedSubscription && - managedViewer._roiSelectedSubscription.unsubscribe(); + managedViewer._roiAddedSubscription && managedViewer._roiAddedSubscription.unsubscribe(); + managedViewer._roiModifiedSubscription && managedViewer._roiModifiedSubscription.unsubscribe(); + managedViewer._roiRemovedSubscription && managedViewer._roiRemovedSubscription.unsubscribe(); + managedViewer._roiUpdatedSubscription && managedViewer._roiUpdatedSubscription.unsubscribe(); + managedViewer._roiSelectedSubscription && managedViewer._roiSelectedSubscription.unsubscribe(); managedViewer._roiAddedSubscription = null; managedViewer._roiModifiedSubscription = null; @@ -264,8 +251,7 @@ export default class MicroscopyService extends PubSubService { * @returns {Array} The managed viewers for the given series UID */ getManagedViewersForStudy(studyInstanceUID) { - const filter = managedViewer => - managedViewer.studyInstanceUID === studyInstanceUID; + const filter = managedViewer => managedViewer.studyInstanceUID === studyInstanceUID; return Array.from(this.managedViewers).filter(filter); } @@ -276,41 +262,32 @@ export default class MicroscopyService extends PubSubService { */ _restoreAnnotations(managedViewer) { const { studyInstanceUID, seriesInstanceUID } = managedViewer; - const annotations = this.getAnnotationsForSeries( - studyInstanceUID, - seriesInstanceUID - ); + const annotations = this.getAnnotationsForSeries(studyInstanceUID, seriesInstanceUID); annotations.forEach(roiAnnotation => { managedViewer.addRoiGraphic(roiAnnotation.roiGraphic); }); } /** - * Creates a managed viewer instance for the given thrid-party API's viewer. + * Creates a managed viewer instance for the given third-party API's viewer. * Restores existing annotations for the given study/series. * Adds event subscriptions for the viewer being added. * Focuses the selected annotation when the viewer is being loaded into the * active viewport. * - * @param {Object} viewer Third-party viewer API's object to be managed - * @param {Number} viewportIndex The index of the viewport to load the viewer - * @param {HTMLElement} container The DOM element where it will be renderd - * @param {String} studyInstanceUID The study UID of the loaded image - * @param {String} seriesInstanceUID The series UID of the loaded image - * @param {Array} displaySets All displaySets related to the same StudyInstanceUID + * @param viewer - Third-party viewer API's object to be managed + * @param viewportId - The viewport Id where the viewer will be loaded + * @param container - The DOM element where it will be rendered + * @param studyInstanceUID - The study UID of the loaded image + * @param seriesInstanceUID - The series UID of the loaded image + * @param displaySets - All displaySets related to the same StudyInstanceUID * * @returns {ViewerManager} managed viewer */ - addViewer( - viewer, - viewportIndex, - container, - studyInstanceUID, - seriesInstanceUID - ) { + addViewer(viewer, viewportId, container, studyInstanceUID, seriesInstanceUID) { const managedViewer = new ViewerManager( viewer, - viewportIndex, + viewportId, container, studyInstanceUID, seriesInstanceUID @@ -325,7 +302,7 @@ export default class MicroscopyService extends PubSubService { if (this.pendingFocus) { this.pendingFocus = false; - this.focusAnnotation(this.selectedAnnotation, viewportIndex); + this.focusAnnotation(this.selectedAnnotation, viewportId); } return managedViewer; @@ -380,7 +357,7 @@ export default class MicroscopyService extends PubSubService { /** * Removes the given third-party viewer API's object from the managed viewers - * and cleares all its event subscriptions + * and clears all its event subscriptions * * @param {Object} viewer Third-party viewer API's object to be removed */ @@ -502,14 +479,12 @@ export default class MicroscopyService extends PubSubService { /** * Toggles overview map * - * @param viewportIndex The active viewport index + * @param viewportId The active viewport index * @returns {void} */ - toggleOverviewMap(viewportIndex) { + toggleOverviewMap(viewportId) { const managedViewers = Array.from(this.managedViewers); - const managedViewer = managedViewers.find( - mv => mv.viewportIndex === viewportIndex - ); + const managedViewer = managedViewers.find(mv => mv.viewportId === viewportId); if (managedViewer) { managedViewer.toggleOverviewMap(); } @@ -529,9 +504,7 @@ export default class MicroscopyService extends PubSubService { const managedViewers = Array.from(this.managedViewers).filter(filter); - managedViewers.forEach(managedViewer => - managedViewer.removeRoiGraphic(uid) - ); + managedViewers.forEach(managedViewer => managedViewer.removeRoiGraphic(uid)); if (this.annotations[uid]) { this.roiUids.delete(uid); @@ -550,10 +523,10 @@ export default class MicroscopyService extends PubSubService { * the managed viewer instance is created. * * @param {RoiAnnotation} roiAnnotation RoiAnnotation instance to be focused - * @param {Number} viewportIndex Index of the viewport to focus + * @param {string} viewportId Index of the viewport to focus */ - focusAnnotation(roiAnnotation, viewportIndex) { - const filter = mv => mv.viewportIndex === viewportIndex; + focusAnnotation(roiAnnotation, viewportId) { + const filter = mv => mv.viewportId === viewportId; const managedViewer = Array.from(this.managedViewers).find(filter); if (managedViewer) { managedViewer.setViewStateByExtent(roiAnnotation); @@ -570,34 +543,24 @@ export default class MicroscopyService extends PubSubService { */ synchronizeViewers(baseManagedViewer) { const { studyInstanceUID, seriesInstanceUID } = baseManagedViewer; - const managedViewers = this._getManagedViewersForSeries( - studyInstanceUID, - seriesInstanceUID - ); + const managedViewers = this._getManagedViewersForSeries(studyInstanceUID, seriesInstanceUID); // Prevent infinite loops arrising from updates. - managedViewers.forEach(managedViewer => - this._removeManagedViewerSubscriptions(managedViewer) - ); + managedViewers.forEach(managedViewer => this._removeManagedViewerSubscriptions(managedViewer)); managedViewers.forEach(managedViewer => { if (managedViewer === baseManagedViewer) { return; } - const annotations = this.getAnnotationsForSeries( - studyInstanceUID, - seriesInstanceUID - ); + const annotations = this.getAnnotationsForSeries(studyInstanceUID, seriesInstanceUID); managedViewer.clearRoiGraphics(); annotations.forEach(roiAnnotation => { managedViewer.addRoiGraphic(roiAnnotation.roiGraphic); }); }); - managedViewers.forEach(managedViewer => - this._addManagedViewerSubscriptions(managedViewer) - ); + managedViewers.forEach(managedViewer => this._addManagedViewerSubscriptions(managedViewer)); } /** diff --git a/extensions/dicom-microscopy/src/tools/viewerManager.js b/extensions/dicom-microscopy/src/tools/viewerManager.js index bff7318d7e7..b7f3dac8a18 100644 --- a/extensions/dicom-microscopy/src/tools/viewerManager.js +++ b/extensions/dicom-microscopy/src/tools/viewerManager.js @@ -46,16 +46,10 @@ const EVENTS = { * expose only the features/behaviors that are relevant to the application */ class ViewerManager extends PubSubService { - constructor( - viewer, - viewportIndex, - container, - studyInstanceUID, - seriesInstanceUID - ) { + constructor(viewer, viewportId, container, studyInstanceUID, seriesInstanceUID) { super(EVENTS); this.viewer = viewer; - this.viewportIndex = viewportIndex; + this.viewportId = viewportId; this.container = container; this.studyInstanceUID = studyInstanceUID; this.seriesInstanceUID = seriesInstanceUID; @@ -115,22 +109,13 @@ class ViewerManager extends PubSubService { } /** - * Cleares all the relevant event handlers for the third-party API + * Clears all the relevant event handlers for the third-party API */ unregisterEvents() { this.container.removeEventListener(ApiEvents.ROI_ADDED, this.onRoiAdded); - this.container.removeEventListener( - ApiEvents.ROI_MODIFIED, - this.onRoiModified - ); - this.container.removeEventListener( - ApiEvents.ROI_REMOVED, - this.onRoiRemoved - ); - this.container.removeEventListener( - ApiEvents.ROI_SELECTED, - this.onRoiSelected - ); + this.container.removeEventListener(ApiEvents.ROI_MODIFIED, this.onRoiModified); + this.container.removeEventListener(ApiEvents.ROI_REMOVED, this.onRoiRemoved); + this.container.removeEventListener(ApiEvents.ROI_SELECTED, this.onRoiSelected); } /** @@ -220,7 +205,7 @@ class ViewerManager extends PubSubService { * @param {String} label The label of the annotation. */ addRoiGraphicWithLabel(roiGraphic, label) { - // NOTE: Dicom Microscopy Viewer will override styles for "Text" evalutations + // NOTE: Dicom Microscopy Viewer will override styles for "Text" evaluations // to hide all other geometries, we are not going to use its label. // if (label) { // if (!roiGraphic.properties) roiGraphic.properties = {}; @@ -327,26 +312,16 @@ class ViewerManager extends PubSubService { */ activateInteractions(interactions) { const interactionsMap = { - draw: activate => - activate ? 'activateDrawInteraction' : 'deactivateDrawInteraction', - modify: activate => - activate ? 'activateModifyInteraction' : 'deactivateModifyInteraction', + draw: activate => (activate ? 'activateDrawInteraction' : 'deactivateDrawInteraction'), + modify: activate => (activate ? 'activateModifyInteraction' : 'deactivateModifyInteraction'), translate: activate => - activate - ? 'activateTranslateInteraction' - : 'deactivateTranslateInteraction', - snap: activate => - activate ? 'activateSnapInteraction' : 'deactivateSnapInteraction', + activate ? 'activateTranslateInteraction' : 'deactivateTranslateInteraction', + snap: activate => (activate ? 'activateSnapInteraction' : 'deactivateSnapInteraction'), dragPan: activate => - activate - ? 'activateDragPanInteraction' - : 'deactivateDragPanInteraction', + activate ? 'activateDragPanInteraction' : 'deactivateDragPanInteraction', dragZoom: activate => - activate - ? 'activateDragZoomInteraction' - : 'deactivateDragZoomInteraction', - select: activate => - activate ? 'activateSelectInteraction' : 'deactivateSelectInteraction', + activate ? 'activateDragZoomInteraction' : 'deactivateDragZoomInteraction', + select: activate => (activate ? 'activateSelectInteraction' : 'deactivateSelectInteraction'), }; const availableInteractionsName = Object.keys(interactionsMap); @@ -355,9 +330,7 @@ class ViewerManager extends PubSubService { interaction => interaction[0] === availableInteractionName ); if (!interaction) { - const deactivateInteractionMethod = interactionsMap[ - availableInteractionName - ](false); + const deactivateInteractionMethod = interactionsMap[availableInteractionName](false); this.viewer[deactivateInteractionMethod](); } else { const [name, config] = interaction; diff --git a/extensions/dicom-microscopy/src/utils/RoiAnnotation.js b/extensions/dicom-microscopy/src/utils/RoiAnnotation.js index e8ca04021f6..550a791008b 100644 --- a/extensions/dicom-microscopy/src/utils/RoiAnnotation.js +++ b/extensions/dicom-microscopy/src/utils/RoiAnnotation.js @@ -13,13 +13,7 @@ const EVENTS = { * Represents a single annotation for the Microscopy Viewer */ class RoiAnnotation extends PubSubService { - constructor( - roiGraphic, - studyInstanceUID, - seriesInstanceUID, - label = '', - viewState = null - ) { + constructor(roiGraphic, studyInstanceUID, seriesInstanceUID, label = '', viewState = null) { super(EVENTS); this.uid = roiGraphic.uid; this.roiGraphic = roiGraphic; @@ -34,9 +28,7 @@ class RoiAnnotation extends PubSubService { const roiGraphic = this.roiGraphic; const roiGraphicSymbols = Object.getOwnPropertySymbols(roiGraphic); - const _scoord3d = roiGraphicSymbols.find( - s => String(s) === 'Symbol(scoord3d)' - ); + const _scoord3d = roiGraphicSymbols.find(s => String(s) === 'Symbol(scoord3d)'); return roiGraphic[_scoord3d]; } @@ -45,9 +37,7 @@ class RoiAnnotation extends PubSubService { const scoord3d = this.getScoord3d(); const scoord3dSymbols = Object.getOwnPropertySymbols(scoord3d); - const _coordinates = scoord3dSymbols.find( - s => String(s) === 'Symbol(coordinates)' - ); + const _coordinates = scoord3dSymbols.find(s => String(s) === 'Symbol(coordinates)'); const coordinates = scoord3d[_coordinates]; return coordinates; @@ -161,7 +151,7 @@ class RoiAnnotation extends PubSubService { /** * Returns the geometry type of the annotation concatenated with the label * defined for the annotation. - * Difference with getDetailedLabel() is that this will return empty string for empy + * Difference with getDetailedLabel() is that this will return empty string for empty * label. * * @returns {String} Text with geometry type and label diff --git a/extensions/dicom-microscopy/src/utils/areaOfPolygon.js b/extensions/dicom-microscopy/src/utils/areaOfPolygon.js index 91a68c9aecd..ea07684fe6e 100644 --- a/extensions/dicom-microscopy/src/utils/areaOfPolygon.js +++ b/extensions/dicom-microscopy/src/utils/areaOfPolygon.js @@ -5,9 +5,7 @@ export default function areaOfPolygon(coordinates) { let j = n - 1; for (let i = 0; i < n; i++) { - area += - (coordinates[j][0] + coordinates[i][0]) * - (coordinates[j][1] - coordinates[i][1]); + area += (coordinates[j][0] + coordinates[i][0]) * (coordinates[j][1] - coordinates[i][1]); j = i; // j is previous vertex to i } diff --git a/extensions/dicom-microscopy/src/utils/callInputDialog.tsx b/extensions/dicom-microscopy/src/utils/callInputDialog.tsx index 5a746b7bd2d..d36d836570a 100644 --- a/extensions/dicom-microscopy/src/utils/callInputDialog.tsx +++ b/extensions/dicom-microscopy/src/utils/callInputDialog.tsx @@ -53,7 +53,7 @@ export default function callInputDialog({ label="Enter your annotation" labelClassName="text-white text-[14px] leading-[1.2]" autoFocus - className="bg-black border-primary-main" + className="border-primary-main bg-black" type="text" value={value.defaultValue} onChange={event => { diff --git a/extensions/dicom-microscopy/src/utils/cleanDenaturalizedDataset.ts b/extensions/dicom-microscopy/src/utils/cleanDenaturalizedDataset.ts index 43abcaf2258..93acfb1438c 100644 --- a/extensions/dicom-microscopy/src/utils/cleanDenaturalizedDataset.ts +++ b/extensions/dicom-microscopy/src/utils/cleanDenaturalizedDataset.ts @@ -35,9 +35,7 @@ export default function cleanDenaturalizedDataset( } ): any { if (Array.isArray(obj)) { - const newAry = obj.map(o => - isPrimitive(o) ? o : cleanDenaturalizedDataset(o, options) - ); + const newAry = obj.map(o => (isPrimitive(o) ? o : cleanDenaturalizedDataset(o, options))); return newAry; } else if (isPrimitive(obj)) { return obj; @@ -47,31 +45,19 @@ export default function cleanDenaturalizedDataset( delete obj[key].Value; } else if (Array.isArray(obj[key].Value) && obj[key].vr) { if (obj[key].Value.length === 1 && obj[key].Value[0].BulkDataURI) { - dicomWebUtils.fixBulkDataURI( - obj[key].Value[0], - options, - options.dataSourceConfig - ); + dicomWebUtils.fixBulkDataURI(obj[key].Value[0], options, options.dataSourceConfig); obj[key].BulkDataURI = obj[key].Value[0].BulkDataURI; // prevent mixed-content blockage - if ( - window.location.protocol === 'https:' && - obj[key].BulkDataURI.startsWith('http:') - ) { - obj[key].BulkDataURI = obj[key].BulkDataURI.replace( - 'http:', - 'https:' - ); + if (window.location.protocol === 'https:' && obj[key].BulkDataURI.startsWith('http:')) { + obj[key].BulkDataURI = obj[key].BulkDataURI.replace('http:', 'https:'); } delete obj[key].Value; } else if (vrNumerics.includes(obj[key].vr)) { obj[key].Value = obj[key].Value.map(v => +v); } else { - obj[key].Value = obj[key].Value.map(entry => - cleanDenaturalizedDataset(entry, options) - ); + obj[key].Value = obj[key].Value.map(entry => cleanDenaturalizedDataset(entry, options)); } } }); diff --git a/extensions/dicom-microscopy/src/utils/constructSR.ts b/extensions/dicom-microscopy/src/utils/constructSR.ts index 604bc6c6b46..07e50943e68 100644 --- a/extensions/dicom-microscopy/src/utils/constructSR.ts +++ b/extensions/dicom-microscopy/src/utils/constructSR.ts @@ -9,11 +9,7 @@ import DEVICE_OBSERVER_UID from './DEVICE_OBSERVER_UID'; * * @return Comprehensive3DSR dataset */ -export default function constructSR( - metadata, - { SeriesDescription, SeriesNumber }, - annotations -) { +export default function constructSR(metadata, { SeriesDescription, SeriesNumber }, annotations) { // Handle malformed data if (!metadata.SpecimenDescriptionSequence) { metadata.SpecimenDescriptionSequence = { @@ -31,11 +27,9 @@ export default function constructSR( schemeDesignator: 'DCM', meaning: 'Person', }), - observerIdentifyingAttributes: new dcmjs.sr.templates.PersonObserverIdentifyingAttributes( - { - name: '@ohif/extension-dicom-microscopy', - } - ), + observerIdentifyingAttributes: new dcmjs.sr.templates.PersonObserverIdentifyingAttributes({ + name: '@ohif/extension-dicom-microscopy', + }), }), observerDeviceContext: new dcmjs.sr.templates.ObserverContext({ observerType: new dcmjs.sr.coding.CodedConcept({ @@ -43,11 +37,9 @@ export default function constructSR( schemeDesignator: 'DCM', meaning: 'Device', }), - observerIdentifyingAttributes: new dcmjs.sr.templates.DeviceObserverIdentifyingAttributes( - { - uid: DEVICE_OBSERVER_UID, - } - ), + observerIdentifyingAttributes: new dcmjs.sr.templates.DeviceObserverIdentifyingAttributes({ + uid: DEVICE_OBSERVER_UID, + }), }), subjectContext: new dcmjs.sr.templates.SubjectContext({ subjectClass: new dcmjs.sr.coding.CodedConcept({ @@ -55,33 +47,23 @@ export default function constructSR( schemeDesignator: 'DCM', meaning: 'Specimen', }), - subjectClassSpecificContext: new dcmjs.sr.templates.SubjectContextSpecimen( - { - uid: SpecimenDescriptionSequence.SpecimenUID, - identifier: - SpecimenDescriptionSequence.SpecimenIdentifier || - metadata.SeriesInstanceUID, - containerIdentifier: - metadata.ContainerIdentifier || metadata.SeriesInstanceUID, - } - ), + subjectClassSpecificContext: new dcmjs.sr.templates.SubjectContextSpecimen({ + uid: SpecimenDescriptionSequence.SpecimenUID, + identifier: SpecimenDescriptionSequence.SpecimenIdentifier || metadata.SeriesInstanceUID, + containerIdentifier: metadata.ContainerIdentifier || metadata.SeriesInstanceUID, + }), }), }); const imagingMeasurements = []; for (let i = 0; i < annotations.length; i++) { const { roiGraphic: roi, label } = annotations[i]; - let { - measurements, - evaluations, - marker, - presentationState, - } = roi.properties; + let { measurements, evaluations, marker, presentationState } = roi.properties; - console.debug('[SR] storing marker...', marker); - console.debug('[SR] storing measurements...', measurements); - console.debug('[SR] storing evaluations...', evaluations); - console.debug('[SR] storing presentation state...', presentationState); + console.log('[SR] storing marker...', marker); + console.log('[SR] storing measurements...', measurements); + console.log('[SR] storing evaluations...', evaluations); + console.log('[SR] storing presentation state...', presentationState); if (presentationState) { presentationState.marker = marker; @@ -97,9 +79,7 @@ export default function constructSR( ? measurement.MeasuredValueSequence[0] : measurement.MeasuredValueSequence; - const MeasuredValueUnits = Array.isArray( - MeasuredValue.MeasurementUnitsCodeSequence - ) + const MeasuredValueUnits = Array.isArray(MeasuredValue.MeasurementUnitsCodeSequence) ? MeasuredValue.MeasurementUnitsCodeSequence[0] : MeasuredValue.MeasurementUnitsCodeSequence; @@ -136,29 +116,27 @@ export default function constructSR( }); const identifier = `ROI #${i + 1}`; - const group = new dcmjs.sr.templates.PlanarROIMeasurementsAndQualitativeEvaluations( - { - trackingIdentifier: new dcmjs.sr.templates.TrackingIdentifier({ - uid: roi.uid, - identifier: presentationState - ? identifier.concat(`(${JSON.stringify(presentationState)})`) - : identifier, - }), - referencedRegion: new dcmjs.sr.contentItems.ImageRegion3D({ - graphicType: roi.scoord3d.graphicType, - graphicData: roi.scoord3d.graphicData, - frameOfReferenceUID: roi.scoord3d.frameOfReferenceUID, - }), - findingType: new dcmjs.sr.coding.CodedConcept({ - value: label, - schemeDesignator: '@ohif/extension-dicom-microscopy', - meaning: 'FREETEXT', - }), - /** Evaluations will conflict with current tracking identifier */ - /** qualitativeEvaluations: evaluations, */ - measurements, - } - ); + const group = new dcmjs.sr.templates.PlanarROIMeasurementsAndQualitativeEvaluations({ + trackingIdentifier: new dcmjs.sr.templates.TrackingIdentifier({ + uid: roi.uid, + identifier: presentationState + ? identifier.concat(`(${JSON.stringify(presentationState)})`) + : identifier, + }), + referencedRegion: new dcmjs.sr.contentItems.ImageRegion3D({ + graphicType: roi.scoord3d.graphicType, + graphicData: roi.scoord3d.graphicData, + frameOfReferenceUID: roi.scoord3d.frameOfReferenceUID, + }), + findingType: new dcmjs.sr.coding.CodedConcept({ + value: label, + schemeDesignator: '@ohif/extension-dicom-microscopy', + meaning: 'FREETEXT', + }), + /** Evaluations will conflict with current tracking identifier */ + /** qualitativeEvaluations: evaluations, */ + measurements, + }); imagingMeasurements.push(...group); } @@ -180,8 +158,7 @@ export default function constructSR( evidence: [metadata], seriesInstanceUID: dcmjs.data.DicomMetaDictionary.uid(), seriesNumber: SeriesNumber, - seriesDescription: - SeriesDescription || 'Whole slide imaging structured report', + seriesDescription: SeriesDescription || 'Whole slide imaging structured report', sopInstanceUID: dcmjs.data.DicomMetaDictionary.uid(), instanceNumber: 1, manufacturer: 'dcmjs-org', diff --git a/extensions/dicom-microscopy/src/utils/coordinateFormatScoord3d2Geometry.js b/extensions/dicom-microscopy/src/utils/coordinateFormatScoord3d2Geometry.js index de5c4771d18..e4530b94cf9 100644 --- a/extensions/dicom-microscopy/src/utils/coordinateFormatScoord3d2Geometry.js +++ b/extensions/dicom-microscopy/src/utils/coordinateFormatScoord3d2Geometry.js @@ -3,10 +3,7 @@ import { inv, multiply } from 'mathjs'; // TODO -> This is pulled out of some internal logic from Dicom Microscopy Viewer, // We should likely just expose this there. -export default function coordinateFormatScoord3d2Geometry( - coordinates, - pyramid -) { +export default function coordinateFormatScoord3d2Geometry(coordinates, pyramid) { let transform = false; if (!Array.isArray(coordinates[0])) { coordinates = [coordinates]; diff --git a/extensions/dicom-microscopy/src/utils/dicomWebClient.ts b/extensions/dicom-microscopy/src/utils/dicomWebClient.ts index bb8b1f2e73b..8d6d784628e 100644 --- a/extensions/dicom-microscopy/src/utils/dicomWebClient.ts +++ b/extensions/dicom-microscopy/src/utils/dicomWebClient.ts @@ -15,10 +15,7 @@ DICOMwebClient._buildMultipartAcceptHeaderFieldValue = () => { * @param param0 * @returns */ -export default function getDicomWebClient({ - extensionManager, - servicesManager, -}) { +export default function getDicomWebClient({ extensionManager, servicesManager }) { const dataSourceConfig = window.config.dataSources.find( ds => ds.sourceName === extensionManager.activeDataSource ); @@ -55,29 +52,19 @@ export default function getDicomWebClient({ // client.retrieveInstanceFrames = async options => { if (!('studyInstanceUID' in options)) { - throw new Error( - 'Study Instance UID is required for retrieval of instance frames' - ); + throw new Error('Study Instance UID is required for retrieval of instance frames'); } if (!('seriesInstanceUID' in options)) { - throw new Error( - 'Series Instance UID is required for retrieval of instance frames' - ); + throw new Error('Series Instance UID is required for retrieval of instance frames'); } if (!('sopInstanceUID' in options)) { - throw new Error( - 'SOP Instance UID is required for retrieval of instance frames' - ); + throw new Error('SOP Instance UID is required for retrieval of instance frames'); } if (!('frameNumbers' in options)) { - throw new Error( - 'frame numbers are required for retrieval of instance frames' - ); + throw new Error('frame numbers are required for retrieval of instance frames'); } console.log( - `retrieve frames ${options.frameNumbers.toString()} of instance ${ - options.sopInstanceUID - }` + `retrieve frames ${options.frameNumbers.toString()} of instance ${options.sopInstanceUID}` ); const instance = DicomMetadataStore.getInstance( @@ -91,9 +78,7 @@ export default function getDicomWebClient({ : options.frameNumbers.split(','); return frameNumbers.map(fr => - Array.isArray(instance.PixelData) - ? instance.PixelData[+fr - 1] - : instance.PixelData + Array.isArray(instance.PixelData) ? instance.PixelData[+fr - 1] : instance.PixelData ); }; } diff --git a/extensions/dicom-microscopy/src/utils/getSourceDisplaySet.js b/extensions/dicom-microscopy/src/utils/getSourceDisplaySet.js index d1455b639f7..a2c7439e0bf 100644 --- a/extensions/dicom-microscopy/src/utils/getSourceDisplaySet.js +++ b/extensions/dicom-microscopy/src/utils/getSourceDisplaySet.js @@ -5,24 +5,18 @@ * @param {*} microscopySRDisplaySet * @returns */ -export default function getSourceDisplaySet( - allDisplaySets, - microscopySRDisplaySet -) { +export default function getSourceDisplaySet(allDisplaySets, microscopySRDisplaySet) { const { ReferencedFrameOfReferenceUID } = microscopySRDisplaySet; const otherDisplaySets = allDisplaySets.filter( - ds => - ds.displaySetInstanceUID !== microscopySRDisplaySet.displaySetInstanceUID + ds => ds.displaySetInstanceUID !== microscopySRDisplaySet.displaySetInstanceUID ); const referencedDisplaySet = otherDisplaySets.find( displaySet => displaySet.Modality === 'SM' && (displaySet.FrameOfReferenceUID === ReferencedFrameOfReferenceUID || // sometimes each depth instance has the different FrameOfReferenceID - displaySet.othersFrameOfReferenceUID.includes( - ReferencedFrameOfReferenceUID - )) + displaySet.othersFrameOfReferenceUID.includes(ReferencedFrameOfReferenceUID)) ); if (!referencedDisplaySet && otherDisplaySets.length >= 1) { diff --git a/extensions/dicom-microscopy/src/utils/loadSR.js b/extensions/dicom-microscopy/src/utils/loadSR.js index c690028bb98..c300b6f5e88 100644 --- a/extensions/dicom-microscopy/src/utils/loadSR.js +++ b/extensions/dicom-microscopy/src/utils/loadSR.js @@ -3,8 +3,7 @@ import dcmjs from 'dcmjs'; import DCM_CODE_VALUES from './dcmCodeValues'; import toArray from './toArray'; -const MeasurementReport = - dcmjs.adapters.DICOMMicroscopyViewer.MeasurementReport; +const MeasurementReport = dcmjs.adapters.DICOMMicroscopyViewer.MeasurementReport; // Define as async so that it returns a promise, expected by the ViewportGrid export default async function loadSR( @@ -16,9 +15,7 @@ export default async function loadSR( const { StudyInstanceUID, FrameOfReferenceUID } = referencedDisplaySet; - const managedViewers = microscopyService.getManagedViewersForStudy( - StudyInstanceUID - ); + const managedViewers = microscopyService.getManagedViewersForStudy(StudyInstanceUID); if (!managedViewers || !managedViewers.length) { return; @@ -26,10 +23,7 @@ export default async function loadSR( microscopySRDisplaySet.isLoaded = true; - const { rois, labels } = await _getROIsFromToolState( - naturalizedDataset, - FrameOfReferenceUID - ); + const { rois, labels } = await _getROIsFromToolState(naturalizedDataset, FrameOfReferenceUID); const managedViewer = managedViewers[0]; @@ -37,7 +31,7 @@ export default async function loadSR( // NOTE: When saving Microscopy SR, we are attaching identifier property // to each ROI, and when read for display, it is coming in as "TEXT" // evaluation. - // As the Dicom Microscopy Viewer will override styles for "Text" evalutations + // As the Dicom Microscopy Viewer will override styles for "Text" evaluations // to hide all other geometries, we are going to manually remove that // evaluation item. const roi = rois[i]; @@ -58,9 +52,7 @@ async function _getROIsFromToolState(naturalizedDataset, FrameOfReferenceUID) { /* webpackChunkName: "dicom-microscopy-viewer" */ 'dicom-microscopy-viewer' ); - const measurementGroupContentItems = _getMeasurementGroups( - naturalizedDataset - ); + const measurementGroupContentItems = _getMeasurementGroups(naturalizedDataset); const rois = []; const labels = []; @@ -71,17 +63,13 @@ async function _getROIsFromToolState(naturalizedDataset, FrameOfReferenceUID) { const capsToolType = t.toUpperCase(); - const measurementGroupContentItemsForTool = measurementGroupContentItems.filter( - mg => { - const imageRegionContentItem = toArray(mg.ContentSequence).find( - ci => - ci.ConceptNameCodeSequence.CodeValue === - DCM_CODE_VALUES.IMAGE_REGION - ); + const measurementGroupContentItemsForTool = measurementGroupContentItems.filter(mg => { + const imageRegionContentItem = toArray(mg.ContentSequence).find( + ci => ci.ConceptNameCodeSequence.CodeValue === DCM_CODE_VALUES.IMAGE_REGION + ); - return imageRegionContentItem.GraphicType === capsToolType; - } - ); + return imageRegionContentItem.GraphicType === capsToolType; + }); toolSpecificToolState.forEach((coordinates, index) => { const properties = {}; @@ -109,9 +97,7 @@ async function _getROIsFromToolState(naturalizedDataset, FrameOfReferenceUID) { ); const trackingGroup = toArray(measurementGroup.ContentSequence).find( - ci => - ci.ConceptNameCodeSequence.CodeValue === - DCM_CODE_VALUES.TRACKING_UNIQUE_IDENTIFIER + ci => ci.ConceptNameCodeSequence.CodeValue === DCM_CODE_VALUES.TRACKING_UNIQUE_IDENTIFIER ); /** @@ -138,9 +124,7 @@ async function _getROIsFromToolState(naturalizedDataset, FrameOfReferenceUID) { ); let evaluations = toArray(measurementGroup.ContentSequence).filter(ci => - [DCM_CODE_VALUES.TRACKING_UNIQUE_IDENTIFIER].includes( - ci.ConceptNameCodeSequence.CodeValue - ) + [DCM_CODE_VALUES.TRACKING_UNIQUE_IDENTIFIER].includes(ci.ConceptNameCodeSequence.CodeValue) ); /** @@ -165,12 +149,12 @@ async function _getROIsFromToolState(naturalizedDataset, FrameOfReferenceUID) { if (measurements && measurements.length) { properties.measurements = measurements; - console.debug('[SR] retrieving measurements...', measurements); + console.log('[SR] retrieving measurements...', measurements); } if (evaluations && evaluations.length) { properties.evaluations = evaluations; - console.debug('[SR] retrieving evaluations...', evaluations); + console.log('[SR] retrieving evaluations...', evaluations); } const roi = new DICOMMicroscopyViewer.roi.ROI({ scoord3d, properties }); @@ -191,17 +175,12 @@ function _getMeasurementGroups(naturalizedDataset) { const { ContentSequence } = naturalizedDataset; const imagingMeasurementsContentItem = ContentSequence.find( - ci => - ci.ConceptNameCodeSequence.CodeValue === - DCM_CODE_VALUES.IMAGING_MEASUREMENTS + ci => ci.ConceptNameCodeSequence.CodeValue === DCM_CODE_VALUES.IMAGING_MEASUREMENTS ); const measurementGroupContentItems = toArray( imagingMeasurementsContentItem.ContentSequence - ).filter( - ci => - ci.ConceptNameCodeSequence.CodeValue === DCM_CODE_VALUES.MEASUREMENT_GROUP - ); + ).filter(ci => ci.ConceptNameCodeSequence.CodeValue === DCM_CODE_VALUES.MEASUREMENT_GROUP); return measurementGroupContentItems; } diff --git a/extensions/dicom-pdf/.webpack/webpack.prod.js b/extensions/dicom-pdf/.webpack/webpack.prod.js index a653d7b9fc3..eb47972e1a8 100644 --- a/extensions/dicom-pdf/.webpack/webpack.prod.js +++ b/extensions/dicom-pdf/.webpack/webpack.prod.js @@ -3,8 +3,7 @@ const { merge } = require('webpack-merge'); const path = require('path'); const webpackCommon = require('./../../../.webpack/webpack.base.js'); const pkg = require('./../package.json'); -const BundleAnalyzerPlugin = require('webpack-bundle-analyzer') - .BundleAnalyzerPlugin; +const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; const ROOT_DIR = path.join(__dirname, './..'); const SRC_DIR = path.join(__dirname, '../src'); @@ -38,13 +37,7 @@ module.exports = (env, argv) => { libraryTarget: 'umd', filename: `${pkg.main}`, }, - externals: [ - /\b(vtk.js)/, - /\b(dcmjs)/, - /\b(gl-matrix)/, - /^@ohif/, - /^@cornerstonejs/, - ], + externals: [/\b(vtk.js)/, /\b(dcmjs)/, /\b(gl-matrix)/, /^@ohif/, /^@cornerstonejs/], plugins: [ new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1, diff --git a/extensions/dicom-pdf/CHANGELOG.md b/extensions/dicom-pdf/CHANGELOG.md new file mode 100644 index 00000000000..fc99f3749a4 --- /dev/null +++ b/extensions/dicom-pdf/CHANGELOG.md @@ -0,0 +1,926 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [3.8.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.59...v3.8.0-beta.60) (2024-03-15) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.58...v3.8.0-beta.59) (2024-03-08) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.57...v3.8.0-beta.58) (2024-03-05) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.56...v3.8.0-beta.57) (2024-02-28) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.55...v3.8.0-beta.56) (2024-02-22) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.54...v3.8.0-beta.55) (2024-02-21) + + +### Features + +* **resize:** Optimize resizing process and maintain zoom level ([#3889](https://github.com/OHIF/Viewers/issues/3889)) ([b3a0faf](https://github.com/OHIF/Viewers/commit/b3a0faf5f5f0a1993b2b017eb4cc1216164ea2c6)) + + + + + +# [3.8.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.53...v3.8.0-beta.54) (2024-02-14) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.52...v3.8.0-beta.53) (2024-02-05) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.51...v3.8.0-beta.52) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.50...v3.8.0-beta.51) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.49...v3.8.0-beta.50) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.48...v3.8.0-beta.49) (2024-01-19) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.47...v3.8.0-beta.48) (2024-01-17) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.46...v3.8.0-beta.47) (2024-01-12) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.45...v3.8.0-beta.46) (2024-01-12) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.44...v3.8.0-beta.45) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.43...v3.8.0-beta.44) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.42...v3.8.0-beta.43) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.41...v3.8.0-beta.42) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.40...v3.8.0-beta.41) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.39...v3.8.0-beta.40) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.38...v3.8.0-beta.39) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.37...v3.8.0-beta.38) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.36...v3.8.0-beta.37) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.35...v3.8.0-beta.36) (2023-12-15) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.34...v3.8.0-beta.35) (2023-12-14) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.33...v3.8.0-beta.34) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.32...v3.8.0-beta.33) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.31...v3.8.0-beta.32) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.30...v3.8.0-beta.31) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.29...v3.8.0-beta.30) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.28...v3.8.0-beta.29) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.27...v3.8.0-beta.28) (2023-12-08) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.26...v3.8.0-beta.27) (2023-12-06) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.25...v3.8.0-beta.26) (2023-11-28) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.24...v3.8.0-beta.25) (2023-11-27) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.23...v3.8.0-beta.24) (2023-11-24) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.22...v3.8.0-beta.23) (2023-11-24) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.21...v3.8.0-beta.22) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.20...v3.8.0-beta.21) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.19...v3.8.0-beta.20) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.18...v3.8.0-beta.19) (2023-11-18) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.17...v3.8.0-beta.18) (2023-11-15) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.16...v3.8.0-beta.17) (2023-11-13) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.15...v3.8.0-beta.16) (2023-11-13) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.14...v3.8.0-beta.15) (2023-11-10) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.14](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.13...v3.8.0-beta.14) (2023-11-10) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.13](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.12...v3.8.0-beta.13) (2023-11-09) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.12](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.11...v3.8.0-beta.12) (2023-11-08) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.11](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.10...v3.8.0-beta.11) (2023-11-08) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.10](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.9...v3.8.0-beta.10) (2023-11-03) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.9](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.8...v3.8.0-beta.9) (2023-11-02) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.8](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.7...v3.8.0-beta.8) (2023-10-31) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.7](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.6...v3.8.0-beta.7) (2023-10-30) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.6](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.5...v3.8.0-beta.6) (2023-10-25) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.5](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.4...v3.8.0-beta.5) (2023-10-24) + + +### Bug Fixes + +* **sr:** dcm4chee requires the patient name for an SR to match what is in the original study ([#3739](https://github.com/OHIF/Viewers/issues/3739)) ([d98439f](https://github.com/OHIF/Viewers/commit/d98439fe7f3825076dbc87b664a1d1480ff414d3)) + + + + + +# [3.8.0-beta.4](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.3...v3.8.0-beta.4) (2023-10-23) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.3](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.2...v3.8.0-beta.3) (2023-10-23) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.1...v3.8.0-beta.2) (2023-10-19) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.0...v3.8.0-beta.1) (2023-10-19) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.8.0-beta.0](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.110...v3.8.0-beta.0) (2023-10-12) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.110](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.109...v3.7.0-beta.110) (2023-10-11) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.109](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.108...v3.7.0-beta.109) (2023-10-11) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.107...v3.7.0-beta.108) (2023-10-10) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.106...v3.7.0-beta.107) (2023-10-10) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.105...v3.7.0-beta.106) (2023-10-10) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.104...v3.7.0-beta.105) (2023-10-10) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.103...v3.7.0-beta.104) (2023-10-09) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.102...v3.7.0-beta.103) (2023-10-09) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.101...v3.7.0-beta.102) (2023-10-06) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.100...v3.7.0-beta.101) (2023-10-06) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.99...v3.7.0-beta.100) (2023-10-06) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.98...v3.7.0-beta.99) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.97...v3.7.0-beta.98) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.96...v3.7.0-beta.97) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.95...v3.7.0-beta.96) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.94...v3.7.0-beta.95) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.93...v3.7.0-beta.94) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.92...v3.7.0-beta.93) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.91...v3.7.0-beta.92) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.90...v3.7.0-beta.91) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.89...v3.7.0-beta.90) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.88...v3.7.0-beta.89) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.87...v3.7.0-beta.88) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.86...v3.7.0-beta.87) (2023-09-29) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.85...v3.7.0-beta.86) (2023-09-29) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf + + + + + +# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23) + +**Note:** Version bump only for package @ohif/extension-dicom-pdf diff --git a/extensions/dicom-pdf/package.json b/extensions/dicom-pdf/package.json index f6a89f57f36..463ed8cc7a8 100644 --- a/extensions/dicom-pdf/package.json +++ b/extensions/dicom-pdf/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-dicom-pdf", - "version": "3.7.0-beta.46", + "version": "3.8.0-beta.60", "description": "OHIF extension for PDF display", "author": "OHIF", "license": "MIT", @@ -20,6 +20,8 @@ "access": "public" }, "scripts": { + "clean": "shx rm -rf dist", + "clean:deep": "yarn run clean && shx rm -rf node_modules", "dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --output-pathinfo", "build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js", "build:package-1": "yarn run build", @@ -28,9 +30,9 @@ "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" }, "peerDependencies": { - "@ohif/core": "3.7.0-beta.46", - "@ohif/ui": "3.7.0-beta.46", - "dcmjs": "^0.29.5", + "@ohif/core": "3.8.0-beta.60", + "@ohif/ui": "3.8.0-beta.60", + "dcmjs": "^0.29.12", "dicom-parser": "^1.8.9", "hammerjs": "^2.0.8", "prop-types": "^15.6.2", diff --git a/extensions/dicom-pdf/src/getSopClassHandlerModule.js b/extensions/dicom-pdf/src/getSopClassHandlerModule.js index 99a9bae805c..8e7e9025256 100644 --- a/extensions/dicom-pdf/src/getSopClassHandlerModule.js +++ b/extensions/dicom-pdf/src/getSopClassHandlerModule.js @@ -9,25 +9,12 @@ const SOP_CLASS_UIDS = { const sopClassUids = Object.values(SOP_CLASS_UIDS); -const _getDisplaySetsFromSeries = ( - instances, - servicesManager, - extensionManager -) => { +const _getDisplaySetsFromSeries = (instances, servicesManager, extensionManager) => { const dataSource = extensionManager.getActiveDataSource()[0]; return instances.map(instance => { const { Modality, SOPInstanceUID, EncapsulatedDocument } = instance; - const { - SeriesDescription = 'PDF', - MIMETypeOfEncapsulatedDocument, - } = instance; - const { - SeriesNumber, - SeriesDate, - SeriesInstanceUID, - StudyInstanceUID, - SOPClassUID, - } = instance; + const { SeriesDescription = 'PDF', MIMETypeOfEncapsulatedDocument } = instance; + const { SeriesNumber, SeriesDate, SeriesInstanceUID, StudyInstanceUID, SOPClassUID } = instance; const pdfUrl = dataSource.retrieve.directURL({ instance, tag: 'EncapsulatedDocument', @@ -68,16 +55,9 @@ const _getDisplaySetsFromSeries = ( }); }; -export default function getSopClassHandlerModule({ - servicesManager, - extensionManager, -}) { +export default function getSopClassHandlerModule({ servicesManager, extensionManager }) { const getDisplaySetsFromSeries = instances => { - return _getDisplaySetsFromSeries( - instances, - servicesManager, - extensionManager - ); + return _getDisplaySetsFromSeries(instances, servicesManager, extensionManager); }; return [ diff --git a/extensions/dicom-pdf/src/index.tsx b/extensions/dicom-pdf/src/index.tsx index 6c35c4a5fcf..ab3d33cf4ba 100644 --- a/extensions/dicom-pdf/src/index.tsx +++ b/extensions/dicom-pdf/src/index.tsx @@ -3,9 +3,7 @@ import getSopClassHandlerModule from './getSopClassHandlerModule'; import { id } from './id.js'; const Component = React.lazy(() => { - return import( - /* webpackPrefetch: true */ './viewports/OHIFCornerstonePdfViewport' - ); + return import(/* webpackPrefetch: true */ './viewports/OHIFCornerstonePdfViewport'); }); const OHIFCornerstonePdfViewport = props => { @@ -41,9 +39,7 @@ const dicomPDFExtension = { ); }; - return [ - { name: 'dicom-pdf', component: ExtendedOHIFCornerstonePdfViewport }, - ]; + return [{ name: 'dicom-pdf', component: ExtendedOHIFCornerstonePdfViewport }]; }, // getCommandsModule({ servicesManager }) { // return { diff --git a/extensions/dicom-pdf/src/viewports/OHIFCornerstonePdfViewport.tsx b/extensions/dicom-pdf/src/viewports/OHIFCornerstonePdfViewport.tsx index 9da1904c1a9..e4e2bac4bba 100644 --- a/extensions/dicom-pdf/src/viewports/OHIFCornerstonePdfViewport.tsx +++ b/extensions/dicom-pdf/src/viewports/OHIFCornerstonePdfViewport.tsx @@ -21,8 +21,12 @@ function OHIFCornerstonePdfViewport({ displaySets }) { }, [pdfUrl]); return ( -
- +
+
No online PDF viewer installed
diff --git a/extensions/dicom-video/.webpack/webpack.prod.js b/extensions/dicom-video/.webpack/webpack.prod.js index 633be1c44c3..79c56911f6f 100644 --- a/extensions/dicom-video/.webpack/webpack.prod.js +++ b/extensions/dicom-video/.webpack/webpack.prod.js @@ -3,8 +3,7 @@ const { merge } = require('webpack-merge'); const path = require('path'); const webpackCommon = require('./../../../.webpack/webpack.base.js'); const pkg = require('./../package.json'); -const BundleAnalyzerPlugin = require('webpack-bundle-analyzer') - .BundleAnalyzerPlugin; +const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; const ROOT_DIR = path.join(__dirname, './..'); const SRC_DIR = path.join(__dirname, '../src'); @@ -38,13 +37,7 @@ module.exports = (env, argv) => { libraryTarget: 'umd', filename: pkg.main, }, - externals: [ - /\b(vtk.js)/, - /\b(dcmjs)/, - /\b(gl-matrix)/, - /^@ohif/, - /^@cornerstonejs/, - ], + externals: [/\b(vtk.js)/, /\b(dcmjs)/, /\b(gl-matrix)/, /^@ohif/, /^@cornerstonejs/], plugins: [ new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1, diff --git a/extensions/dicom-video/CHANGELOG.md b/extensions/dicom-video/CHANGELOG.md new file mode 100644 index 00000000000..460161139c0 --- /dev/null +++ b/extensions/dicom-video/CHANGELOG.md @@ -0,0 +1,926 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [3.8.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.59...v3.8.0-beta.60) (2024-03-15) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.58...v3.8.0-beta.59) (2024-03-08) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.57...v3.8.0-beta.58) (2024-03-05) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.56...v3.8.0-beta.57) (2024-02-28) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.56](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.55...v3.8.0-beta.56) (2024-02-22) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.55](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.54...v3.8.0-beta.55) (2024-02-21) + + +### Features + +* **resize:** Optimize resizing process and maintain zoom level ([#3889](https://github.com/OHIF/Viewers/issues/3889)) ([b3a0faf](https://github.com/OHIF/Viewers/commit/b3a0faf5f5f0a1993b2b017eb4cc1216164ea2c6)) + + + + + +# [3.8.0-beta.54](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.53...v3.8.0-beta.54) (2024-02-14) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.53](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.52...v3.8.0-beta.53) (2024-02-05) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.52](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.51...v3.8.0-beta.52) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.51](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.50...v3.8.0-beta.51) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.50](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.49...v3.8.0-beta.50) (2024-01-22) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.49](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.48...v3.8.0-beta.49) (2024-01-19) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.48](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.47...v3.8.0-beta.48) (2024-01-17) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.47](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.46...v3.8.0-beta.47) (2024-01-12) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.46](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.45...v3.8.0-beta.46) (2024-01-12) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.45](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.44...v3.8.0-beta.45) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.44](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.43...v3.8.0-beta.44) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.43](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.42...v3.8.0-beta.43) (2024-01-09) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.42](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.41...v3.8.0-beta.42) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.41](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.40...v3.8.0-beta.41) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.40](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.39...v3.8.0-beta.40) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.39](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.38...v3.8.0-beta.39) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.38](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.37...v3.8.0-beta.38) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.37](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.36...v3.8.0-beta.37) (2024-01-08) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.36](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.35...v3.8.0-beta.36) (2023-12-15) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.35](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.34...v3.8.0-beta.35) (2023-12-14) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.34](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.33...v3.8.0-beta.34) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.33](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.32...v3.8.0-beta.33) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.32](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.31...v3.8.0-beta.32) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.31](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.30...v3.8.0-beta.31) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.30](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.29...v3.8.0-beta.30) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.29](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.28...v3.8.0-beta.29) (2023-12-13) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.28](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.27...v3.8.0-beta.28) (2023-12-08) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.27](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.26...v3.8.0-beta.27) (2023-12-06) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.26](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.25...v3.8.0-beta.26) (2023-11-28) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.25](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.24...v3.8.0-beta.25) (2023-11-27) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.24](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.23...v3.8.0-beta.24) (2023-11-24) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.23](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.22...v3.8.0-beta.23) (2023-11-24) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.22](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.21...v3.8.0-beta.22) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.21](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.20...v3.8.0-beta.21) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.20](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.19...v3.8.0-beta.20) (2023-11-21) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.19](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.18...v3.8.0-beta.19) (2023-11-18) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.18](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.17...v3.8.0-beta.18) (2023-11-15) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.17](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.16...v3.8.0-beta.17) (2023-11-13) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.16](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.15...v3.8.0-beta.16) (2023-11-13) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.15](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.14...v3.8.0-beta.15) (2023-11-10) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.14](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.13...v3.8.0-beta.14) (2023-11-10) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.13](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.12...v3.8.0-beta.13) (2023-11-09) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.12](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.11...v3.8.0-beta.12) (2023-11-08) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.11](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.10...v3.8.0-beta.11) (2023-11-08) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.10](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.9...v3.8.0-beta.10) (2023-11-03) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.9](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.8...v3.8.0-beta.9) (2023-11-02) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.8](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.7...v3.8.0-beta.8) (2023-10-31) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.7](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.6...v3.8.0-beta.7) (2023-10-30) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.6](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.5...v3.8.0-beta.6) (2023-10-25) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.5](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.4...v3.8.0-beta.5) (2023-10-24) + + +### Bug Fixes + +* **sr:** dcm4chee requires the patient name for an SR to match what is in the original study ([#3739](https://github.com/OHIF/Viewers/issues/3739)) ([d98439f](https://github.com/OHIF/Viewers/commit/d98439fe7f3825076dbc87b664a1d1480ff414d3)) + + + + + +# [3.8.0-beta.4](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.3...v3.8.0-beta.4) (2023-10-23) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.3](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.2...v3.8.0-beta.3) (2023-10-23) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.2](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.1...v3.8.0-beta.2) (2023-10-19) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.1](https://github.com/OHIF/Viewers/compare/v3.8.0-beta.0...v3.8.0-beta.1) (2023-10-19) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.8.0-beta.0](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.110...v3.8.0-beta.0) (2023-10-12) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.110](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.109...v3.7.0-beta.110) (2023-10-11) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.109](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.108...v3.7.0-beta.109) (2023-10-11) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.108](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.107...v3.7.0-beta.108) (2023-10-10) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.107](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.106...v3.7.0-beta.107) (2023-10-10) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.106](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.105...v3.7.0-beta.106) (2023-10-10) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.105](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.104...v3.7.0-beta.105) (2023-10-10) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.104](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.103...v3.7.0-beta.104) (2023-10-09) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.103](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.102...v3.7.0-beta.103) (2023-10-09) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.102](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.101...v3.7.0-beta.102) (2023-10-06) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.101](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.100...v3.7.0-beta.101) (2023-10-06) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.100](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.99...v3.7.0-beta.100) (2023-10-06) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.99](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.98...v3.7.0-beta.99) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.98](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.97...v3.7.0-beta.98) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.97](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.96...v3.7.0-beta.97) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.96](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.95...v3.7.0-beta.96) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.95](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.94...v3.7.0-beta.95) (2023-10-04) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.94](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.93...v3.7.0-beta.94) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.93](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.92...v3.7.0-beta.93) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.92](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.91...v3.7.0-beta.92) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.91](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.90...v3.7.0-beta.91) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.90](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.89...v3.7.0-beta.90) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.89](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.88...v3.7.0-beta.89) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.88](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.87...v3.7.0-beta.88) (2023-10-03) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.87](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.86...v3.7.0-beta.87) (2023-09-29) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.86](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.85...v3.7.0-beta.86) (2023-09-29) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.85](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.84...v3.7.0-beta.85) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.84](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.83...v3.7.0-beta.84) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.83](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.82...v3.7.0-beta.83) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.82](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.81...v3.7.0-beta.82) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.81](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.80...v3.7.0-beta.81) (2023-09-26) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.80](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.79...v3.7.0-beta.80) (2023-09-22) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.79](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.78...v3.7.0-beta.79) (2023-09-22) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.78](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.77...v3.7.0-beta.78) (2023-09-21) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.77](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.76...v3.7.0-beta.77) (2023-09-21) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.76](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.75...v3.7.0-beta.76) (2023-09-19) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.75](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.74...v3.7.0-beta.75) (2023-09-18) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.74](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.73...v3.7.0-beta.74) (2023-09-15) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.73](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.72...v3.7.0-beta.73) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.72](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.71...v3.7.0-beta.72) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.71](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.70...v3.7.0-beta.71) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.70](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.69...v3.7.0-beta.70) (2023-09-12) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.69](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.68...v3.7.0-beta.69) (2023-09-11) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.68](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.67...v3.7.0-beta.68) (2023-09-11) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.67](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.66...v3.7.0-beta.67) (2023-09-06) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.66](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.65...v3.7.0-beta.66) (2023-09-06) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.65](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.64...v3.7.0-beta.65) (2023-09-06) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.64](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.63...v3.7.0-beta.64) (2023-09-05) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.63](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.62...v3.7.0-beta.63) (2023-09-01) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.62](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.61...v3.7.0-beta.62) (2023-08-30) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.61](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.60...v3.7.0-beta.61) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.60](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.59...v3.7.0-beta.60) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.59](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.58...v3.7.0-beta.59) (2023-08-29) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.58](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.57...v3.7.0-beta.58) (2023-08-25) + +**Note:** Version bump only for package @ohif/extension-dicom-video + + + + + +# [3.7.0-beta.57](https://github.com/OHIF/Viewers/compare/v3.7.0-beta.56...v3.7.0-beta.57) (2023-08-23) + +**Note:** Version bump only for package @ohif/extension-dicom-video diff --git a/extensions/dicom-video/package.json b/extensions/dicom-video/package.json index 021018ca1bd..01a7bbca47f 100644 --- a/extensions/dicom-video/package.json +++ b/extensions/dicom-video/package.json @@ -1,6 +1,6 @@ { "name": "@ohif/extension-dicom-video", - "version": "3.7.0-beta.46", + "version": "3.8.0-beta.60", "description": "OHIF extension for video display", "author": "OHIF", "license": "MIT", @@ -20,6 +20,8 @@ "access": "public" }, "scripts": { + "clean": "shx rm -rf dist", + "clean:deep": "yarn run clean && shx rm -rf node_modules", "dev": "cross-env NODE_ENV=development webpack --config .webpack/webpack.dev.js --watch --output-pathinfo", "build": "cross-env NODE_ENV=production webpack --config .webpack/webpack.prod.js", "build:package-1": "yarn run build", @@ -28,9 +30,9 @@ "test:unit:ci": "jest --ci --runInBand --collectCoverage --passWithNoTests" }, "peerDependencies": { - "@ohif/core": "3.7.0-beta.46", - "@ohif/ui": "3.7.0-beta.46", - "dcmjs": "^0.29.5", + "@ohif/core": "3.8.0-beta.60", + "@ohif/ui": "3.8.0-beta.60", + "dcmjs": "^0.29.12", "dicom-parser": "^1.8.9", "hammerjs": "^2.0.8", "prop-types": "^15.6.2", diff --git a/extensions/dicom-video/src/getSopClassHandlerModule.js b/extensions/dicom-video/src/getSopClassHandlerModule.js index f7d28e7bfbf..50181590f20 100644 --- a/extensions/dicom-video/src/getSopClassHandlerModule.js +++ b/extensions/dicom-video/src/getSopClassHandlerModule.js @@ -7,8 +7,7 @@ const SOP_CLASS_UIDS = { VIDEO_ENDOSCOPIC_IMAGE_STORAGE: '1.2.840.10008.5.1.4.1.1.77.1.1.1', /** Need to use fallback, could be video or image */ SECONDARY_CAPTURE_IMAGE_STORAGE: '1.2.840.10008.5.1.4.1.1.7', - MULTIFRAME_TRUE_COLOR_SECONDARY_CAPTURE_IMAGE_STORAGE: - '1.2.840.10008.5.1.4.1.1.7.4', + MULTIFRAME_TRUE_COLOR_SECONDARY_CAPTURE_IMAGE_STORAGE: '1.2.840.10008.5.1.4.1.1.7.4', }; const sopClassUids = Object.values(SOP_CLASS_UIDS); @@ -29,49 +28,31 @@ const SupportedTransferSyntaxes = { const supportedTransferSyntaxUIDs = Object.values(SupportedTransferSyntaxes); -const _getDisplaySetsFromSeries = ( - instances, - servicesManager, - extensionManager -) => { +const _getDisplaySetsFromSeries = (instances, servicesManager, extensionManager) => { const dataSource = extensionManager.getActiveDataSource()[0]; return instances .filter(metadata => { const tsuid = - metadata.AvailableTransferSyntaxUID || - metadata.TransferSyntaxUID || - metadata['00083002']; + metadata.AvailableTransferSyntaxUID || metadata.TransferSyntaxUID || metadata['00083002']; if (supportedTransferSyntaxUIDs.includes(tsuid)) { return true; } - if ( - metadata.SOPClassUID === SOP_CLASS_UIDS.VIDEO_PHOTOGRAPHIC_IMAGE_STORAGE - ) { + if (metadata.SOPClassUID === SOP_CLASS_UIDS.VIDEO_PHOTOGRAPHIC_IMAGE_STORAGE) { return true; } // Assume that an instance with one of the secondary capture SOPClassUIDs and // with at least 90 frames (i.e. typically 3 seconds of video) is indeed a video. return ( - secondaryCaptureSopClassUids.includes(metadata.SOPClassUID) && - metadata.NumberOfFrames >= 90 + secondaryCaptureSopClassUids.includes(metadata.SOPClassUID) && metadata.NumberOfFrames >= 90 ); }) .map(instance => { - const { - Modality, - SOPInstanceUID, - SeriesDescription = 'VIDEO', - } = instance; - const { - SeriesNumber, - SeriesDate, - SeriesInstanceUID, - StudyInstanceUID, - NumberOfFrames, - } = instance; + const { Modality, SOPInstanceUID, SeriesDescription = 'VIDEO' } = instance; + const { SeriesNumber, SeriesDate, SeriesInstanceUID, StudyInstanceUID, NumberOfFrames } = + instance; const displaySet = { //plugin: id, Modality, @@ -107,16 +88,9 @@ const _getDisplaySetsFromSeries = ( }); }; -export default function getSopClassHandlerModule({ - servicesManager, - extensionManager, -}) { +export default function getSopClassHandlerModule({ servicesManager, extensionManager }) { const getDisplaySetsFromSeries = instances => { - return _getDisplaySetsFromSeries( - instances, - servicesManager, - extensionManager - ); + return _getDisplaySetsFromSeries(instances, servicesManager, extensionManager); }; return [ diff --git a/extensions/dicom-video/src/index.tsx b/extensions/dicom-video/src/index.tsx index 1bc3a3359d3..a5fcd40d156 100644 --- a/extensions/dicom-video/src/index.tsx +++ b/extensions/dicom-video/src/index.tsx @@ -3,9 +3,7 @@ import getSopClassHandlerModule from './getSopClassHandlerModule'; import { id } from './id'; const Component = React.lazy(() => { - return import( - /* webpackPrefetch: true */ './viewports/OHIFCornerstoneVideoViewport' - ); + return import(/* webpackPrefetch: true */ './viewports/OHIFCornerstoneVideoViewport'); }); const OHIFCornerstoneVideoViewport = props => { @@ -42,9 +40,7 @@ const dicomVideoExtension = { ); }; - return [ - { name: 'dicom-video', component: ExtendedOHIFCornerstoneVideoViewport }, - ]; + return [{ name: 'dicom-video', component: ExtendedOHIFCornerstoneVideoViewport }]; }, // getCommandsModule({ servicesManager }) { // return { diff --git a/extensions/dicom-video/src/viewports/OHIFCornerstoneVideoViewport.tsx b/extensions/dicom-video/src/viewports/OHIFCornerstoneVideoViewport.tsx index 0b9a92d497a..8f7e965a86c 100644 --- a/extensions/dicom-video/src/viewports/OHIFCornerstoneVideoViewport.tsx +++ b/extensions/dicom-video/src/viewports/OHIFCornerstoneVideoViewport.tsx @@ -22,17 +22,23 @@ function OHIFCornerstoneVideoViewport({ displaySets }) { // Need to copies of the source to fix a firefox bug return ( -
+