Skip to content

feat(deps): move from dcmjs 0.52.0 to the @dcmjs-org/dcmjs 1.0 beta - #6397

Draft
wayfarer3130 wants to merge 23 commits into
masterfrom
feat/dcmjs-1.0-beta
Draft

wayfarer3130 wants to merge 23 commits into
masterfrom
feat/dcmjs-1.0-beta

Conversation

@wayfarer3130

@wayfarer3130 wayfarer3130 commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Context

This PR moves OHIF from dcmjs@0.52.0 to the dcmjs 1.0 beta. The npm package of the beta is @dcmjs-org/dcmjs@1.0.0-beta.10. The companion Cornerstone3D PR is cornerstonejs/cornerstone3D#3019.

The default export of @dcmjs-org/dcmjs has the same names as dcmjs 0.52. Thus most source files change only the import line. But dcmjs 1.0 changes the shape of naturalized datasets. The dcmjs migration guide describes the changes in docs/MIGRATION.md, "Naturalization: six changes":

  • A sequence with one item is now the item object. The item object has a hidden [0], a length of 1, and an iterator. Array.isArray, map, forEach, find and push do not work on it. A spread, a structuredClone or a JSON copy of the item object loses [0].
  • The VM of the dictionary decides between a single value and a list. A VM of 1-n keeps a list, also for one value. For example, WindowCenter is [40] and SpecificCharacterSet is ["ISO_IR 100"].
  • A VM 1 value is a scalar, and the value is null when it is empty. PatientName is { Alphabetic }, and PatientName[0] is undefined.
  • Private elements go into one key for each reserved block, `${gggg}:${creator}`, for example "7053:Philips PET Private Group": { "creator": "Philips PET Private Group", "reservedBlock": 16, "x09": [1.88] }. The hex key (70531009) is only a non-enumerable alias. A spread, a JSON copy or a clone loses the alias, but keeps the block.

These shape changes broke some OHIF code. No unit test found the breakages. This PR fixes them.

CS3D_REF: feat/dcmjs-1.0-beta

The CS3D_REF line above tells CI to build this PR together with the Cornerstone3D branch of cornerstonejs/cornerstone3D#3019. The Playwright workflow and the CircleCI jobs clone that branch, build it, and link it into OHIF. The combined deploy preview is at cs3d-pr-6397--ohif-dev.netlify.app. While the line names a branch, the "CS3D Branch Merge Guard" job shows a failure. This failure is correct, because this PR must not merge before the Cornerstone3D release.

Merge order

Do not merge this PR before a Cornerstone3D release that contains cornerstonejs/cornerstone3D#3019. This PR pins @cornerstonejs/*@5.12.0, and these packages read the data with dcmjs 0.52 code. In @cornerstonejs/adapters@5.12.0, generateToolState gives frameNumber as [3] and not 3 for an SR that dcmjs 1.0 reads. The SR hydration of OHIF then uses an array where it expects a number. The Cornerstone3D branch gives 3. A local check with the linked Cornerstone3D branch showed this result.

  1. Review the two PRs together. Use the combined build of the CS3D_REF line for the tests and the preview.
  2. Merge feat(deps): move from dcmjs 0.52 to the dcmjs 1.0 beta (@dcmjs-org/dcmjs) cornerstonejs/cornerstone3D#3019. The Cornerstone3D release workflow publishes a new version. The current release is 5.14.0, so the bump from 5.12.0 also brings the changes of 5.13 and 5.14.
  3. In this PR, run node .scripts/cs3d-set-version.mjs <version>, and commit the package.json changes and the lockfile.
  4. In this PR, change the line to CS3D_REF: feat/dcmjs-1.0-beta now <version>. The merge guard then passes, because the PR depends on a published release.
  5. In this PR, examine the dcmjs: 0.52.0 override in pnpm-workspace.yaml. Remove the override if no package needs dcmjs 0.52 after step 3.
  6. Merge this PR.

Do not merge this PR alone. Without the Cornerstone3D release, the viewer has two dcmjs copies, and the old adapters give an array as the SR frame number. Also, this PR imports asMultiValueArray, asArrayFirst and getPrivateTagValue from @cornerstonejs/utils. @cornerstonejs/utils@5.12.0 does not export these functions, so a build without the CS3D_REF line fails until step 3.

