Emit Source Series Information Sequence (3006,004C) in the HD RT Structure Set #2

Merged
sjswerdloff merged 1 commit from fix/hdss-source-series-information-sequence into main 2026-09-01 14:06:14 +02:00
Collaborator

Closes #1.

Two files, additive, no caller signature changes.

DicomExporter.export now also returns the series-level values it had already applied to the CT
datasets — modality, series date/time/description/number. Returned rather than recomputed, so
the RT-SS cannot end up describing a series different from the one written.

_build_structure_set_tags takes that dict and builds (3006,004C). All six sub-attributes are
Type 1, so a missing value raises ValueError naming the key rather than emitting a partial item
— an item short a Type 1 value is a defect that looks, from outside, exactly like the omission it
replaces. ct_series_uid is checked separately because it arrives by a different route and would
otherwise be written as the literal string "None".

Passing ct_series_info=None reproduces the previous behaviour exactly, so any other caller is
unaffected.

Verified against a generated phantom, not by inspection

before   839 passed, 1 failed   (3006,004C) SHALL be present but is missing
after    846 passed, 0 failed   conforms to HDSS (6 of 6 ROIs HD)

Row total 866 → 872: six new sub-attribute rows, plus the 004C row flipping from failed to passed,
which accounts for passed +7.

The emitted values were cross-checked against the CT series actually written to disk —
SeriesInstanceUID, Modality, SeriesDate and SeriesDescription all match, and the UID equals the one
in RT Referenced Series Sequence. The raise path was exercised for every required key, for an
empty-string value, and for an absent ct_series_uid; each names the value it is missing.

One choice that is yours

I widened DicomExporter.export's return value. The alternative is building 004C by re-reading the
written CT datasets. Mine is the smaller diff and cannot drift from what was written, but it does
mean the CT exporter's return value is now part of the RT-SS exporter's contract. Happy to redo
it the other way if you prefer that boundary
— say so and it is a quick change, no argument.

Tested headlessly (the domain layer is Qt-free, so PhantomModelDicomExporter
RtssHdExporter runs without a display). Nothing in the GUI path was touched.


Written by River (river-2fc1630e), collaborating with @sjswerdloff and posting through his account. The commit on the branch carries river-2fc1630e as author.

Closes #1. Two files, additive, no caller signature changes. `DicomExporter.export` now also returns the series-level values it had already applied to the CT datasets — modality, series date/time/description/number. **Returned rather than recomputed**, so the RT-SS cannot end up describing a series different from the one written. `_build_structure_set_tags` takes that dict and builds (3006,004C). All six sub-attributes are Type 1, so a missing value raises `ValueError` naming the key rather than emitting a partial item — an item short a Type 1 value is a defect that looks, from outside, exactly like the omission it replaces. `ct_series_uid` is checked separately because it arrives by a different route and would otherwise be written as the literal string `"None"`. Passing `ct_series_info=None` reproduces the previous behaviour exactly, so any other caller is unaffected. ## Verified against a generated phantom, not by inspection before 839 passed, 1 failed (3006,004C) SHALL be present but is missing after 846 passed, 0 failed conforms to HDSS (6 of 6 ROIs HD) Row total 866 → 872: six new sub-attribute rows, plus the 004C row flipping from failed to passed, which accounts for passed +7. The emitted values were cross-checked against the CT series actually written to disk — SeriesInstanceUID, Modality, SeriesDate and SeriesDescription all match, and the UID equals the one in RT Referenced Series Sequence. The raise path was exercised for every required key, for an empty-string value, and for an absent `ct_series_uid`; each names the value it is missing. ## One choice that is yours I widened `DicomExporter.export`'s return value. The alternative is building 004C by re-reading the written CT datasets. Mine is the smaller diff and cannot drift from what was written, but it does mean the CT exporter's return value is now part of the RT-SS exporter's contract. **Happy to redo it the other way if you prefer that boundary** — say so and it is a quick change, no argument. Tested headlessly (the domain layer is Qt-free, so `PhantomModel` → `DicomExporter` → `RtssHdExporter` runs without a display). Nothing in the GUI path was touched. --- Written by **River** (`river-2fc1630e`), collaborating with @sjswerdloff and posting through his account. The commit on the branch carries `river-2fc1630e` as author.
HDSS requires (3006,004C) on the Structure Set module and the exporter never
wrote it, so every RT-SS this tool produced was non-conformant on that row.

Why it is required, since the printed code invites the opposite reading: the
supplement's attribute table (Rev 1.0 PC, p.31) prints R+* against 004C, and
by the document's own key -- Table 7.1.2-2 and section 7.1.2 -- R+ means the
attribute SHALL be present, while the trailing * is only a display exemption
("If a requirement lists *, then that attribute is not required to be
displayed"). It is not a condition. The attribute description carries no
"Required if" clause either, and 004C appears on exactly one page of the
supplement, so no prose elsewhere narrows it.

Implementation is additive and changes no signature a caller depends on:

  dicom.py         DicomExporter.export now also returns the series-level
                   values it had already applied to the CT datasets --
                   modality, series date/time/description/number. Returned
                   rather than recomputed, so the RT-SS cannot end up
                   describing a series different from the one written.

  rtss_exporter.py _build_structure_set_tags takes that dict and builds the
                   sequence. All six sub-attributes are Type 1, so a missing
                   value raises ValueError naming the key instead of emitting
                   a partial item -- an item short a Type 1 value is a defect
                   that looks, from outside, exactly like the omission this
                   replaces. ct_series_uid is checked separately because it
                   arrives by another route and would otherwise be written as
                   the literal string "None".

Passing ct_series_info=None reproduces the previous behaviour exactly, so
any other caller is unaffected.

Verified against a generated phantom rather than by inspection:

  before   839 passed, 1 failed  -- (3006,004C) SHALL be present but is missing
  after    846 passed, 0 failed  -- conforms to HDSS (6 of 6 ROIs HD)

Row total moves 866 -> 872: six new sub-attribute rows, and the 004C row
itself flips from failed to passed, which accounts for passed +7.

The emitted values were cross-checked against the CT series actually written
to disk -- SeriesInstanceUID, Modality, SeriesDate and SeriesDescription all
match, and the UID equals the one in RT Referenced Series Sequence. The
raise-path was exercised for every required key, for an empty-string value,
and for an absent ct_series_uid; each names the value it is missing.
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
IHE-RO-Tooling/virtual-ct!2
No description provided.