After step 3, only dicom-microscopy-viewer@0.48.6 needs the override. That package declares dcmjs ^0.41.0, and OHIF loads its prebuilt dist/dynamic-import bundle, so webpack does not bundle that dcmjs copy. A removal of the override only installs dcmjs 0.41.x for that package.

Changes & Results

Dependency

  • platform/app and platform/core depend on @dcmjs-org/dcmjs@1.0.0-beta.10 (exact pin) in place of dcmjs@0.52.0. The extensions that import dcmjs change their peer dependency in the same way.
  • Five extensions declared dcmjs but imported nothing from it: dicom-pdf, dicom-video, measurement-tracking, cornerstone-dynamic-volume and test-extension. This PR removes the declaration. cornerstone-dicom-sr uses dcmjs only in a test, so the dependency moves to devDependencies.
  • cornerstone-dicom-rt, cornerstone-dicom-seg and dicom-microscopy import dcmjs but did not declare it. They got dcmjs through hoisting. These extensions now declare @dcmjs-org/dcmjs.
  • cornerstone-dicom-rt, cornerstone-dicom-sr, tmtv and dicom-video now declare @cornerstonejs/utils, because they import asArray.
  • pnpm-workspace.yaml adds @dcmjs-org/* to minimumReleaseAgeExclude. The exact pin of the beta can be newer than the 48-hour cutoff.
  • pnpm-workspace.yaml keeps the dcmjs: 0.52.0 override, so that the copy in @cornerstonejs/adapters, metadata, utils and ai 5.12.0 stays pinned. Until step 2 of the merge order, the viewer bundle has two dcmjs copies.
  • The webpack externals regex /\b(dcmjs)/ already matches @dcmjs-org/dcmjs, so webpack needs no change.
  • jest needs no moduleNameMapper. OHIF imports only the default entry, and beta.10 has a require entry.

Fixes for the 1.0 shapes

  • RT loadRTStruct.js: forEach on ROIContourSequence and ContourSequence, and find on StructureSetROISequence and RTROIObservationsSequence, threw TypeError for an RTSTRUCT with one ROI or one contour. The local _toArray copy is removed.
  • RT getSopClassHandlerModule.ts: forEach on the four reference sequences. A missing sequence is now skipped, and the code does not throw. The referenced images now come from every item of ReferencedSeriesSequence. Before, the code read ReferencedSeriesSequence.ReferencedInstanceSequence, which is undefined for a sequence with many items. That bug existed with 0.52.
  • SR formatContentItem.ts: a PNAME item showed no name.
  • SR OHIFCornerstoneSRContainer.tsx: contentSequence.map threw for a container with one child.
  • SR getSopClassHandlerModule.ts: find on the root ContentSequence. ReferencedFrameNumber became [n], so annotation.data.frameNumber became an array. The value is now a number, and the default is 1. The reference check _measurementReferencesSOPInstanceUID read the raw value. An empty ReferencedFrameNumber is not valid DICOM, but some writers make it. dcmjs 1.0 gives it as [], so Number([] || 1) was 0, and the measurement did not load. Both places now use one function, _getReferencedFrameNumber, and an empty value gives frame 1.
  • Microscopy loadSR.ts and DicomMicroscopySRSopClassHandler.js: find on the root ContentSequence.
  • Core MetadataProvider.ts: SequenceOfUltrasoundRegions?.map threw for an ultrasound image with one region. One region is the usual case.
  • TMTV commandsModule.ts: after an edit in the SUV panel, RadiopharmaceuticalInformationSequence[0] was undefined, because the object spread of the panel removes the hidden [0].
  • Video getSopClassHandlerModule.js: an AvailableTransferSyntaxUID list with one value did not match the list of video transfer syntaxes.
  • DICOMweb bulk data: addRetrieveBulkDataNaturalized went into a value only when Array.isArray(value) was true. Thus bulk data in a sequence with one item got no retrieveBulkData and no absolute URI. Examples are the ECG WaveformSequence, RTSTRUCT contour data with one ROI, and SR GraphicData. Private bulk data in a reserved block, for example the Philips SUV factor 70531000, had the same problem. The walk is now in DicomWebDataSource/utils/forEachBulkDataValue.ts. The walk goes into arrays, one-item sequences and private blocks. The walk does not go into PN objects, _vrMap, typed arrays or bulk data descriptors.
  • PET SUV, CorrectedImage: some DICOMweb servers store a multi-valued CS as one backslash-joined string, for example CorrectedImage: ["DECY\\ATTN\\SCAT"]. dcmjs 0.52 gave the string, and the ATTN check found a substring. dcmjs 1.0 gives a list with one item, so the check failed, calculate-suv threw, and the PT had no SUV scaling. The code now splits the values with asMultiValueArray from @cornerstonejs/utils.
  • fixMultiValueKeys: the same storage form of ImageType (["ORIGINAL\\PRIMARY\\VOLUME\\NONE"]) was not split. dcmjs 1.0 denaturalizeDataset then cut the value to 16 characters, and the microscopy viewer found no VOLUME image. fixMultiValueKeys now splits ImageType and FrameType with asMultiValueArray, and DicomMicroscopyViewport.tsx uses fixMultiValueKeys.

The other fixes use asArray and asArrayFirst from @cornerstonejs/utils. The SR, microscopy and tag browser code keep their local toArray helpers. These helpers use Array.isArray, so they are already correct for 1.0.

Private tags

OHIF reads each private tag with getPrivateTagValue(dataset, group, creator, element) from @cornerstonejs/utils (cornerstonejs/cornerstone3D#3019). The function reads the dcmjs 1.0 block form gggg:creator first. It also reads the hex-key form, because some Cornerstone3D paths still make that form. For the hex form, the function finds the creator element gggg00xx and then reads ggggxxEE. The function does not assume block 0x10.

  • PET SUV, Philips: dcmjs 1.0 gives each private element as a list, so 70531000 (SUV scale factor) and 70531009 (activity concentration scale factor) were [0.00038]. coerceNumber rejected the list, and the Philips scale factors were lost for a series with Units = CNTS. getPTImageIdInstanceMetadata.ts now reads block 7053:Philips PET Private Group, elements x00 and x09, with getPrivateTagValue, and uses the first value.
  • PET SUV, GE: getPTImageIdInstanceMetadata.ts reads block 0009:GEMS_PETD_01, element x0D, with getPrivateTagValue.
  • Multi-frame instances: the old combineFrameInstance copied the instance with a spread. The spread lost the hex aliases, so a multi-frame PT had no Philips or GE values. combineFrameInstance now uses the dcmjs frameDataset (see "Multi-frame instances: dcmjs frameDataset"). The frame views keep the private blocks and the hex aliases on each level. The option privateMacros: true also merges a private sequence in a functional group item, as the old code did.
  • resolveBulkDataTags.ts: the registry of private bulk data tags now keeps a creator for each tag. The two default tags (70531000, 70531009) have the creator Philips PET Private Group. With a creator, the function finds the value with findPrivateTag, and writes the number back into the block, so the number survives a later copy. registerResolvedBulkDataTags also accepts { tag, creator }, and a hex string still works.
  • DICOM JSON data source: no change against master. An earlier commit of this PR added a definePrivateAliases call. The readers now read the block, so this PR removes that call again.
  • Tag browser: dcmjs 1.0 puts private tags into a gggg:creator key, so the browser showed no private tags. The new getDatasetEntries.ts gives each block element as a hex tag, with the value from the block.

Multi-frame instances: dcmjs frameDataset

dcmjs 1.0.0-beta.9 added dcmjs.data.frameDataset(dataset, frameNumber, options) (dcmjs-org/dcmjs#631). The function gives the view of one frame. The view reads the per-frame functional groups, then the shared functional groups, then the root. dcmjs-org/dcmjs#628 is also in beta.9, so the views keep the private hex aliases with no workaround.

  • OHIF no longer has its own combineFrameInstance. platform/core/src/classes/MetadataProvider.ts calls utilities.combineFrameInstance from @cornerstonejs/metadata (feat(deps): move from dcmjs 0.52 to the dcmjs 1.0 beta (@dcmjs-org/dcmjs) cornerstonejs/cornerstone3D#3019). That function calls frameDataset, and gives the NM and RTDOSE position fallbacks, the ImageType split and frameNumber.
  • platform/core/src/utils/combineFrameInstance.ts and platform/core/src/utils/dicomSplit.ts are deleted. @ohif/core did not export them.
  • MetadataProvider.ts keeps the guard NumberOfFrames >= 2, so a single-frame instance does not change. It also keeps the ImageType split on the root instance, because isNMReconstructable reads ImageType[2] on the root instance.
  • The local definePrivateAliases workaround is removed.
  • The views are cached on the instance in the non-enumerable _frameDatasets key. The keys _parentInstance and _shared, and the numeric frame keys, are removed.
  • platform/core/src/utils/addProxyFields.js read _parentInstance._shared. The function now reads SharedFunctionalGroupsSequence[0] directly. Its setter now writes to the frame view, and not to the root instance, because each view inherits from the root instance.
  • extensions/default/src/utils/dicomWriter.ts no longer lists _parentInstance as a runtime key.

Changes that users and integrators see

  • Multi-frame position order. A per-frame ImagePositionPatient now has priority over a root ImagePositionPatient. The root value is a fallback only. PS3.3 C.7.6.16 gives this order. Before, the root value had priority.

  • RTDOSE gets a position for each frame. Since fix(RTStruct): RTSTRUCT contours rendered on first slice for multi-frame images without IPP #5811, the old RTDOSE branch with GridFrameOffsetVector did not run, so each RTDOSE frame got the root position. Now each frame gets its own position. The code reads both encodings of PS3.3 C.8.8.3.2: relative offsets (the first value is 0), and absolute z positions.

  • NM does not change the root instance. The old code wrote ImageOrientationPatient from DetectorInformationSequence on the root instance. Now only the frame views get this value. When the root instance has no position, the position from the detector replaces a per-frame position.

  • ImageType split. The ImageType split now also splits the dcmjs 1.0 form with one joined value, for example ['ORIGINAL\\PRIMARY\\RECON TOMO']. Thus such an NM series can now count as reconstructable.

  • A frame number above the last frame throws. A frame number above NumberOfFrames now throws a RangeError from frameDataset. Before, the code gave the shared values and the root values. A frame number 0 is frame 1.

  • A multi-frame image with no position keeps a position for each frame. A US cine, an XA run or a multi-frame secondary capture with no ImagePositionPatient gets [0, 0, frameNumber] on each frame, as with fix(RTStruct): RTSTRUCT contours rendered on first slice for multi-frame images without IPP #5811. A test in MetadataProvider.test.ts covers this behaviour.

  • An SR with a labelled measurement saves. The dcmjs 1.0 SR writer gives a one-item ConceptCodeSequence as the item object. The Cornerstone3D LabelData now reads the sequence as an array or as one item. Before this fix, "Save SR" and "Download SR" failed for a measurement with a label.

  • Shared values on the root instance. addProxyFields now gives the shared ImageOrientationPatient and PixelSpacing on a multi-frame root instance from the start. Before, these values came only after the first frame lookup.

  • Removed instance keys. An extension that read _parentInstance or _shared from an instance must use frameDataset or the instance itself.

  • Philips PET in the hex-key form needs the creator element. getPrivateTagValue reads 70531000 and 70531009 only when the creator element 7053,00xx has the value Philips PET Private Group. Before, the code read 70531000 and 70531009 also without a creator element. PS3.5 7.8.1 requires the creator element, and data from dcmjs and from a DICOMweb server has the creator. A dataset without the creator element now has no Philips scale factors. With this rule, the code does not read a wrong value when a different vendor uses group 7053.

  • GE PET SUV values can change. getPTImageIdInstanceMetadata.ts read the GE private key '0009100d' in lower case. dcmjs and DICOM JSON use upper-case keys, so the key never matched, also with dcmjs 0.52. The code now reads the GE post-injection date and time (block GEMS_PETD_01, element x0D) with getPrivateTagValue. calculate-suv uses this value only when SeriesDate/SeriesTime is later than the earliest AcquisitionDate/AcquisitionTime. In that case, the scan time for the decay correction is now the GE value, and not the earliest acquisition time. The new SUV is the old SUV × 2^((t_GE − t_acq) / T½). For example, a GE time 1800 s before the acquisition time with F-18 (T½ = 6586.2 s) gives a factor of about 0.827. If the GE time is before the injection start, calculate-suv throws "Decay time cannot be less than zero", and the series has no SUV scaling.

  • .scripts/dicom-json-generator.js now writes the 1.0 shapes. A sequence with one item is an object, and a VM 1-n value with one value is a list. OHIF reads both shapes, because wrapSequences in the DICOM JSON data source wraps the sequences again. A third-party reader of the generated JSON can see a different format. The generator writes no private elements. This PR updates platform/docs/docs/configuration/dataSources/dicom-json.md and introduction.md with the 1.0 shapes. The docs also describe the private block form of a JSON file that JSON.stringify makes from a dcmjs 1.0 dataset, and getPrivateTagValue.

  • registerResolvedBulkDataTags accepts { tag, creator }. A private bulk data tag that an extension registers without a creator still works with the hex-key form.

  • Instance metadata in DicomMetadataStore has the 1.0 shapes. An extension that reads instance metadata and uses array methods on a sequence must use asArray, or a similar helper.

Decisions: the compatible default export or the new API. Each use keeps the compatible default export. A new-API entry point was not both small and clearly better for any use.

Use Decision Reason
core DicomMetadataStore.ts: readFile, naturalizeDataset default export Main metadata path. /eventStream gives no gain here.
app dicomFileLoader.js: readFile, naturalizeDataset, namifyDataset default export Follow-up: /eventStream for large local files.
default retrieveMetadataLoaderAsync.js: naturalizeDataset default export Same path as the store.
default DicomWebDataSource: DicomDict, denaturalizeDataset default export The writer does not change in 1.0.
default dicomWriter.ts: datasetToDict default export Follow-up: fromNaturalized to Part10Writer.
default DicomTagBrowser: DicomMetaDictionary, Tag.fromPString default export /dictionary does not make the bundle smaller.
cornerstone getSopClassHandlerModule.js: denaturalizeDataset default export Small use.
dicom-rt loadRTStruct.js: readFile, naturalizeDataset, Colors default export Shape fix only.
dicom-seg: Colors.rgb2DICOMLAB, dicomlab2RGB default export Pure functions.
tmtv: datasetToBlob on the RTSS from the adapters default export Plain objects move between the two copies without problems.
dicom-microscopy: sr.*, adapters.DICOMMicroscopyViewer, datasetToBuffer, denaturalizeDataset, uid() default export dcmjs 1.0 keeps sr and adapters.
dicom-pdf, dicom-video no dcmjs use These extensions get the document or the video through data source bulk data. /encapsulated and /media have nothing to replace.

Follow-ups

  • MetadataProvider.ts keeps the guard NumberOfFrames >= 2, so an enhanced image with one frame gets no frame view. frameDataset can make a view for that image. A removal of the guard changes the values that the viewer shows, so this PR does not make that change.
  • cornerstone-dicom-seg/src/getSopClassHandlerModule.ts:308 and cornerstone-dicom-pmap/src/getSopClassHandlerModule.ts:76 read ReferencedInstanceSequence from the first referenced series only. The RT fix of this PR can apply to them too.

Testing

All commands ran from the repository root.

  • npx jest (with the Cornerstone3D branch linked, dcmjs beta.10): 128 suites, 1529 tests pass.
  • pnpm run compiler:coverage:ci: pass.
  • pnpm run lint:compiler:ci: pass.
  • pnpm build: pass.
  • New tests. Each test failed before the fix and passes after the fix:
    • cornerstone-dicom-rt/src/loadRTStruct.test.js: an RTSTRUCT with one ROI and one contour loads. The RT extension had no jest setup, so this PR adds jest.config.js and the test:unit script, as in the SEG extension.
    • cornerstone-dicom-sr/src/utils/formatContentItem.test.ts: a PNAME item shows "Doe, John".
    • DicomWebDataSource/utils/forEachBulkDataValue.test.ts: the walk finds bulk data in a one-item WaveformSequence, in nested ROI contours, in PixelData and in a private block.
    • DicomTagBrowser/getDatasetEntries.test.ts: a private block gives its hex tags, with the values of a JSON copy.
    • platform/core/src/utils/resolveBulkDataTags.test.ts: the number goes into the block (reserved block 0x11), and a JSON copy keeps the number.
    • getPTImageIdInstanceMetadata.test.ts: the Philips scale factors of a multi-frame PT through combineFrameInstance.
    • cornerstone-dicom-sr/src/getSopClassHandlerModule.test.ts: a measurement with an empty ReferencedFrameNumber loads on frame 1.
  • With the Cornerstone3D branch linked locally (pnpm run cs3d:build, then pnpm run cs3d:link), the same jest, compiler and build commands pass.
  • More new tests: the Philips scale factors and the GE post-injection time from a dcmjs 1.0 naturalized dataset, a backslash-joined CorrectedImage, and fixMultiValueKeys.test.ts. Each test failed before its fix.
  • Playwright: the first combined run (40897593586) had 11 failures: ContourCombineOperations (3), ContourSmoothOperation (2), RTDataOverlayForUnreferencedDisplaySetNoHydration, RTHydration, RTHydrationDisableConfirmation, TMTVCSVReport, MicroscopyPanel rename and WSI render. Three runs of other PRs with a CS3D_REF branch on the same Cornerstone3D base had no failures. Thus this PR caused all 11 failures. The CorrectedImage fix corrects the PET, RT, contour and TMTV failures. The ImageType fix corrects the microscopy and WSI failures. Locally, with the Cornerstone3D branch linked, the 7 spec files of the failures pass (12 tests). The combined run on commit 83f15c50d5 passed with no failures. The private tag commits and the beta.9 commits after 83f15c50d5 get a new combined run. Playwright did not run locally for the beta.9 commits.

To test by hand:

  1. Open an RTSTRUCT with one ROI, and make sure that the contour shows.
  2. Open an SR with a person name item, and make sure that the name shows.
  3. Open an ECG, and make sure that the waveform loads.
  4. Open the tag browser on an image with private tags, and make sure that the private tags show.
  5. Open an ultrasound image with one region, and make sure that the measurements use the region calibration.
  6. Open an RTDOSE over a CT, and make sure that each dose frame shows at its own slice position.
  7. Open an enhanced multi-frame CT or MR and an NM multi-frame image, and make sure that the frames show in the correct order and position.
  8. Add a length measurement, give it a label, and save an SR. Make sure that the save succeeds, and that the label shows when the SR loads.

Checklist

PR

  • My Pull Request title is descriptive, accurate and follows the
    semantic-release format and guidelines.

Code

  • My code has been well-documented (function documentation, inline comments,
    etc.)

Public Documentation Updates

  • The documentation page has been updated as necessary for any public API
    additions or removals.

Tested Environment

  • OS: Windows 11
  • Node version: 24.20.0
  • Browser: not tested in a browser

🤖 Generated with Claude Code

wayfarer3130 and others added 3 commits October 28, 2026 16:04
Replace the dcmjs dependency with @dcmjs-org/dcmjs pinned to 1.0.0-beta.5
and import its default export, which keeps the 0.52 API surface.

- Rename the dependency in platform/app, platform/core and the extensions
  that declared it, and declare it in cornerstone-dicom-rt,
  cornerstone-dicom-seg and dicom-microscopy, which import it.
- Exempt @dcmjs-org/* from minimumReleaseAge.
- Keep the dcmjs 0.52.0 override for @cornerstonejs/adapters, metadata,
  utils and ai 5.12.0, which still depend on dcmjs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dcmjs 1.0 naturalizes a one-item sequence to the item object (not an
array), keeps a one-value VM 1-n attribute as a list, gives a VM 1 PN as
one object, and groups private tags into a creator block. Adapt the call
sites that assumed the 0.x shapes, with asArray from @cornerstonejs/utils.

- RT: load an RTSTRUCT with one ROI or one contour, and derive the
  referenced series from one-item reference sequences.
- SR: show a PNAME item, render a container with one child, read a root
  content sequence with one item, and keep a numeric frame number.
- Microscopy SR: read a root content sequence with one item.
- Core: map an ultrasound image with one region.
- TMTV: read the radiopharmaceutical item after an SUV metadata edit.
- Video: match a one-value AvailableTransferSyntaxUID list.
- Tag browser: list the private tags of a creator block.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The DICOMweb data source walked into a sequence only when the value was
an array. With dcmjs 1.0 a one-item sequence is the item object, and a
private element sits in a creator block, so bulk data there (ECG
WaveformData, RTSTRUCT ContourData, SR GraphicData, the Philips SUV
factor) got no absolute URI and no retrieveBulkData.

- Move the walk to utils/forEachBulkDataValue, which also follows a
  one-item sequence and a private block, and test it.
- Move the tag-browser key expansion to getDatasetKeys.ts and test it.
- Use one guard, asArray(x ?? []), at the RT find and SR PNAME sites.
- Name all CS3D packages that still bring dcmjs 0.52 in the override
  comment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@netlify

netlify Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for ohif-dev ready!

Name Link
🔨 Latest commit 2a28245
🔍 Latest deploy log https://app.netlify.com/projects/ohif-dev/deploys/6aec96f29fa9fde96b682afa
😎 Deploy Preview https://deploy-preview-6397--ohif-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@cypress

cypress Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Viewers    Run #7091

Run Properties:  status check passed Passed #7091  •  git commit 2a28245b80: test(core): a frame of a multi-frame image with no position gets [0, 0, frameNum...
Project Viewers
Branch Review feat/dcmjs-1.0-beta
Run status status check passed Passed #7091
Run duration 01m 59s
Commit git commit 2a28245b80: test(core): a frame of a multi-frame image with no position gets [0, 0, frameNum...
Committer Bill Wallace
View all properties for this run ↗︎

Test results
Tests that failed  Failures 0
Tests that were flaky  Flaky 0
Tests that did not run due to a developer annotating a test with .skip  Pending 0
Tests that did not run due to a failure in a mocha hook  Skipped 0
Tests that passed  Passing 28
View all changes introduced in this branch ↗︎

wayfarer3130 and others added 5 commits October 29, 2026 10:48
…six extensions

dicom-pdf, dicom-video, measurement-tracking, cornerstone-dynamic-volume and
test-extension import nothing from dcmjs. cornerstone-dicom-sr uses it only in
a unit test, so it moves to devDependencies there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The display set read ReferencedSeriesSequence.ReferencedInstanceSequence,
which is undefined for a sequence with more than one item and for the
sequence that the code builds from ReferencedFrameOfReferenceSequence.
The bug existed with dcmjs 0.52.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ent the dcmjs 1.0 shapes

The DICOM JSON generator now writes the dcmjs 1.0 shapes. JSON keeps a
private block (gggg:creator) but drops its non-enumerable hex tag aliases,
so a read such as instance['70531000'] (Philips SUV scale factor) gave
undefined. The data source now calls definePrivateAliases on each instance.
A file that the 0.x generator wrote is not changed.

The docs describe the shapes that the generator writes, and that OHIF
reads the files of both generators.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…verlap save test

dcmjs 1.0 gives the US 1-n value as a list. The test used the list as an
object key and relied on the key coercion.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
wayfarer3130 and others added 3 commits October 29, 2026 15:22
dcmjs 1.0 gives a private element as a list, also for one value, so
(7053,1000) and (7053,1009) became [0.00038] and coerceNumber dropped
them. PhilipsPETPrivateGroup was then missing, and SUV for Philips PET
with Units CNTS lost its scale factor. Read asArray(value)[0] for the
Philips scale factors and the GE post-injection date time.

Add @cornerstonejs/utils 5.12.0 to the default extension for asArray,
and a test that uses a dataset from the dcmjs 1.0 naturalizeDataset.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lation

Some DICOMweb sources write all CorrectedImage values as one string, for
example ["DECY\\ATTN\\SCAT"]. dcmjs 0.52 gave the string, and the
calculate-suv check for "ATTN" and "DECY" matched a substring. dcmjs 1.0
keeps the one-item list, so the check threw, the PT series got no SUV
scaling, and the PT viewports showed raw values. This broke the RT,
contour and TMTV CSV Playwright tests on the combined CS3D build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Some DICOMweb sources write all ImageType values as one string, for
example ["ORIGINAL\\PRIMARY\\VOLUME\\NONE"]. fixMultiValueKeys split only
a string, and dcmjs 1.0 keeps the one-item list, so denaturalizeDataset
truncated the value to "ORIGINAL\\PRIMARY". The WSI viewport and the
microscopy viewer then found no VOLUME image ("Reduce of empty array",
"Input metadata array is empty"). Split list items too, also for
FrameType, and use fixMultiValueKeys in the microscopy viewport.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ate tag

- getPTImageIdInstanceMetadata and fixMultiValueKeys use asMultiValueArray
  from @cornerstonejs/utils in place of local split code for CorrectedImage,
  ImageType and FrameType.
- The GE post-injection date time (0009,100D) was read with the key
  '0009100d'. dcmjs (0.52 and 1.0) and DICOM JSON key private tags in
  upper-case hex, so the value was never found. calculate-suv now gets
  GEPrivatePostInjectionDateTime and uses it as the scan time for decay
  correction when SeriesDate/SeriesTime is after the earliest acquisition
  time, in place of that acquisition time.

asMultiValueArray needs the CS3D release that contains it; the pinned
@cornerstonejs/utils 5.12.0 does not have it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
wayfarer3130 and others added 5 commits October 30, 2026 08:22
getPTImageIdInstanceMetadata reads the Philips (7053) and GE (0009)
private tags by creator, so a multi-frame PT keeps its SUV factors
after the combineFrameInstance copy. resolveBulkDataTags finds a
registered private tag by its creator and writes the number into the
dcmjs 1.0 private block; a registration can carry a creator. The tag
browser reads private values from the block, and the DICOM JSON data
source no longer adds the hex tag aliases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… reader

The generator writes no private elements. Describe the gggg:creator
block of a JSON file from a dcmjs 1.0 naturalized dataset, and say that
OHIF reads private tags with getPrivateTagValue, which also reads the
hex-key form.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
combineFrameInstance spreads the instance and merges the functional groups
into Object.create levels. dcmjs 1.0 private tag aliases (70531009) are
non-enumerable, so the spread and the merge lost them. Define the aliases on
the parent copy, the shared level and each per-frame level.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nce check

The frame check of _measurementReferencesSOPInstanceUID read the raw value.
dcmjs 1.0 gives an empty element as [], so Number([] || 1) was 0 and the
measurement did not load. Read the frame with one rule in both places.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ew dcmjs package in a test comment

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
wayfarer3130 and others added 3 commits November 4, 2026 19:01
beta.9 adds dcmjs.data.frameDataset, gives each Object.create child its own
private alias record, and naturalizes a zero-length string element as empty.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hared functional groups

The getters read _parentInstance._shared, which existed only after a first
combineFrameInstance call. A write now defines the value on the object that
is written to, so a write to a frame view does not change the instance.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…js frameDataset

The NM, RTDOSE and default positions move to a module-level frameAttributes,
frameNumber to frameProperties. The local private alias workaround, the
_parentInstance copy and the _shared and per-frame cache keys are gone;
frameDataset caches the views in _frameDatasets. A per-frame
ImagePositionPatient now wins over a root value (PS3.3 C.7.6.16).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…aProvider

Delete the OHIF copy of combineFrameInstance and its test, and call
utilities.combineFrameInstance from @cornerstonejs/metadata. The OHIF copy
placed RTDOSE frames wrong when GridFrameOffsetVector holds absolute z
values (PS3.3 C.8.8.3.2). MetadataProvider keeps the NumberOfFrames < 2
guard, the parseInt of the frame number, and the split of the root
ImageType that isDisplaySetReconstructable reads. Delete dicomSplit.ts,
which has no other users; MetadataProvider uses the @cornerstonejs/metadata
dicomSplit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
beta.10 removes a debug log of an InlineBinary value (dcmjs #636).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…0, frameNumber]

Protects the #5811 behaviour now that MetadataProvider uses the Cornerstone3D combineFrameInstance.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch was successfully deployed

1 active deployment
unrestricted — 2a28245b Deployed Nov 5, 2026 by wayfarer3130 via playwright-tests (24.22.2) #5456
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant