DICOM de-identification requirements-to-tests matrix#

Generated from the PyMedPhys requirements register pymedphys/_dicom/deidentify/requirements.toml, which records each paragraph of DICOM PS3.15 Annex E (2026d) that contains “shall” and each best practice of the MIDI Task Group report. PS3.15 text is reproduced verbatim from DICOM PS3.15 2026d, © NEMA. MIDI report: Clunie DA et al., Report of the Medical Image De-Identification (MIDI) Task Group: Best Practices and Recommendations, 7 February 2025, arXiv:2303.10473.

No test results were given, so the matrix lists the tests that trace each requirement without their outcomes.

Summary#

Status

PS3.15

MIDI

Total

planned

0

0

0

partial

0

3

3

implemented

30

11

41

out-of-scope

1

2

3

not-applicable

4

2

6

Total

35

18

53

Requirements#

Requirement

Status

Milestone

Decisions

Tests

PS3.15-E.1.1-01

implemented

D-001, D-003, D-005, D-011, D-020, D-021

40

PS3.15-E.1.1-02

implemented

D-007, D-011, D-022, D-023

34

PS3.15-E.1.1-03

out-of-scope

D-013

0

PS3.15-E.1.1-04

implemented

D-003, D-004

19

PS3.15-E.1.1-05

implemented

D-012, D-024

27

PS3.15-E.1.1-06

implemented

D-002, D-025, D-027

10

PS3.15-E.1.1-07

not-applicable

0

PS3.15-E.1.1-08

implemented

D-016

4

PS3.15-E.1.1-09

implemented

D-001, D-007, D-009, D-011, D-020, D-021, D-022, D-028

57

PS3.15-E.1.2-01

not-applicable

D-013

0

PS3.15-E.1.2-02

not-applicable

D-013

0

PS3.15-E.1.2-03

not-applicable

D-013

0

PS3.15-E.1.3-01

implemented

D-003, D-004, D-005, D-006, D-007, D-008, D-012, D-013, D-016, D-020, D-021, D-022, D-023, D-025, D-026, D-027

103

PS3.15-E.2-01

implemented

D-012, D-031

6

PS3.15-E.3.1-01

implemented

D-015, D-033, D-037, D-048, D-055, D-056, D-059

50

PS3.15-E.3.1-02

implemented

D-012, D-015, D-048

10

PS3.15-E.3.2-01

implemented

D-015, D-016, D-017, D-033, D-046, D-050, D-051, D-052, D-054

75

PS3.15-E.3.2-02

implemented

D-012, D-015, D-046

33

PS3.15-E.3.3-01

implemented

D-041

11

PS3.15-E.3.4-01

implemented

D-001, D-010, D-047

94

PS3.15-E.3.5-01

implemented

D-009, D-022, D-036

101

PS3.15-E.3.5-02

implemented

D-009, D-036

23

PS3.15-E.3.6-01

implemented

D-012, D-023, D-042, D-043

4

PS3.15-E.3.6-02

implemented

D-004, D-006, D-007, D-034, D-039

23

PS3.15-E.3.6-03

implemented

D-012, D-039

5

PS3.15-E.3.6-04

implemented

D-006, D-007, D-039

2

PS3.15-E.3.7-01

implemented

D-009, D-011, D-012, D-022, D-034, D-038, D-042

3

PS3.15-E.3.7-02

implemented

D-009, D-034, D-038

13

PS3.15-E.3.8-01

implemented

D-003, D-007, D-022, D-034, D-042

5

PS3.15-E.3.9-01

implemented

D-003, D-040, D-042

16

PS3.15-E.3.10-01

implemented

D-001, D-028, D-035

78

PS3.15-E.3.10-02

implemented

D-028

9

PS3.15-E.3.10-03

implemented

D-028, D-035

12

PS3.15-E.3.10-04

implemented

D-035

2

PS3.15-E.3.11-01

implemented

D-011, D-020, D-022, D-042, D-044

7

MIDI-BP-01

implemented

D-001, D-015, D-016, D-017, D-022, D-023, D-027, D-032, D-042, D-053

162

MIDI-BP-02

not-applicable

0

MIDI-BP-03

implemented

D-001, D-003, D-005, D-008, D-011, D-020, D-021, D-022, D-026, D-031

177

MIDI-BP-04

partial

M4

D-007, D-017, D-022, D-034, D-036, D-042, D-053

6

MIDI-BP-05

implemented

D-001, D-016, D-017, D-022, D-053

32

MIDI-BP-06

implemented

D-001, D-010, D-022, D-023

57

MIDI-BP-07

implemented

D-017, D-053

20

MIDI-BP-08

not-applicable

0

MIDI-BP-09

out-of-scope

D-017

0

MIDI-BP-10

implemented

D-015, D-017

21

MIDI-BP-11

implemented

D-009, D-017, D-022, D-027

27

MIDI-BP-12

partial

M4

D-004, D-006, D-007, D-039

22

MIDI-BP-13

implemented

D-001, D-028, D-035

10

MIDI-BP-14

implemented

D-010, D-021, D-022, D-027

17

MIDI-BP-15

partial

M4

D-015, D-017, D-046, D-050, D-053

57

MIDI-BP-16

out-of-scope

D-010

0

MIDI-BP-17

implemented

D-016, D-017, D-026, D-027

72

MIDI-BP-18

implemented

D-009, D-011, D-012, D-016, D-024, D-026, D-027, D-029

107

Exclusions#

Requirement

Status

Reason

PS3.15-E.1.1-03

out-of-scope

The engine does not write the Encrypted Attributes Sequence, and removes any that its input carries (D-013).

PS3.15-E.1.1-07

not-applicable

The engine reads and writes DICOM files, not DICOM Real-Time Video.

PS3.15-E.1.2-01

not-applicable

PyMedPhys does not claim conformance as a re-identifier, and its output carries no Encrypted Attributes Sequence (D-013).

PS3.15-E.1.2-02

not-applicable

PyMedPhys does not claim conformance as a re-identifier, and its output carries no Encrypted Attributes Sequence (D-013).

PS3.15-E.1.2-03

not-applicable

PyMedPhys does not claim conformance as a re-identifier, and its output carries no Encrypted Attributes Sequence (D-013).

MIDI-BP-02

not-applicable

A decision for whoever releases the data. The engine’s Options and presets are the means it offers.

MIDI-BP-08

not-applicable

The engine processes DICOM only.

MIDI-BP-09

out-of-scope

Pseudonymising accompanying spreadsheets with the same key is later work. The statistical assessment already accounts for earlier linked releases (D-017).

MIDI-BP-16

out-of-scope

Photographic images are outside the supported objects and are sequestered (D-010).

Remaining work#

Requirement

Status

Milestone

Note

MIDI-BP-04

partial

M4

Under every option, an attribute that no rule covers is kept where its VR is CS, AT, or numeric, so acquisition settings and characteristics, such as KVP, Image Type, and Acquisition Number, are kept as read (design Architecture). Under Clean Descriptors, a technique descriptor such as Protocol Name or Series Description is kept where every word of it is in the reviewed technique vocabulary and it echoes no identifier (D-036). The Retain Device Identity Option keeps machine identity, such as Station Name and Device Serial Number, as Table E.1-1 gives it, replacing each AE title by a keyed token (D-034), and a removed AE title or private value found in a text attribute that the Option keeps, such as an AE title equal to the Station Name, does not send the file to review (D-042). tps-import retains device identity, and modifies the device dates and times that the Option would keep by their role under Modified Dates (D-007). public-release omits it whatever an assessment of the collection finds, as the maintainer decided on 24 October 2026, because device serial numbers and machine names can identify an institution and, with dates, individual treatments (D-017). A public-release run is ready for release only where its statistical gate passes with a recorded external assessment of the collection, and the record cannot be made without a statement of whether keeping machine identity, such as Station Name and Device Serial Number, would raise the risk above the threshold (D-017, D-053); the engine records that statement and does not interpret it, so whoever reviews the record sees whether the omission was needed. Remaining: a policy that keeps machine identity in a public release, released only where an assessment of its own output, with machine identity among the matching variables, finds the risk within the threshold, which public-release deliberately is not (D-017) (M4).

MIDI-BP-12

partial

M4

Offsets are whole weeks, derived from the key and the subject’s identity, and every date, time, and datetime attribute has a reviewed role that decides whether it moves with the subject. Under Retain Longitudinal Temporal Information with Modified Dates, a cleaner of the whole instance moves every subject-event and radiation-source date back by the subject’s offset, the same in every instance of the subject in a run, so date-only intervals, weekdays, local times of day, and decay intervals at a common UTC offset are preserved, and replaces device and other dates with dummy values (D-039, PS3.15-E.3.6-02). Intervals between instances with different UTC offsets change by the difference between those offsets (D-007). Times of day are kept, not replaced, since whole weeks keep them and a time alone identifies no one (D-006); only the date of a datetime moves. Each subject’s offset is the one its profile records (D-004). Under a run-scoped key, the profile is the derivation, so offsets are consistent across a subject’s studies within a run but not between separate runs. Under a project key, the subject profile store records the offset at first export and every later run reads it back, so studies de-identified in separate runs keep their intervals; the store is saved before the release is published, so a store that cannot be saved publishes nothing. Project keys are refused until PROJECT_KEYS_ENABLED is set, so no run can use them yet, since the first supported release uses a run-scoped key. Under every preset, two runs of the synthetic collection under one project key and its store, the second holding references to the first’s instances, two of them again, and a prior and a follow-up study of the subject, give the subject one Patient ID and Patient’s Name and each source UID one replacement, so that the second run’s retained references resolve to the first’s instances. Under Modified Dates, every subject-event date moves by one offset, so the other studies keep their intervals; under tps-import, the runs also keep the birth date that the store records. Remaining: enable project keys (PROJECT_KEYS_ENABLED) in the release PR (M4).

MIDI-BP-15

partial

M4

Recognizable Visual Features of YES, an RT Structure Set’s contours of an ROI whose interpreted type or name is EXTERNAL, BODY, or SKIN, the patient’s outline, or BOLUS or FIXATION, which lie on it, every CT, MR, PET, and RT Dose volume, single-frame or enhanced, whatever its slice spacing, every CT, MR, and PET localizer, every RT Image, Enhanced RT Image, and Enhanced Continuous RT Image, whatever its gantry angle, which can show the face in profile, every Nuclear Medicine image, a planar view, a tomographic acquisition’s projections, or a volume reconstructed from them, whatever its Image Type, every Segmentation, whose skin or body segment traces the face, and every Parametric Map, whose map of the head shows its outline, are reported as indicators of a reconstructable face, and a volume, localizer, RT Image, Enhanced RT Image, Enhanced Continuous RT Image, Nuclear Medicine image, Segmentation, or Parametric Map that names a region of the head or neck, a Segmentation also by a segment’s anatomy or property type, as showing it. Each run lists each instance’s indicators and each series’ indicators, assessed over its released and held instances, in its QC pack for review. The QC pack renders the skin surface of each volume that it projects, whose slices may be the frames of multi-frame CT, MR, and PET instances placed by their functional groups, a volume for each stack and time point, and for each phase of slices that repeat their positions, parted by the time point or acquisition that each names,whose slices may lie off the patient’s axes, as with a gantry tilt, a shear, or an oblique orientation, sampled to voxels along the patient’s axes with every slice drawn, and the 5% isodose surface of each RT Dose grid of two or more frames, from the front, both obliques, both profiles, and behind, on voxels of 2 mm or its own spacing however long it is, so that the reviewer sees the face that a recognition system would; a volume more than 2,000 mm long or 1,000 mm across is not rendered, and is held beyond its cine strip. Every preview draws padding as one black, or as no patient, so an instance with a frame whose padding holds more than one stored value is held. A dose render that shows no face is inconclusive, since lower doses can keep an outline that the 5% isodose misses. It renders each RT Structure Set’s contours as written from the same directions, each frame’s ROIs united and each ROI alone, drawn with the surface of a volume in their frame, so that the reviewer sees a face that an outline traces, and each BINARY or LABELMAP Segmentation’s segments as written likewise, each frame placed by its functional groups; a FRACTIONAL Segmentation, or one that a sheet cannot place or show, is held (D-017). A public-release run is ready for release only where every instance that it releases that can show a face, one with pixel data or contours, is in a series that the reviewer marked as showing no face, on the pixel data and contours that the run writes, or where its statistical gate passes with an external assessment that covered faces reconstructed from the images, which D-017 accepts as the risk assessment reference that unblocks facial information; otherwise its pixel and face review gate fails (D-017, D-053). The public-release preset selects the Clean Recognizable Visual Features Option, so its run removes the face block from each CT series in which it finds one, the remedy that the recommendation names, and holds every instance that can show a face unless a reviewer marked its series as showing none, on the pixel data and contours that the run writes; under the preset, an assessment of facial risk releases no face (D-017, D-046, D-050). A reviewer’s verdict that the renders show no face stands in for a quantified facial risk, as D-017 decides; the engine computes no facial re-identification probability itself. Remaining: a collection-specific quantified analysis of the facial re-identification risk wherever a series can show a face, which a reviewer’s verdict does not supply (M4).

Details#

PS3.15-E.1.1-01#

Source: PS3.15 E.1.1, paragraph. Status: implemented.

Each Attribute to be protected shall then either be removed from the Data Set, or have its value replaced by a different "replacement value" that does not allow identification of the patient.

Decisions: D-001, D-003, D-005, D-011, D-020, D-021.

Implementation:

  • _dicom/deidentify/dummy_values.py

  • _dicom/deidentify/edits.py

  • _dicom/deidentify/instance_transform.py

  • _dicom/deidentify/pseudonyms.py

  • _dicom/deidentify/walker.py

Tests:

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_no_published_file_holds_a_marker

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_a_kept_marker_anywhere_but_its_place_is_found

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_no_released_file_keeps_a_numeric_marker_at_its_place

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_each_kept_marker_is_kept_at_its_place

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_each_sequence_of_content_items_a_run_keeps_keeps_its_items

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_instances_keep_their_reviewed_dummy_values

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_each_interlock_users_institution_code_sequence_is_released_as_reviewed

  • tests/dicom/test_deidentify_dummy_values.py::test_z_writes_a_zero_length_value

  • tests/dicom/test_deidentify_dummy_values.py::test_d_writes_its_vrs_constant

  • tests/dicom/test_deidentify_dummy_values.py::test_d_writes_the_second_constant_where_the_source_equals_the_first

  • tests/dicom/test_deidentify_dummy_values.py::test_different_source_values_give_the_same_dummy_value

  • tests/dicom/test_deidentify_dummy_values.py::test_ui_takes_the_keyed_replacement

  • tests/dicom/test_deidentify_dummy_values.py::test_a_vr_without_a_generic_dummy_value_is_refused

  • tests/dicom/test_deidentify_dummy_values.py::test_d_on_person_identification_code_sequence_writes_one_item

  • tests/dicom/test_deidentify_dummy_values.py::test_d_on_person_identification_code_sequence_takes_the_second_constants

  • tests/dicom/test_deidentify_dummy_values.py::test_d_on_an_interlock_users_institution_code_sequence_writes_one_item

  • tests/dicom/test_deidentify_dummy_values.py::test_d_on_an_interlock_users_institution_code_sequence_takes_the_second_constants

  • tests/dicom/test_deidentify_dummy_values.py::test_d_on_institution_code_sequence_elsewhere_has_no_dummy_value

  • tests/dicom/test_deidentify_dummy_values.py::test_d_on_any_other_sequence_is_refused

  • tests/dicom/test_deidentify_dummy_values.py::test_d_on_referenced_performed_procedure_step_sequence_replaces_each_item

  • tests/dicom/test_deidentify_dummy_values.py::test_a_procedure_step_sequence_without_a_uid_to_replace_is_refused

  • tests/dicom/test_deidentify_edits.py::test_d_writes_the_dummy_value_and_the_second_where_the_source_equals_it

  • tests/dicom/test_deidentify_edits.py::test_an_empty_value_is_edited_and_collected_whatever_the_transfer_syntax

  • tests/dicom/test_deidentify_edits.py::test_patients_name_and_id_take_the_pseudonyms_of_a_given_identity

  • tests/dicom/test_deidentify_edits.py::test_d_replaces_person_identification_code_sequence_with_its_dummy_item

  • tests/dicom/test_deidentify_instance_transform.py::test_each_instance_of_a_collection_is_written_and_named_by_its_replacements

  • tests/dicom/test_deidentify_instance_transform.py::test_d_writes_the_institution_item_of_the_user_who_resolved_an_interlock

  • tests/dicom/test_deidentify_instance_transform.py::test_an_override_operators_institution_code_sequence_goes_with_its_sequence

  • tests/dicom/test_deidentify_instance_transform.py::test_d_writes_the_person_identification_item

  • tests/dicom/test_deidentify_instance_transform.py::test_d_writes_the_person_identification_item_of_an_interlocks_user

  • tests/dicom/test_deidentify_instance_transform.py::test_attributes_five_items_deep_in_an_enhanced_rt_image_take_their_actions

  • tests/dicom/test_deidentify_instance_transform.py::test_a_radiation_generation_mode_nested_once_is_not_kept

  • tests/dicom/test_deidentify_instance_transform.py::test_conceptual_volume_uids_five_items_deep_in_a_delivery_instruction_are_replaced

  • tests/dicom/test_deidentify_pseudonyms.py::test_a_pseudonym_follows_the_specified_derivation

  • tests/dicom/test_deidentify_pseudonyms.py::test_a_pseudonym_is_valid_for_its_vr_and_conspicuous

  • tests/dicom/test_deidentify_pseudonyms.py::test_different_subjects_give_different_pseudonyms

  • tests/dicom/test_deidentify_pseudonyms.py::test_the_same_identifier_from_another_issuer_is_another_subject

  • tests/dicom/test_deidentify_pseudonyms.py::test_a_pseudonym_does_not_contain_the_identifier

  • tests/dicom/test_deidentify_verifying_observer.py::test_d_keeps_each_item_with_only_its_reviewed_attributes

  • tests/dicom/test_deidentify_verifying_observer.py::test_a_verified_report_is_written_with_one_dummy_verifier_per_item

Note: Patient ID and Patient’s Name have keyed replacement values that keep subjects separate and do not contain the source identifier. Z writes a zero-length value, and D a conspicuous constant for each VR that does not depend on the source value, or a second constant where the source value equals the first, so the replacement always differs from it; a UI value takes its keyed replacement, and D on an attribute of any other VR is refused until a reviewed rule covers it. Reviewed rules give Person Identification Code Sequence one item with a dummy code, Referenced Performed Procedure Step Sequence one item for each source item with its UID replaced, and ICC Profile the source profile cleaned of everything but its colorimetry, or a fixed sRGB profile where the source cannot be cleaned, and keep each item of Verifying Observer Sequence with only the four attributes that PS3.3 defines there, each under its own rule (D-021). The run’s instance transform removes or replaces each Attribute to be protected in every instance it writes, and an end-to-end test of basic and basic-clean-descriptors over the synthetic collection finds none of the collection’s markers in the published files (MIDI-BP-01).

PS3.15-E.1.1-02#

Source: PS3.15 E.1.1, paragraph. Status: implemented.

Each Attribute specified to be retained shall be retained. At the discretion of the de-identifier, Attributes may be added to the Data Set to be protected.

Decisions: D-007, D-011, D-022, D-023.

Implementation:

  • _dicom/deidentify/actions.py

  • _dicom/deidentify/instance_transform.py

  • _dicom/deidentify/policy.py

  • _dicom/deidentify/preservation.py

  • _dicom/deidentify/preserving_writer.py

  • _dicom/deidentify/walker.py

Tests:

  • tests/dicom/test_deidentify_actions.py::test_an_option_overrides_the_profile

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_released_images_keep_their_transfer_syntax_and_pixels

  • tests/dicom/test_deidentify_instance_transform.py::test_the_output_is_written_from_the_source_and_verified_against_it

  • tests/dicom/test_deidentify_instance_transform.py::test_a_compressed_instance_keeps_its_pixel_data_byte_for_byte

  • tests/dicom/test_deidentify_instance_transform.py::test_a_write_the_writer_refuses_is_sequestered_by_its_reason

  • tests/dicom/test_deidentify_instance_transform.py::test_output_that_does_not_preserve_its_source_is_sequestered

  • tests/dicom/test_deidentify_policy.py::test_tps_import_keeps_device_identity_and_patient_characteristics

  • tests/dicom/test_deidentify_policy.py::test_a_custom_option_set_is_validated_and_not_yet_enabled

  • tests/dicom/test_deidentify_policy.py::test_the_tps_import_options_are_rejected_without_the_preset

  • tests/dicom/test_deidentify_policy.py::test_a_conflict_that_a_new_edition_adds_is_rejected

  • tests/dicom/test_deidentify_preservation.py::test_a_copy_that_keeps_every_element_is_preserved

  • tests/dicom/test_deidentify_preservation.py::test_another_transfer_syntax_is_refused

  • tests/dicom/test_deidentify_preservation.py::test_each_element_must_correspond_to_its_plan

  • tests/dicom/test_deidentify_preservation.py::test_a_kept_value_that_decodes_the_same_from_other_bytes_is_refused

  • tests/dicom/test_deidentify_preservation.py::test_an_ambiguous_vr_resolved_again_with_the_same_bytes_is_refused

  • tests/dicom/test_deidentify_preservation.py::test_a_kept_sequence_with_another_number_of_items_is_refused

  • tests/dicom/test_deidentify_preservation.py::test_a_kept_value_written_with_another_length_encoding_is_refused

  • tests/dicom/test_deidentify_preservation.py::test_encapsulated_pixel_data_with_the_same_bytes_is_preserved

  • tests/dicom/test_deidentify_preservation.py::test_encapsulated_pixel_data_with_other_bytes_is_refused

  • tests/dicom/test_deidentify_preservation.py::test_kept_text_in_another_character_set_is_refused

  • tests/dicom/test_deidentify_preservation.py::test_a_kept_element_in_another_context_is_refused

  • tests/dicom/test_deidentify_preservation.py::test_an_empty_specific_character_set_is_the_default_as_an_absent_one_is

  • tests/dicom/test_deidentify_preserving_writer.py::test_a_written_plan_preserves_what_it_keeps

  • tests/dicom/test_deidentify_preserving_writer.py::test_kept_text_keeps_its_inherited_bytes_and_new_text_takes_them

  • tests/dicom/test_deidentify_preserving_writer.py::test_a_ct_voi_lut_descriptor_keeps_its_vr_and_bytes

  • tests/dicom/test_deidentify_preserving_writer.py::test_pydicom_resolves_the_descriptor_again_where_this_writer_does_not

  • tests/dicom/test_deidentify_preserving_writer.py::test_an_introduced_sequence_is_written_with_its_items

  • tests/dicom/test_deidentify_preserving_writer.py::test_an_element_without_a_plan_is_refused

  • tests/dicom/test_deidentify_preserving_writer.py::test_a_kept_value_of_fragments_is_refused

  • tests/dicom/test_deidentify_preserving_writer.py::test_encapsulated_pixel_data_is_copied_and_verified_byte_for_byte

  • tests/dicom/test_deidentify_preserving_writer.py::test_an_edit_inside_a_un_sequence_keeps_it_un_with_implicit_items

  • tests/dicom/test_deidentify_preserving_writer.py::test_a_kept_element_inside_a_removed_or_replaced_sequence_is_refused

  • tests/dicom/test_deidentify_preserving_writer.py::test_a_replaced_character_set_under_kept_text_is_refused

  • tests/dicom/test_deidentify_preserving_writer.py::test_a_data_set_kept_whole_is_copied_byte_for_byte

Note: Reviewed rules add the text attributes, dates, and times that Table E.1-1 omits to those protected (D-022, D-023). A policy keeps (K) each attribute that its selected options retain, and an option set that gives an attribute to be retained another action as well is rejected. The one exception is tps-import, which modifies eleven device and beam hold dates and times that Retain Device Identity would keep and claims no PS3.15 conformance (D-007). A written file can be verified against the plan for its instance: every source element is kept, changed, or removed; nothing else is written; the file has its source’s transfer syntax; every change is written; and each kept element has the same VR as written, length form, and Value Field bytes as in the source, encapsulated Pixel Data the same bytes as a whole, a kept private element the same Private Creator, and kept text the same Specific Character Set in force, so a VR resolved again or text decoded and encoded again is refused. A data set is written from its source’s bytes: each kept element is copied byte for byte, including a sequence with no change in it; a kept sequence with a change in it is written with undefined lengths, keeping every item; and only replacements and introduced elements are encoded, each refused unless its text can be written in the character set in force at its place and its VR is the one written, so pydicom never re-encodes a kept value or resolves its VR again. A plan that keeps an element inside a removed or replaced sequence, keeps a group length, or changes the character set of kept text is refused. The run’s instance transform plans each instance’s data set from its policy, writes it this way, verifies it, and sequesters an instance whose output fails verification.

PS3.15-E.1.1-03#

Source: PS3.15 E.1.1, paragraph. Status: out-of-scope.

If used, all instances of the Encrypted Attributes Data Set shall be encoded with a DICOM Transfer Syntax, encrypted, and stored in the Data Set to be protected as an Item of the Encrypted Attributes Sequence (0400,0500). The encryption shall be done using RSA [RFC 2313] for the key transport of the content-encryption keys. A de-identifier conforming to this security Profile may use either AES or Triple-DES for content-encryption. The AES key length may be any length allowed by the RFCs. The Triple-DES key length is 168 bits as defined by [ANSI X9.52]. Encoding shall be performed according to the specifications for RSA Key Transport and Triple DES Content Encryption in [RFC 3370] and for AES Content Encryption in [RFC 3565].

Decisions: D-013.

Note: The engine does not write the Encrypted Attributes Sequence, and removes any that its input carries (D-013).

PS3.15-E.1.1-04#

Source: PS3.15 E.1.1, paragraph. Status: implemented.

No requirements on the size of the asymmetric key pairs used for RSA key transport are defined in this confidentiality scheme. Implementations claiming conformance to the Basic Application Level Confidentiality Profile as a de-identifier shall always protect (e.g., encrypt and replace) the SOP Instance UID (0008,0018) Attribute as well as all references to other SOP Instances, whether contained in the main Data Set or embedded in an Item of a Sequence of Items, that could potentially be used by unauthorized entities to identify the patient.

Decisions: D-003, D-004.

Implementation:

  • _dicom/deidentify/edits.py

  • _dicom/deidentify/instance_transform.py

  • _dicom/deidentify/keys.py

  • _dicom/deidentify/uid_roles.py

  • _dicom/deidentify/uids.py

Tests:

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_no_published_file_holds_a_marker

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_each_registration_ties_its_images_to_the_frame_as_written

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_the_rt_image_names_its_plan_as_written

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_the_enhanced_rt_images_name_their_plan_as_written

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_the_rt_ion_plan_and_its_record_name_what_they_reference_as_written

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_the_brachy_and_summary_records_name_what_they_reference_as_written

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_the_delivery_instructions_name_what_they_reference_as_written

  • tests/dicom/test_deidentify_edits.py::test_uids_are_replaced_by_their_keyed_replacement_unless_registered

  • tests/dicom/test_deidentify_edits.py::test_a_sequence_kept_under_u_by_its_iod_is_kept_and_its_uids_replaced

  • tests/dicom/test_deidentify_instance_transform.py::test_references_between_instances_follow_their_replacements

  • tests/dicom/test_deidentify_keys.py::test_derivations_are_pinned

  • tests/dicom/test_deidentify_retain_uids.py::test_an_instance_keeps_its_uids_and_references_and_claims_the_option

  • tests/dicom/test_deidentify_uid_roles.py::test_every_ui_attribute_in_the_dictionary_has_exactly_one_role

  • tests/dicom/test_deidentify_uid_roles.py::test_every_uid_table_e1_1_replaces_is_an_instance_uid

  • tests/dicom/test_deidentify_uid_roles.py::test_an_instance_uid_is_replaced

  • tests/dicom/test_deidentify_uids.py::test_a_replacement_follows_the_specified_derivation

  • tests/dicom/test_deidentify_uids.py::test_a_replacement_is_a_valid_uid_from_a_version_5_uuid

  • tests/dicom/test_deidentify_uids.py::test_a_replacement_depends_only_on_the_key_and_the_unpadded_uid

  • tests/dicom/test_deidentify_uids.py::test_different_uids_give_different_replacements

Note: Replacement UIDs are derived from the key, so every occurrence of a UID is replaced consistently, and every UI attribute has a reviewed role. The run’s instance transform replaces each SOP Instance UID and each reference to another SOP Instance in a data set, at the top level and in sequences, with the same replacement in every instance of the run. Under the Retain UIDs Option, the SOP Instance UID and the references that Table E.1-1 gives K under it are kept instead (PS3.15-E.3.9-01, D-040).

PS3.15-E.1.1-05#

Source: PS3.15 E.1.1, paragraph. Status: implemented.

The Attribute Patient Identity Removed (0012,0062) shall be replaced or added to the Data Set with a value of YES. Additionally, one or more codes from CID 7050 “De-identification Method” corresponding to the Profile and Options used shall be added to De-identification Method Code Sequence (0012,0064), and/or a text string describing the method used shall be inserted in or added to De-identification Method (0012,0063).

Decisions: D-012, D-024.

Implementation:

  • _dicom/deidentify/instance_transform.py

  • _dicom/deidentify/markers.py

  • _dicom/deidentify/method_digest.py

Tests:

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_every_released_instance_claims_the_policys_options

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_every_released_instance_carries_the_profiles_markers

  • tests/dicom/test_deidentify_instance_transform.py::test_each_output_carries_the_de_identification_markers

  • tests/dicom/test_deidentify_instance_transform.py::test_markers_already_present_are_updated_as_ps3_15_says

  • tests/dicom/test_deidentify_markers.py::test_each_preset_adds_exactly_its_markers

  • tests/dicom/test_deidentify_markers.py::test_basic_clean_descriptors_claims_clean_descriptors_only_where_satisfied

  • tests/dicom/test_deidentify_markers.py::test_a_custom_option_set_is_named_as_one_and_claims_its_satisfied_options

  • tests/dicom/test_deidentify_markers.py::test_tps_import_claims_no_ps3_15_conformance_and_adds_no_codes

  • tests/dicom/test_deidentify_markers.py::test_patient_identity_removed_is_never_no

  • tests/dicom/test_deidentify_markers.py::test_the_first_method_value_is_exactly_the_method_digest

  • tests/dicom/test_deidentify_markers.py::test_every_code_is_a_row_of_the_pinned_context_groups

  • tests/dicom/test_deidentify_markers.py::test_every_marker_value_is_valid_for_its_vr_and_vm

  • tests/dicom/test_deidentify_markers.py::test_existing_markers_and_contributing_equipment_items_are_kept

  • tests/dicom/test_deidentify_markers.py::test_the_same_release_and_policy_applied_twice_gives_the_markers_of_one_run

  • tests/dicom/test_deidentify_markers.py::test_a_code_already_present_is_not_added_again_and_new_codes_follow

  • tests/dicom/test_deidentify_markers.py::test_marked_output_reads_back_strictly_and_gains_nothing_when_marked_again

  • tests/dicom/test_deidentify_markers.py::test_markers_changed_after_markers_for_are_refused_before_they_are_written

  • tests/dicom/test_deidentify_markers.py::test_a_satisfied_list_that_leaves_out_a_selected_option_is_refused

  • tests/dicom/test_deidentify_markers.py::test_clean_descriptors_is_the_one_selected_option_that_can_go_unsatisfied

  • tests/dicom/test_deidentify_method_digest.py::test_the_digest_is_only_64_lowercase_hexadecimal_digits_and_fits_one_lo_value

  • tests/dicom/test_deidentify_method_digest.py::test_a_small_synthetic_input_has_the_canonical_form_written_by_hand

  • tests/dicom/test_deidentify_method_digest.py::test_the_same_inputs_always_give_the_same_digest

  • tests/dicom/test_deidentify_method_digest.py::test_any_part_of_the_policy_changes_the_digest

  • tests/dicom/test_deidentify_method_digest.py::test_python_and_library_versions_leave_the_digest_unchanged

  • tests/dicom/test_deidentify_method_digest.py::test_changing_adding_or_removing_an_engine_file_changes_the_digest

  • tests/dicom/test_deidentify_method_digest.py::test_the_reviewed_names_digest_is_an_input_that_shows_only_in_its_component

  • tests/dicom/test_deidentify_method_digest.py::test_each_descriptor_digest_is_an_input_that_shows_only_in_its_component

Note: The markers of an instance are computed from its policy, the de-identification method digest, and the options it satisfies, and added to a copy of its data set (D-012). Patient Identity Removed is set to YES. De-identification Method keeps its values and, unless they already include the pair as two consecutive values, gains two: the method digest, 64 lowercase hexadecimal digits, which fit one LO value, computed from the policy and everything the engine could apply, including its own source and rule files and the source files of the PyMedPhys modules it imports, whether or not the policy’s options use them, and then a readable value naming PyMedPhys, its version, the PS3.15 edition, and the preset. The digest identifies the policy and the PyMedPhys implementation and resources that apply it, not the runtime environment, which Software Versions records instead, so upgrading Python, pydicom, or tomlkit leaves it unchanged (D-012, D-024). The digest is PyMedPhys’s own provenance, added beside the readable description that this paragraph allows; PS3.15 does not require it. De-identification Method Code Sequence keeps its items and gains the CID 7050 codes of the Basic Profile and of each satisfied option that it does not already hold. Under a policy that claims conformance, every selected option must be satisfied except Clean Descriptors, so that the codes name each option whose actions the instance carries. tps-import, which claims no conformance, adds no code and says so in its readable value. The run’s instance transform adds the markers to each instance it de-identifies, then writes the instance and verifies what it preserves. An end-to-end test runs basic and basic-clean-descriptors over the synthetic collection and finds in every released instance Patient Identity Removed YES, the method digest as the first value of De-identification Method, and the code of the Basic Profile, with that of Clean Descriptors under basic-clean-descriptors and not under basic (MIDI-BP-01).

PS3.15-E.1.1-06#

Source: PS3.15 E.1.1, paragraph. Status: implemented.

If the Data Set being de-identified is being stored within a DICOM File, then the File Meta Information including the 128 byte preamble, if present, shall be replaced with a description of the de-identifying application. Otherwise, there is a risk that identity information may leak through unmodified File Meta Information or preamble. See PS3.10. This includes information regarding Application Entity Titles, Presentation Addresses, implementation information, and private information.

Decisions: D-002, D-025, D-027.

Implementation:

  • _dicom/deidentify/file_meta.py

  • _dicom/deidentify/instance_transform.py

  • _dicom/deidentify/preserving_writer.py

Tests:

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_no_published_file_holds_a_marker

  • tests/dicom/test_deidentify_file_meta.py::test_the_implementation_class_uid_is_the_first_uid_of_the_reserved_arc

  • tests/dicom/test_deidentify_file_meta.py::test_the_implementation_version_name_holds_the_version_where_it_fits

  • tests/dicom/test_deidentify_file_meta.py::test_the_file_meta_information_holds_exactly_the_seven_elements_in_order

  • tests/dicom/test_deidentify_file_meta.py::test_the_group_length_counts_the_bytes_of_the_elements_after_it

  • tests/dicom/test_deidentify_file_meta.py::test_a_written_file_has_a_zero_preamble_and_exactly_the_built_elements

  • tests/dicom/test_deidentify_file_meta.py::test_no_source_file_meta_element_or_preamble_is_written

  • tests/dicom/test_deidentify_instance_transform.py::test_the_source_preamble_and_file_meta_are_not_written

  • tests/dicom/test_deidentify_preserving_writer.py::test_the_file_has_new_file_meta_information

  • tests/dicom/test_deidentify_preserving_writer.py::test_an_element_that_does_not_belong_in_a_data_set_is_refused

Note: The File Meta Information that replaces the source file’s describes the de-identifier by a fixed Implementation Class UID under the PyMedPhys root and an Implementation Version Name, and holds no Application Entity Title, Presentation Address, or private element. A data set is written with it and a zeroed preamble, whatever File Meta Information and preamble the source file held. The run’s instance transform writes each de-identified instance this way.

PS3.15-E.1.1-07#

Source: PS3.15 E.1.1, paragraph. Status: not-applicable.

If the Data Set being de-identified is being communicated by DICOM Real-Time Video, then the File Meta Information including the 128 byte preamble, if present, shall be replaced with a description of the de-identifying application. Otherwise, there is a risk that identity information may leak through unmodified File Meta Information or preamble. See PS3.22. This includes information regarding Application Entity Titles, Presentation Addresses, implementation information, and private information.

Note: The engine reads and writes DICOM files, not DICOM Real-Time Video.

PS3.15-E.1.1-08#

Source: PS3.15 E.1.1, paragraph. Status: implemented.

All Data Elements with a Group Number of 0004 shall be removed from any SOP Instance or DICOM File other than a DICOMDIR File. If a DICOMDIR File is required, it shall either be created from the de-identified DICOM Files it references, or an existing DICOMDIR File shall be de-identifed according to this Profile. Any existing non-de-identified DICOMDIR File shall be removed from the File-set.

Decisions: D-016.

Implementation:

  • _dicom/deidentify/element_rules.py

  • _dicom/deidentify/instance_transform.py

  • _dicom/deidentify/run.py

  • _dicom/deidentify/run_discovery.py

  • _dicom/deidentify/run_results.py

Tests:

  • tests/dicom/test_deidentify_element_rules.py::test_each_level_decides_its_elements

  • tests/dicom/test_deidentify_element_rules.py::test_every_element_of_groups_0002_and_0004_in_a_data_set_is_removed

  • tests/dicom/test_deidentify_instance_transform.py::test_no_group_0004_element_is_written_at_any_level

  • tests/dicom/test_deidentify_run.py::test_a_dicomdir_is_never_passed_through

Note: Every element of group 0004 in a data set, at the top level or in an item, has a rule that removes it, under every policy, before any other rule applies. A source DICOMDIR is never passed through, and none is written, since the output is a plain directory tree rather than a File-set (D-016): a run refuses a source file named DICOMDIR, whose Media Storage SOP Class UID is the Media Storage Directory Storage SOP Class, or that holds a Directory Record Sequence. The run’s instance transform applies those rules to each instance it writes, so no written file holds an element of group 0004.

PS3.15-E.1.1-09#

Source: PS3.15 E.1.1, paragraph. Status: implemented.

The Attributes listed in Table E.1-1 for each Profile or Option are contained in Standard IODs, or may be contained in Standard Extended IODs. An implementation claiming conformance to the Basic Application Level Confidentiality Profile as a de-identifier shall protect or retain all instances of the Attributes listed in Table E.1-1, whether contained in the top level Data Set or embedded in an Item of a Sequence of Items. The action codes in Table E.1-1a are used in Table E.1-1.

Decisions: D-001, D-007, D-009, D-011, D-020, D-021, D-022, D-028.

Implementation:

  • _dicom/deidentify/actions.py

  • _dicom/deidentify/compound_actions.py

  • _dicom/deidentify/edits.py

  • _dicom/deidentify/element_rules.py

  • _dicom/deidentify/elements.py

  • _dicom/deidentify/instance_transform.py

  • _dicom/deidentify/policy.py

  • _dicom/deidentify/sequences.py

  • _dicom/deidentify/source.py

  • _dicom/deidentify/standard.py

  • _dicom/deidentify/walker.py

Tests:

  • tests/dicom/test_deidentify_actions.py::test_the_basic_profile_alone_gives_each_attribute_its_profile_action

  • tests/dicom/test_deidentify_actions.py::test_an_option_overrides_the_profile

  • tests/dicom/test_deidentify_actions.py::test_each_attribute_has_one_action_or_a_conflict

  • tests/dicom/test_deidentify_compound_actions.py::test_each_compound_action_resolves_by_type

  • tests/dicom/test_deidentify_compound_actions.py::test_the_type_depends_on_the_enclosing_sequence

  • tests/dicom/test_deidentify_compound_actions.py::test_x_z_on_a_type_1_attribute_writes_the_dummy_value

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_each_run_releases_all_but_the_instances_it_must_withhold

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_no_published_file_holds_a_marker

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_no_released_file_keeps_a_numeric_marker_at_its_place

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_no_released_file_keeps_a_removed_sequence_without_a_marker

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_the_brachy_records_source_manufacturer_is_emptied_or_kept

  • tests/dicom/test_deidentify_element_rules.py::test_each_level_decides_its_elements

  • tests/dicom/test_deidentify_element_rules.py::test_a_sequence_given_c_takes_its_basic_profile_action

  • tests/dicom/test_deidentify_element_rules.py::test_every_dictionary_attribute_has_the_rule_of_the_first_level_covering_it

  • tests/dicom/test_deidentify_element_rules.py::test_every_element_of_group_0000_in_a_data_set_is_removed

  • tests/dicom/test_deidentify_element_rules.py::test_the_overlay_masks_match_every_repeating_group

  • tests/dicom/test_deidentify_element_rules.py::test_no_mask_matches_outside_the_repeating_groups

  • tests/dicom/test_deidentify_elements.py::test_an_implicit_vr_rt_plan_decodes_rt_assertions_with_the_pinned_vr

  • tests/dicom/test_deidentify_elements.py::test_un_in_explicit_vr_is_decoded_as_implicit_vr_with_the_pinned_vr

  • tests/dicom/test_deidentify_elements.py::test_a_vr_that_conflicts_with_the_pinned_dictionary_is_refused

  • tests/dicom/test_deidentify_elements.py::test_text_that_could_not_be_written_back_is_refused

  • tests/dicom/test_deidentify_elements.py::test_a_sequence_whose_items_cannot_be_read_is_refused

  • tests/dicom/test_deidentify_elements.py::test_a_un_sequence_that_pydicom_has_decoded_is_refused

  • tests/dicom/test_deidentify_iod_conformance.py::test_an_instance_loses_its_ethics_committee_name_with_its_approval_number

  • tests/dicom/test_deidentify_plain_actions.py::test_a_plain_d_on_an_attribute_the_iod_does_not_define_there_removes_it

  • tests/dicom/test_deidentify_plain_actions.py::test_a_plain_d_on_an_attribute_the_iod_defines_there_stays_d

  • tests/dicom/test_deidentify_plain_actions.py::test_a_plain_z_on_an_attribute_the_iod_does_not_define_there_empties_it

  • tests/dicom/test_deidentify_plain_actions.py::test_a_plain_z_on_a_type_1_attribute_writes_the_dummy_value

  • tests/dicom/test_deidentify_plain_actions.py::test_a_plain_z_on_a_type_2_or_3_attribute_stays_z

  • tests/dicom/test_deidentify_plain_actions.py::test_a_plain_x_on_an_optional_attribute_removes_it_alone

  • tests/dicom/test_deidentify_plain_actions.py::test_a_plain_x_on_a_required_attribute_removes_the_innermost_type_3_sequence

  • tests/dicom/test_deidentify_plain_actions.py::test_a_plain_x_skips_each_enclosing_sequence_that_is_required

  • tests/dicom/test_deidentify_plain_actions.py::test_a_plain_x_on_a_required_attribute_outside_a_type_3_sequence_sequesters

  • tests/dicom/test_deidentify_plain_actions.py::test_a_plain_x_on_overlay_data_removes_its_repeating_group

  • tests/dicom/test_deidentify_plain_actions.py::test_a_plain_x_on_overlay_data_in_a_conditional_overlay_module_sequesters

  • tests/dicom/test_deidentify_plain_actions.py::test_a_plain_x_on_roi_interpreter_sequence_removes_it_alone

  • tests/dicom/test_deidentify_plain_actions.py::test_each_lapsed_condition_needs_an_attribute_that_every_option_removes

  • tests/dicom/test_deidentify_plain_actions.py::test_every_plain_action_removes_clinical_trial_protocol_ethics_committee_name

  • tests/dicom/test_deidentify_plain_actions.py::test_requested_procedure_id_is_emptied_in_each_referenced_request_sequence

  • tests/dicom/test_deidentify_plain_actions.py::test_source_manufacturer_is_emptied_in_each_recorded_source_sequence

  • tests/dicom/test_deidentify_policy.py::test_the_basic_policy_gives_each_attribute_its_basic_profile_action

  • tests/dicom/test_deidentify_policy.py::test_each_preset_gives_each_attribute_one_action

  • tests/dicom/test_deidentify_private_removal.py::test_the_basic_profile_removes_every_private_attribute

  • tests/dicom/test_deidentify_private_removal.py::test_private_attributes_in_a_sequence_read_as_unknown_are_removed

  • tests/dicom/test_deidentify_private_removal.py::test_private_attributes_in_a_known_sequence_written_as_unknown_are_removed

  • tests/dicom/test_deidentify_sequences.py::test_items_are_decoded

  • tests/dicom/test_deidentify_sequences.py::test_a_sequence_nested_in_an_item_is_decoded

  • tests/dicom/test_deidentify_sequences.py::test_an_item_is_decoded_in_its_own_character_set_or_the_one_it_inherits

  • tests/dicom/test_deidentify_sequences.py::test_a_value_that_is_not_only_items_is_refused

  • tests/dicom/test_deidentify_source.py::test_a_private_value_that_reads_as_items_is_read_as_a_sequence

  • tests/dicom/test_deidentify_source.py::test_an_odd_length_is_refused_where_it_starts

  • tests/dicom/test_deidentify_source.py::test_the_same_structure_with_even_lengths_is_admitted

  • tests/dicom/test_deidentify_source.py::test_an_undefined_length_is_admitted

  • tests/dicom/test_deidentify_walker.py::test_x_z_on_a_type_1_attribute_gives_d

  • tests/dicom/test_deidentify_walker.py::test_a_plain_x_where_the_iod_requires_the_attribute_removes_a_type_3_sequence

  • tests/dicom/test_deidentify_walker.py::test_a_plain_x_where_the_iod_requires_the_attribute_and_no_sequence_sequesters

  • tests/dicom/test_deidentify_walker.py::test_removing_overlay_data_removes_every_attribute_of_its_group

Note: Each attribute that Table E.1-1 lists has an action under the Basic Profile and the selected options, an option’s action overriding the Profile’s, and an attribute to which two selected options give different actions is reported as a conflict. Policy validation rejects every conflict except those the tps-import preset resolves, so each preset’s policy gives every attribute one action. A compound action of Table E.1-1a resolves to one action from the strictest Type that the instance’s IOD gives the attribute at its place, at the top level or in a sequence, and X/Z on a Type 1 or 1C attribute gives D, the non-zero-length dummy value that Table E.1-1a allows Z. A plain Z on such an attribute gives D too, and a plain D on an attribute that the IOD does not define at that place gives X, as Note 13 after Table E.1-1a describes. A plain X removes the attribute whatever its Type; where the IOD requires the attribute at that place, the innermost enclosing sequence that the IOD makes Type 3 at its own place is removed with it, and where none encloses it, the instance is sequestered. Removing Overlay Data removes every attribute of its repeating group, and ROI Interpreter Sequence is removed alone; so is Clinical Trial Protocol Ethics Committee Name, which Table E.1-1 gives D, since its Type 1C condition needs the approval number, which the table removes under every option (D-020). Two places depart from the table’s X, as the maintainer decided: Requested Procedure ID, Type 2 in a Structured Report’s Type 1C Referenced Request Sequence, and Source Manufacturer, Type 2 in an RT Brachy Treatment Record’s Type 1 Recorded Source Sequence, are emptied there rather than sequester the instance (D-020). Each element, at the top level or in an item, is read with the VR that the pinned data dictionary gives its attribute, so that the attributes inside a sequence that pydicom does not know and reads as UN, such as an RT Plan’s RT Assertions Sequence, can be found; an element whose VR conflicts with the dictionary, or whose value or items do not decode exactly, is reported as undecodable rather than read as pydicom guesses it. Each element, at the top level or in an item, takes the rule of the first source that covers it: the engine’s own removals, Table E.1-1’s Private Attributes row, the table’s other rows, whose masked tags match only the repeating groups, the supplementary rules and UID roles, removal by Type of other text, and the rule for attributes that no rule covers (D-022); a sequence to which an option gives C takes its Basic Profile action (D-009). Every instance of the Private Attributes row is removed from a data set, at the top level and in the items of every standard sequence, including one that pydicom reads as UN (D-028). A source file is admitted only once its whole structure is read from its own bytes, with every Value Length and Item Length but the undefined length even, so that every element at the top level and in items is found. The run’s instance transform applies the resolved actions to every instance of each attribute in each instance it de-identifies, at the top level and in sequences, including the sequences and overlay groups that a plain X removes with an attribute, sequesters the instances that a plain X calls for, and removes private attributes.

PS3.15-E.1.2-01#

Source: PS3.15 E.1.2, paragraph. Status: not-applicable.

The application shall decrypt, using its recipient key, one instance of the Encrypted Content (0400,0520) Attribute within the Encrypted Attributes Sequence (0400,0500) and decode the resulting block of bytes into a DICOM Data Set using the Transfer Syntax specified in the Encrypted Content Transfer Syntax UID (0400,0510). Re-identifiers claiming conformance to this Profile shall be capable of decrypting the Encrypted Content using either AES or Triple-DES in all possible key lengths specified in this Profile.

Decisions: D-013.

Note: PyMedPhys does not claim conformance as a re-identifier, and its output carries no Encrypted Attributes Sequence (D-013).

PS3.15-E.1.2-02#

Source: PS3.15 E.1.2, paragraph. Status: not-applicable.

The application shall move all Attributes contained in the single item of the Modified Attributes Sequence (0400,0550) of the decoded Data Set into the top level Data Set, replacing "dummy value" Attributes that may be present in the top level Data Set.

Decisions: D-013.

Note: PyMedPhys does not claim conformance as a re-identifier, and its output carries no Encrypted Attributes Sequence (D-013).

PS3.15-E.1.2-03#

Source: PS3.15 E.1.2, paragraph. Status: not-applicable.

The Attribute Patient Identity Removed (0012,0062) shall be replaced or added to the Data Set with a value of NO and De-identification Method (0012,0063) and De-identification Method Code Sequence (0012,0064) shall be removed.

Decisions: D-013.

Note: PyMedPhys does not claim conformance as a re-identifier, and its output carries no Encrypted Attributes Sequence (D-013).

PS3.15-E.1.3-01#

Source: PS3.15 E.1.3, paragraph. Status: implemented.

The Conformance Statement of an application that claims conformance to the Basic Application Level Confidentiality Profile shall describe:

- which Attributes are removed during protection;
- which Attributes are replaced by dummy values and how the dummy values are generated;
- which Attributes are included in Encrypted Attributes Data Sets for later re-identification, and any pertinent details about how keys are selected for performing the encryption;
- the scope across which the application is able to ensure referential integrity of replacement values for references such as SOP Instance UID, Frame of Reference UID, etc. if multiple SOP instances are protected (e.g., across multiple Studies, consistent replacement if the same Study processed more than once, etc.);
- which Attributes and Attribute Values are inserted during protection of a SOP instance;
- which Transfer Syntaxes are supported for encoding/decoding of the Encrypted Attributes Data Set;
- which Options are supported;
- any additional restrictions (e. g. key sizes for public keys).

Decisions: D-003, D-004, D-005, D-006, D-007, D-008, D-012, D-013, D-016, D-020, D-021, D-022, D-023, D-025, D-026, D-027.

Implementation:

  • _dicom/deidentify/conformance.py

  • _dicom/deidentify/conformance_markdown.py

  • _dicom/deidentify/conformance_values.py

  • _dicom/deidentify/run_report.py

Tests:

  • tests/dicom/test_deidentify_conformance.py::test_every_table_row_and_supplementary_rule_is_listed_with_the_policys_action

  • tests/dicom/test_deidentify_conformance.py::test_every_ui_attribute_that_the_table_omits_is_listed_by_its_role

  • tests/dicom/test_deidentify_conformance.py::test_compound_actions_are_resolved_at_every_place_a_supported_iod_defines_them

  • tests/dicom/test_deidentify_conformance.py::test_institution_name_is_resolved_by_its_type_at_each_place

  • tests/dicom/test_deidentify_conformance.py::test_a_d_without_a_dummy_value_is_listed_as_sequestering

  • tests/dicom/test_deidentify_conformance.py::test_an_interlock_users_institution_code_sequence_is_listed_as_d

  • tests/dicom/test_deidentify_conformance.py::test_d_on_institution_code_sequence_elsewhere_would_sequester

  • tests/dicom/test_deidentify_conformance.py::test_x_z_on_a_type_1_attribute_is_described_as_the_dummy_value

  • tests/dicom/test_deidentify_conformance.py::test_the_dummy_values_are_those_that_d_writes

  • tests/dicom/test_deidentify_conformance.py::test_the_key_and_the_scope_of_referential_integrity_are_described

  • tests/dicom/test_deidentify_conformance.py::test_no_attribute_is_encrypted_for_later_reidentification

  • tests/dicom/test_deidentify_conformance.py::test_a_statement_with_sections_still_to_describe_makes_no_claim

  • tests/dicom/test_deidentify_conformance.py::test_only_the_tps_import_statement_describes_synthetic_birth_dates

  • tests/dicom/test_deidentify_conformance.py::test_no_enabled_preset_has_an_incomplete_statement

  • tests/dicom/test_deidentify_conformance.py::test_the_first_supported_releases_presets_have_complete_statements

  • tests/dicom/test_deidentify_conformance.py::test_the_markers_match_those_the_engine_writes

  • tests/dicom/test_deidentify_conformance.py::test_every_attribute_the_markers_write_is_described

  • tests/dicom/test_deidentify_conformance.py::test_private_attribute_removal_is_described

  • tests/dicom/test_deidentify_conformance.py::test_each_presets_inserted_codes_and_temporal_value

  • tests/dicom/test_deidentify_conformance.py::test_every_listed_action_is_the_one_the_engine_applies

  • tests/dicom/test_deidentify_conformance.py::test_the_engines_own_removals_supersede_the_table

  • tests/dicom/test_deidentify_conformance.py::test_the_file_meta_information_the_engine_writes_is_described

  • tests/dicom/test_deidentify_conformance.py::test_a_sequence_that_the_policy_cleans_takes_its_basic_profile_action

  • tests/dicom/test_deidentify_conformance.py::test_elements_that_no_row_covers_are_no_longer_pending

  • tests/dicom/test_deidentify_conformance.py::test_the_rules_for_other_elements_are_the_engines

  • tests/dicom/test_deidentify_conformance.py::test_the_rules_for_other_elements_are_described

  • tests/dicom/test_deidentify_conformance.py::test_every_engine_group_has_its_reason

  • tests/dicom/test_deidentify_conformance.py::test_a_policy_that_the_engine_refuses_lists_the_policys_actions

  • tests/dicom/test_deidentify_conformance.py::test_every_attribute_that_no_row_covers_takes_the_action_described

  • tests/dicom/test_deidentify_conformance.py::test_elements_outside_the_dictionary_and_rows_are_removed

  • tests/dicom/test_deidentify_conformance.py::test_cleaning_is_pending_only_where_the_engine_cleans

  • tests/dicom/test_deidentify_conformance_options.py::test_the_statement_lists_the_options_composed_applied_and_not_supported

  • tests/dicom/test_deidentify_conformance_options.py::test_no_enabled_option_has_an_incomplete_statement

  • tests/dicom/test_deidentify_conformance_options.py::test_an_enabled_custom_option_set_claims_its_options

  • tests/dicom/test_deidentify_conformance_places.py::test_plain_actions_are_resolved_by_type_at_every_place

  • tests/dicom/test_deidentify_conformance_places.py::test_the_statement_says_a_reports_requested_procedure_id_is_emptied

  • tests/dicom/test_deidentify_conformance_places.py::test_the_statement_says_a_brachy_records_source_manufacturer_is_emptied

  • tests/dicom/test_deidentify_conformance_places.py::test_a_plain_x_on_a_required_attribute_removes_its_sequence

  • tests/dicom/test_deidentify_conformance_places.py::test_a_plain_x_that_no_removal_keeps_valid_sequesters

  • tests/dicom/test_deidentify_conformance_places.py::test_a_plain_x_on_overlay_data_removes_its_overlay_group

  • tests/dicom/test_deidentify_conformance_places.py::test_a_plain_d_that_no_supported_iod_defines_is_removed

  • tests/dicom/test_deidentify_conformance_places.py::test_a_plain_d_on_a_presentation_states_graphic_annotations_sequesters_it

  • tests/dicom/test_deidentify_conformance_places.py::test_a_plain_d_on_a_reports_content_sequence_sequesters_it

  • tests/dicom/test_deidentify_conformance_places.py::test_a_plain_d_on_a_reports_verifying_observer_sequence_writes_its_rule

  • tests/dicom/test_deidentify_conformance_places.py::test_a_plain_z_on_a_type_1_attribute_gives_d

  • tests/dicom/test_deidentify_conformance_places.py::test_plain_actions_by_type_are_no_longer_pending

  • tests/dicom/test_deidentify_conformance_places.py::test_the_last_column_lists_places_that_remove_more_than_the_attribute

  • tests/dicom/test_deidentify_conformance_places.py::test_the_walker_sequesters_where_the_statement_says

  • tests/dicom/test_deidentify_conformance_places.py::test_the_walker_removes_the_enclosing_sequence_that_the_statement_names

  • tests/dicom/test_deidentify_conformance_places.py::test_the_walker_removes_the_overlay_group_that_the_statement_names

  • tests/dicom/test_deidentify_conformance_places.py::test_plain_z_and_d_are_resolved_as_the_walker_plans_them

  • tests/dicom/test_deidentify_conformance_places.py::test_places_within_a_sequence_that_the_policy_removes_are_not_listed

  • tests/dicom/test_deidentify_conformance_places.py::test_places_within_a_removed_sequence_follow_the_policy_without_rules

  • tests/dicom/test_deidentify_conformance_roi_names.py::test_roi_name_cleaning_is_described_only_where_roi_name_is_cleaned

  • tests/dicom/test_deidentify_conformance_roi_names.py::test_a_published_vocabulary_is_named_with_its_edition

  • tests/dicom/test_deidentify_conformance_roi_names.py::test_the_statement_with_a_vocabulary_cites_no_decision_number

  • tests/dicom/test_deidentify_conformance_roi_names.py::test_the_described_automatic_renaming_is_the_engines

  • tests/dicom/test_deidentify_conformance_roi_names.py::test_every_review_reason_is_described

  • tests/dicom/test_deidentify_conformance_roi_names.py::test_every_reviewer_decision_and_outcome_is_described

  • tests/dicom/test_deidentify_conformance_roi_names.py::test_the_described_holding_of_a_roi_name_is_the_engines

  • tests/dicom/test_deidentify_conformance_roi_names.py::test_the_described_condition_for_the_clean_descriptors_code_is_the_engines

  • tests/dicom/test_deidentify_conformance_roi_names.py::test_every_preset_with_clean_descriptors_has_a_fallback

  • tests/dicom/test_deidentify_conformance_values.py::test_the_reviewed_dummy_item_is_described

  • tests/dicom/test_deidentify_conformance_values.py::test_the_reviewed_procedure_step_items_are_described

  • tests/dicom/test_deidentify_conformance_values.py::test_the_reviewed_icc_profile_is_described

  • tests/dicom/test_deidentify_conformance_values.py::test_the_reviewed_institution_item_is_described_with_its_place

  • tests/dicom/test_deidentify_conformance_values.py::test_only_the_reviewed_items_coding_scheme_names_pymedphys

  • tests/dicom/test_deidentify_conformance_values.py::test_pseudonyms_are_described_where_the_engine_writes_them

  • tests/dicom/test_deidentify_conformance_values.py::test_series_numbers_ranks_are_described

  • tests/dicom/test_deidentify_conformance_values.py::test_the_longest_replacement_uid_is_described

  • tests/dicom/test_deidentify_conformance_values.py::test_dates_take_their_listed_actions_without_a_temporal_option

  • tests/dicom/test_deidentify_conformance_values.py::test_dates_under_modified_dates_are_modified_by_role_as_described

  • tests/dicom/test_deidentify_conformance_values.py::test_the_temporal_attributes_are_counted_by_their_listed_action

  • tests/dicom/test_deidentify_conformance_values.py::test_the_residual_search_coverage_is_described

  • tests/dicom/test_deidentify_conformance_values.py::test_every_reason_a_value_is_not_searched_is_described

  • tests/dicom/test_deidentify_conformance_values.py::test_each_vr_described_as_not_searched_is_not_searched

  • tests/dicom/test_deidentify_conformance_values.py::test_the_release_report_names_sequestered_instances_by_label

  • tests/dicom/test_deidentify_conformance_values.py::test_the_release_report_counts_held_instances_by_stage

  • tests/dicom/test_deidentify_conformance_values.py::test_each_code_described_as_holding_holds_an_instance

  • tests/dicom/test_deidentify_conformance_values.py::test_a_code_not_described_as_holding_withholds

  • tests/dicom/test_deidentify_conformance_values.py::test_every_other_release_code_withholds

  • tests/dicom/test_deidentify_conformance_values.py::test_one_instance_without_collected_values_withholds_its_subject

  • tests/dicom/test_deidentify_conformance_values.py::test_every_reason_that_the_release_report_counts_is_described

  • tests/dicom/test_deidentify_conformance_values.py::test_per_instance_detail_is_only_in_the_qc_pack

  • tests/dicom/test_deidentify_conformance_values.py::test_nothing_of_the_run_is_pending

  • tests/dicom/test_deidentify_conformance_values.py::test_the_qc_pack_lists_what_the_run_keeps_for_review

  • tests/dicom/test_deidentify_conformance_values.py::test_the_qc_pack_lists_each_ct_mr_pet_and_rt_dose_volume

  • tests/dicom/test_deidentify_conformance_values.py::test_the_described_reason_for_an_undecodable_kept_string_sequesters

  • tests/dicom/test_deidentify_conformance_values.py::test_the_residual_search_is_described_as_part_of_each_release

  • tests/dicom/test_deidentify_conformance_values.py::test_the_report_describes_what_a_run_always_writes

  • tests/dicom/test_deidentify_conformance_values.py::test_the_statement_cites_no_design_decision_by_number

  • tests/dicom/test_deidentify_conformance_values.py::test_values_written_describes_a_procedure_logs_ranked_datetimes

  • tests/dicom/test_deidentify_out_of_scope_siblings.py::test_an_out_of_scope_sibling_no_longer_withholds_its_subject

  • tests/dicom/test_deidentify_out_of_scope_siblings.py::test_an_unlisted_sop_class_still_withholds_its_subject

  • tests/dicom/test_deidentify_pixel_cleaning.py::test_the_statement_says_how_pixel_data_are_cleaned_and_claims_after_review

  • tests/dicom/test_deidentify_project_key_runs.py::test_both_runs_are_under_the_project_key

  • tests/dicom/test_deidentify_project_key_runs.py::test_an_instance_exported_again_is_written_alike

  • tests/dicom/test_deidentify_project_key_runs.py::test_both_runs_give_each_source_uid_one_replacement

  • tests/dicom/test_deidentify_project_key_runs.py::test_the_second_runs_references_resolve_to_the_first_runs_instances

  • tests/dicom/test_deidentify_qc_retained.py::test_a_kept_string_that_cannot_be_decoded_sequesters_the_instance

  • tests/dicom/test_deidentify_redaction_review.py::test_the_statement_gives_the_code_only_after_review

  • tests/dicom/test_deidentify_run_report.py::test_a_run_publishes_the_conformance_statement_of_its_policy

  • tests/dicom/test_deidentify_run_report.py::test_a_statement_that_cannot_be_written_publishes_nothing

Note: The conformance statement of a policy is generated as CommonMark from the policy, the pinned tables, and the engine’s parameters. It lists the action that the engine applies to each attribute of Table E.1-1, of each supplementary rule, and of each UI attribute that the table omits, with each compound action resolved by Type at every place where a supported IOD defines the attribute (D-020), and why where the engine’s own removals, the File Meta Information that it writes in place of the source’s (D-025), or a sequence given C supersede the policy’s action; the rules that give every other element its action (D-022); the values that Z, D, and U write, including the reviewed item of Person Identification Code Sequence, the keyed pseudonyms at the top level of the data set, the synthetic birth dates of tps-import (D-008), and replacement UIDs of at most 44 characters (D-003, D-005, D-021); how dates and times are handled: without a Retain Longitudinal Temporal Information Option, by the listed actions of the attributes that have a temporal role, unshifted (D-006, D-007, D-023); referential integrity within a run under its 256-bit key, and none across runs (D-004); that no attribute is encrypted for later re-identification, so that no transfer syntax encodes an Encrypted Attributes Data Set (D-013); the attributes and values that the markers insert, as the engine computes them for the policy (D-012); the removal of private attributes at every level of nesting; what the residual search covers, and each reason it lists a value as not searched (D-027); how the release report names each sequestered instance by a label drawn at random for the run, with each stage and reason code (D-026), including the release gate’s withholding of every file of a subject one of whose instances gave no values to search, counts the instances held for review by the codes that can hold one, and counts the values that the residual search does not search by attribute and reason, with per-instance detail only in the confidential QC pack (D-027); and the options applied, those that can be composed, and those that are not supported. It describes what each run’s QC pack lists for review: each distinct retained string (D-017), each instance whose attributes show an indicator of risk in its pixel data, and each CT, MR, PET, and RT Dose volume, CT, MR, and PET localizer, RT Image, Enhanced RT Image, Enhanced Continuous RT Image, Nuclear Medicine image, and Segmentation among the released and held instances, with whether it names the head or neck (D-015). A statement lists what it cannot yet describe and then claims nothing. Plain X, Z, and D are resolved by Type at every place too (D-020), except within a sequence that the engine removes with its contents, and the resolved Z and D are checked against the walker’s plan of a synthetic instance. Where an IOD requires an attribute given a plain X, the statement names the enclosing sequence or overlay group that D-020 removes with it, or that the instance is sequestered, and each is checked against the walker’s plan of a synthetic instance; it gives Requested Procedure ID Z in a Structured Report’s Referenced Request Sequence, and Source Manufacturer Z in an RT Brachy Treatment Record’s Recorded Source Sequence, and says that each departs from the table’s X. Under Clean Descriptors, it describes how ROI Name is cleaned in two tiers: automatic renaming against a published TG-263 edition, which it names, and reviewers’ decisions, with every reason a name is held, what is written for each, and the condition on which an instance gains the Clean Descriptors code, each checked against the engine (D-009). Each run with a release reporter writes the statement of its policy, as conformance-statement.md beside the release report at the root of the release, since it holds no instance value; it describes the policy and vocabulary that the report records, and the report gives the run’s own method digest and reviewed-names digest. The statements of basic and basic-clean-descriptors list nothing as pending. For each selected option that has a cleaner, it gives the attributes that the policy gives C and the cleaner settles, with its method: the AE titles under Retain Device Identity (D-034), the free text under Retain Patient Characteristics, which writes none of its values itself and, under Clean Descriptors too, is given the reviewed descriptors list’s decisions (D-038, PS3.15-E.3.7-02), and the dates and times that Modified Dates modifies (D-039, PS3.15-E.3.6-04). A statement still lists as pending, and so claims nothing for, the manner of cleaning any other attribute to which the policy gives C other than under Clean Descriptors alone; this completes with those options. Under tps-import, it describes the synthetic birth dates that Patient’s Birth Date takes, how the reference study is chosen, the empty value written where the ages are missing or inconsistent, and the birth-date-conflict sequestration of a later run that contradicts the reference (D-008). Under tps-import, it gives each attribute other than ROI Name that the policy gives C the action that the preset’s other options give it, with the eleven device dates and times modified as the preset modifies them, as descriptor cleaning falls back to it (D-009). Under Retain Safe Private, it describes how the reviewed safe private rules are matched and applied, and lists each rule that can retain an attribute (D-035, PS3.15-E.3.10-04).

PS3.15-E.2-01#

Source: PS3.15 E.2, paragraph. Status: implemented.

The Attribute Longitudinal Temporal Information Modified (0028,0303) shall be added to the Data Set with a Value of "REMOVED" if none of the Retain Longitudinal Temporal Information Options is applied.

Decisions: D-012, D-031.

Implementation:

  • _dicom/deidentify/instance_transform.py

  • _dicom/deidentify/markers.py

Tests:

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_every_released_instance_carries_the_profiles_markers

  • tests/dicom/test_deidentify_instance_transform.py::test_each_output_carries_the_de_identification_markers

  • tests/dicom/test_deidentify_markers.py::test_each_preset_adds_exactly_its_markers

  • tests/dicom/test_deidentify_markers.py::test_existing_markers_and_contributing_equipment_items_are_kept

  • tests/dicom/test_deidentify_markers.py::test_temporal_information_modified_keeps_the_stricter_value

  • tests/dicom/test_deidentify_markers.py::test_an_unknown_temporal_information_modified_is_refused_without_quoting_it

Note: Every policy that does not apply Modified Dates, the only Retain Longitudinal Temporal Information Option in scope, sets Longitudinal Temporal Information Modified to REMOVED in its markers, replacing any value already present, since REMOVED is the strictest, and refusing a present value that is not UNMODIFIED, MODIFIED, or REMOVED (D-012). The run’s instance transform adds it to each instance it de-identifies. An end-to-end test runs basic and basic-clean-descriptors, neither of which applies Modified Dates, over the synthetic collection and finds REMOVED in every released instance (MIDI-BP-01). MIDI-B’s answer keys expect another value here, under their TCIA review category, so every released instance that the key checks there fails that check by design (D-031).

PS3.15-E.3.1-01#

Source: PS3.15 E.3.1, paragraph. Status: implemented.

When this Option is specified in addition to the Basic Application Level Confidentiality Profile, any information burned in to the Pixel Data (7FE0,0010) corresponding to the Attribute information specified to be removed by the Profile and any other Options specified shall also be removed, as described in Table E.1-1.

Decisions: D-015, D-033, D-037, D-048, D-055, D-056, D-059.

Implementation:

  • _dicom/deidentify/api.py

  • _dicom/deidentify/command.py

  • _dicom/deidentify/imagecodecs_plugin.py

  • _dicom/deidentify/instance_transform.py

  • _dicom/deidentify/pixel_cleaning.py

  • _dicom/deidentify/pixel_decoding.py

  • _dicom/deidentify/pixel_region_rules.py

  • _dicom/deidentify/pixel_region_rules_command.py

  • _dicom/deidentify/redaction_review.py

  • _dicom/deidentify/redaction_review_app.py

  • _dicom/deidentify/redaction_verdicts.py

  • _dicom/deidentify/text_detection.py

Tests:

  • tests/dicom/test_deidentify_command_pixel_data.py::test_a_second_run_given_the_attested_pack_releases_the_reviewed_images

  • tests/dicom/test_deidentify_command_pixel_data.py::test_the_library_given_the_attested_pack_releases_the_reviewed_images

  • tests/dicom/test_deidentify_compressed_redaction.py::test_compressed_frames_are_decoded_and_blacked_out

  • tests/dicom/test_deidentify_compressed_redaction.py::test_each_frame_is_decoded_on_its_own_and_searched

  • tests/dicom/test_deidentify_compressed_redaction.py::test_an_odd_number_of_8_bit_samples_is_padded_to_an_even_length

  • tests/dicom/test_deidentify_compressed_redaction.py::test_colour_is_decoded_to_rgb_and_written_so

  • tests/dicom/test_deidentify_compressed_redaction.py::test_a_cleaned_compressed_image_is_written_uncompressed_and_held

  • tests/dicom/test_deidentify_compressed_redaction.py::test_the_source_is_shown_as_the_child_decoded_it

  • tests/dicom/test_deidentify_compressed_redaction.py::test_a_compressed_image_without_text_is_written_uncompressed_and_held

  • tests/dicom/test_deidentify_compressed_redaction.py::test_reviewed_regions_release_frames_that_decode_as_reviewed

  • tests/dicom/test_deidentify_compressed_redaction.py::test_frames_that_do_not_decode_as_reviewed_are_cleaned_and_held_again

  • tests/dicom/test_deidentify_compressed_redaction.py::test_a_reviewed_compressed_image_is_released_as_its_previews_showed_it

  • tests/dicom/test_deidentify_compressed_redaction.py::test_regions_a_reviewer_adds_to_an_unmarked_image_release_what_was_previewed

  • tests/dicom/test_deidentify_compressed_redaction.py::test_a_compressed_source_that_cannot_be_shown_as_decoded_is_not_previewed

  • tests/dicom/test_deidentify_compressed_redaction.py::test_a_cleaned_compressed_image_is_previewed_without_a_native_decoder

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_each_cleaned_image_is_listed_and_previewed_frame_by_frame

  • tests/dicom/test_deidentify_ct_defacing_review_runs.py::test_with_clean_pixel_data_the_head_ct_is_held_for_its_pixel_data

  • tests/dicom/test_deidentify_ct_defacing_review_runs.py::test_with_both_options_reviewed_the_head_ct_is_released_with_both

  • tests/dicom/test_deidentify_ct_defacing_review_runs.py::test_a_revised_region_that_changes_a_defaced_slice_holds_it_for_its_face

  • tests/dicom/test_deidentify_embedded_overlays.py::test_clean_pixel_data_clears_the_overlay_and_blacks_out_the_text

  • tests/dicom/test_deidentify_embedded_overlays.py::test_an_overlay_that_clean_pixel_data_cannot_clear_leaves_it_uncleanable

  • tests/dicom/test_deidentify_float_pixel_cleaning.py::test_floating_point_text_is_set_to_the_frames_smallest_finite_value

  • tests/dicom/test_deidentify_imagecodecs_decoding.py::test_clean_pixel_data_blacks_out_and_writes_the_decoded_frames

  • tests/dicom/test_deidentify_imagecodecs_decoding.py::test_clean_pixel_data_cleans_low_precision_jpeg_ls_in_a_16_bit_container

  • tests/dicom/test_deidentify_pixel_cleaning.py::test_text_is_set_to_the_frames_stored_fill

  • tests/dicom/test_deidentify_pixel_cleaning.py::test_signed_values_keep_their_sign_in_the_unused_bits

  • tests/dicom/test_deidentify_pixel_cleaning.py::test_rgb_text_is_set_to_black_in_either_planar_configuration

  • tests/dicom/test_deidentify_pixel_cleaning.py::test_each_frame_is_searched_and_blacked_out_with_its_own_fill

  • tests/dicom/test_deidentify_pixel_cleaning.py::test_a_ct_image_is_written_blacked_out_and_held_for_review

  • tests/dicom/test_deidentify_pixel_cleaning.py::test_a_run_holds_its_images_and_reports_why

  • tests/dicom/test_deidentify_pixel_region_rules.py::test_recorded_rules_are_reused_on_later_runs

  • tests/dicom/test_deidentify_pixel_region_rules.py::test_a_key_is_read_from_the_five_attributes_without_padding

  • tests/dicom/test_deidentify_pixel_region_rules.py::test_a_rule_matches_only_its_exact_key

  • tests/dicom/test_deidentify_pixel_region_rules.py::test_the_store_is_refused_inside_a_protected_directory

  • tests/dicom/test_deidentify_pixel_region_rules.py::test_the_keyed_digest_is_the_hmac_of_the_stores_canonical_form

  • tests/dicom/test_deidentify_pixel_region_rules.py::test_a_snapshot_keeps_the_rules_as_they_stood_and_is_never_saved

  • tests/dicom/test_deidentify_pixel_region_rules_command.py::test_a_region_that_a_reviewer_added_is_recorded_for_the_images_kind

  • tests/dicom/test_deidentify_pixel_region_rules_command.py::test_a_later_run_with_the_recorded_rule_blacks_out_its_region

  • tests/dicom/test_deidentify_pixel_region_rules_run.py::test_a_rules_regions_are_blacked_out_in_every_frame_beside_the_finders

  • tests/dicom/test_deidentify_pixel_region_rules_run.py::test_an_image_that_a_rule_matches_is_blacked_out_and_held_for_review

  • tests/dicom/test_deidentify_pixel_region_rules_run.py::test_the_rules_keyed_digest_is_an_input_to_the_method_digest

  • tests/dicom/test_deidentify_pixel_region_rules_run.py::test_a_reviewed_rules_regions_are_released_again_without_the_rules

  • tests/dicom/test_deidentify_readiness.py::test_a_later_run_with_the_same_region_rules_passes_every_gate

  • tests/dicom/test_deidentify_redaction_previews.py::test_a_runs_pack_lists_each_cleaned_instance_and_previews_its_frames

  • tests/dicom/test_deidentify_redaction_review.py::test_an_approved_image_is_released_with_its_reviewed_regions

  • tests/dicom/test_deidentify_redaction_review.py::test_a_rejected_image_is_sequestered

  • tests/dicom/test_deidentify_redaction_review.py::test_an_image_whose_reviewed_region_goes_past_its_frame_is_sequestered

  • tests/dicom/test_deidentify_redaction_review.py::test_an_image_without_a_verdict_is_held_as_in_the_first_run

  • tests/dicom/test_deidentify_redaction_review.py::test_a_rerun_writes_the_reviewed_regions_whatever_its_finder

  • tests/dicom/test_deidentify_risk_assessment.py::test_the_gate_fails_for_other_region_rules

Note: Text that a text finder finds in Pixel Data is blacked out, whatever it says, and every instance cleaned is held for review (D-037). Reviewed region rules, kept by the custodian and keyed by SOP Class UID, Manufacturer, Manufacturer’s Model Name, Rows, and Columns, add rectangles that are blacked out in every frame of a matching image beside the finder’s regions; the instance is still held for review, the QC pack records which regions rules gave, and the rules’ keyed digest is an input to the method digest (D-024, D-037). A private command records a rule from an attested QC pack, holding only regions that the reviewer’s verdict approved for the image, keyed by the image’s source file, which must have the SHA-256 that the pack records (D-037, D-048). Compressed Pixel Data is decoded first, in the isolated decoding child for the native decoders, and its frames are written uncompressed, in Explicit VR Little Endian, as decoded, with or without a region, so that the QC pack previews the samples that a release writes (D-037). The frames that pydicom’s GDCM plugin refuses, 12-bit JPEG Extended, JPEG-LS at 6 or 7 bits, and signed JPEG-LS Near-Lossless below 8 bits, are decoded with imagecodecs in the same child, never in the engine’s process, so they are cleaned rather than sequestered as undecodable-pixel-data (D-055). In native Pixel Data, the bit plane of an overlay that an overlay group without Overlay Data places above High Bit is cleared in the same write, and its group removed, so the instance is cleaned rather than sequestered as uncleanable-pixel-data (D-037). A later run of the same files, given the attested QC pack, blacks out the regions that the reviewer accepted, without any finder’s region that the reviewer dropped and with any that the reviewer drew, without the finder, and releases the instance with the option’s code; for compressed Pixel Data, only where its frames decode to the SHA-256 that the pack records, and otherwise the instance is held again. An instance that the reviewer rejected is sequestered (D-048). A private local app shows the reviewer every frame of each cleaned instance at full depth, with the finder’s regions as proposals to keep or drop and a rule’s regions locked, lets them draw regions over text that the finder missed, and saves their verdicts for the attestation command; attesting refuses an accepted or revised verdict until the reviewer has been shown every frame that its review requires, which is every frame of every IOD for now; attesting records the mode of each approval’s review, and the release report counts the approved files reviewed in each mode and the frames of them that were not shown (D-059). The private command’s run under the option, and the library’s, makes the text detector with the pinned model, and takes the attested QC pack whose verdicts apply and the custodian’s region rules (D-033). A workflow run by hand counts the regions that the text detector finds in the slices of the HaN-Seg data set’s 42 head and neck planning CTs and 42 T1-weighted MRs, which hold no burned-in text, on a hosted runner that alone downloads it, and publishes only counts, which bear on how many regions a reviewer clears; it claims no result (D-037). The detector’s evaluation and tuning, on images with and without burned-in text across the IODs in scope, against acceptance criteria set before tuning and confirmed once on held-out data, is designed, and its library, and a workflow run by hand that measures openly licensed collections of NCI’s Imaging Data Commons, are built; its inventory job, its other corpora’s runs, its tuning runs, and the adoption of a tuned detector are not yet made (D-056).

PS3.15-E.3.1-02#

Source: PS3.15 E.3.1, paragraph. Status: implemented.

The Attribute Burned In Annotation (0028,0301) shall be added to the Data Set with a Value of "NO".

Decisions: D-012, D-015, D-048.

Implementation:

  • _dicom/deidentify/instance_transform.py

  • _dicom/deidentify/markers.py

Tests:

  • tests/dicom/test_deidentify_command_pixel_data.py::test_a_second_run_given_the_attested_pack_releases_the_reviewed_images

  • tests/dicom/test_deidentify_command_pixel_data.py::test_the_library_given_the_attested_pack_releases_the_reviewed_images

  • tests/dicom/test_deidentify_compressed_redaction.py::test_reviewed_regions_release_frames_that_decode_as_reviewed

  • tests/dicom/test_deidentify_compressed_redaction.py::test_a_reviewed_compressed_image_is_released_as_its_previews_showed_it

  • tests/dicom/test_deidentify_compressed_redaction.py::test_regions_a_reviewer_adds_to_an_unmarked_image_release_what_was_previewed

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_burned_in_annotation_is_no_exactly_where_the_option_is_claimed

  • tests/dicom/test_deidentify_pixel_cleaning.py::test_an_instance_without_pixel_data_claims_the_option

  • tests/dicom/test_deidentify_redaction_review.py::test_the_markers_set_burned_in_annotation_to_no_with_the_code

  • tests/dicom/test_deidentify_redaction_review.py::test_an_approved_image_is_released_with_its_reviewed_regions

  • tests/dicom/test_deidentify_redaction_review.py::test_an_instance_without_pixel_data_claims_the_option

Note: Applies only with the Clean Pixel Data Option. An instance that satisfies the option, because it holds no top-level pixel data or a reviewer approved its regions, has Burned In Annotation set to NO, replacing any value present, whether or not its IOD defines the attribute; in the RT Plan, RT Structure Set, RT Beams Treatment Record, RT Ion Plan, RT Ion Beams Treatment Record, RT Brachy Treatment Record, RT Treatment Summary Record, Enhanced RT Image, Enhanced Continuous RT Image, RT Segment Annotation, RT Radiation Set, C-Arm Photon-Electron Radiation, Tomotherapeutic Radiation, Robotic-Arm Radiation, RT Beams Delivery Instruction, RT Brachy Application Setup Delivery Instruction, RT Radiation Set Delivery Instruction, MR Spectroscopy, Spatial Registration, Deformable Spatial Registration, X-Ray Radiation Dose SR, Enhanced X-Ray Radiation Dose SR, Radiopharmaceutical Radiation Dose SR, Patient Radiation Dose SR, Basic Text SR, Enhanced SR, Comprehensive SR, Comprehensive 3D SR, Extensible SR, Key Object Selection Document, Mammography CAD SR, Chest CAD SR, Colon CAD SR, Procedure Log, Spectacle Prescription Report, Macular Grid Thickness and Volume Report, Waveform Annotation SR, and Real World Value Mapping IODs, which do not, it is a standard attribute of a Standard Extended SOP Class (PS3.2 Section 7.3), and the validators’ findings of it there are known differences (D-048). Any other instance keeps Burned In Annotation as received, and it is never set to NO otherwise (D-015).

PS3.15-E.3.2-01#

Source: PS3.15 E.3.2, paragraph. Status: implemented.

When this Option is specified in addition to the Basic Application Level Confidentiality Profile, if there is sufficient visual information within the Pixel Data of a set of instances to allow an individual to be recognized from the instances themselves or a reconstruction of a set of instances, then sufficient removal or distortion of the Pixel Data shall be applied to prevent recognition.

Decisions: D-015, D-016, D-017, D-033, D-046, D-050, D-051, D-052, D-054.

Implementation:

  • _dicom/deidentify/api.py

  • _dicom/deidentify/command.py

  • _dicom/deidentify/contour_slabs.py

  • _dicom/deidentify/defacing.py

  • _dicom/deidentify/dose_defacing.py

  • _dicom/deidentify/face_blocks.py

  • _dicom/deidentify/face_review.py

  • _dicom/deidentify/functional_groups.py

  • _dicom/deidentify/instance_transform.py

  • _dicom/deidentify/phases.py

  • _dicom/deidentify/pixel_cleaning.py

  • _dicom/deidentify/qc_attestation.py

  • _dicom/deidentify/qc_contours.py

  • _dicom/deidentify/qc_eyes.py

  • _dicom/deidentify/qc_grids.py

  • _dicom/deidentify/region_defacing.py

  • _dicom/deidentify/segmentation_defacing.py

  • _dicom/deidentify/target_hulls.py

Tests:

  • tests/dicom/test_deidentify_api_face_review.py::test_the_library_releases_a_series_marked_absent_with_the_option

  • tests/dicom/test_deidentify_api_face_review.py::test_the_library_holds_a_series_marked_present

  • tests/dicom/test_deidentify_command_face_review.py::test_the_command_releases_a_series_marked_absent_with_the_option

  • tests/dicom/test_deidentify_command_face_review.py::test_the_command_holds_a_series_marked_present

  • tests/dicom/test_deidentify_ct_defacing_review_runs.py::test_a_verdict_of_its_own_keeps_the_block_that_cleaned_it_unconfirmed

  • tests/dicom/test_deidentify_ct_defacing_review_runs.py::test_an_rt_dose_not_set_to_zero_is_released_on_its_own_dose_levels

  • tests/dicom/test_deidentify_ct_defacing_review_runs.py::test_an_undefaced_instance_its_pack_does_not_show_gets_no_verdict

  • tests/dicom/test_deidentify_ct_defacing_review_runs.py::test_high_dose_in_the_block_holds_the_dose_as_received

  • tests/dicom/test_deidentify_ct_defacing_review_runs.py::test_a_hot_grid_is_listed_for_the_reviewer_to_choose

  • tests/dicom/test_deidentify_ct_defacing_review_runs.py::test_a_hot_grid_chosen_as_written_is_released_as_received

  • tests/dicom/test_deidentify_ct_defacing_review_runs.py::test_a_hot_grid_chosen_to_set_to_zero_is_held_for_its_new_dose_levels

  • tests/dicom/test_deidentify_ct_defacing_review_runs.py::test_a_hot_grid_set_to_zero_is_released_on_a_verdict_on_it_so

  • tests/dicom/test_deidentify_ct_defacing_review_runs.py::test_a_hot_grid_chosen_as_written_needs_no_confirmed_block

  • tests/dicom/test_deidentify_ct_defacing_review_runs.py::test_a_hot_grid_set_to_zero_by_an_unconfirmed_block_waits_for_it

  • tests/dicom/test_deidentify_ct_defacing_review_runs.py::test_a_grid_that_cannot_be_placed_is_held_as_received

  • tests/dicom/test_deidentify_ct_defacing_review_runs.py::test_an_rt_dose_of_dose_volume_histograms_alone_is_not_held_for_its_frame

  • tests/dicom/test_deidentify_ct_defacing_review_runs.py::test_a_head_ct_with_a_target_in_the_block_is_defaced_around_it

  • tests/dicom/test_deidentify_ct_defacing_review_runs.py::test_the_structure_set_is_cut_around_a_target_in_the_block

  • tests/dicom/test_deidentify_ct_defacing_review_runs.py::test_dose_and_activity_within_a_target_in_the_block_are_kept

  • tests/dicom/test_deidentify_ct_defacing_review_runs.py::test_with_clean_pixel_data_the_head_ct_is_held_for_its_pixel_data

  • tests/dicom/test_deidentify_ct_defacing_review_runs.py::test_with_both_options_reviewed_the_head_ct_is_released_with_both

  • tests/dicom/test_deidentify_ct_defacing_review_runs.py::test_a_revised_region_that_changes_a_defaced_slice_holds_it_for_its_face

  • tests/dicom/test_deidentify_ct_defacing_review_runs.py::test_with_both_options_reviewed_the_head_pet_is_released_with_both

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_a_head_ct_marked_absent_as_defaced_is_released_defaced_with_the_option

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_a_compressed_head_ct_is_released_defaced_as_the_native_one_is

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_compressed_pet_and_rt_dose_in_the_frame_are_cleaned_as_native_ones_are

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_a_head_ct_marked_absent_as_received_is_held_where_defaced

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_pet_instances_in_the_defaced_frame_that_are_not_cleaned_are_held

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_a_registration_whose_grid_is_tied_to_the_defaced_frame_is_held

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_the_pack_lists_each_undefaced_instance_for_a_verdict_of_its_own

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_an_undefaced_instance_is_released_on_a_verdict_of_absent_of_its_own

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_a_verdict_of_present_of_its_own_or_on_its_series_holds_it

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_a_verdict_of_its_own_is_on_its_pixel_data_whatever_is_defaced

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_a_released_undefaced_instance_needs_a_new_verdict_of_its_own

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_a_verdict_of_its_own_needs_no_confirmed_block

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_the_head_structure_set_is_cut_by_the_block_and_released

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_the_head_pet_is_set_to_zero_activity_in_the_block_and_released

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_the_head_segmentation_is_cleared_in_the_block_and_released

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_the_head_structure_set_pet_and_segmentation_seen_as_received_are_held

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_rt_dose_behind_the_block_is_released_unchanged

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_low_dose_in_the_block_is_set_to_zero_there_and_released

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_two_defaced_series_in_one_frame_are_each_defaced_by_their_own_block

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_what_lies_in_a_frame_of_two_defaced_series_is_cleaned_by_both_blocks

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_a_structure_set_of_two_frames_is_cut_in_the_defaced_one_and_released

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_a_structure_set_cut_by_a_slab_is_released_on_its_drawn_contours

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_an_error_dose_is_released_on_its_signed_dose_previews

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_rt_instances_judged_beside_a_defaced_ct_are_held_in_a_run_without_it

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_a_multi_frame_head_ct_is_written_as_air_where_its_slices_are

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_a_multi_frame_head_ct_gives_its_frame_the_block_of_its_slices

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_a_multi_frame_pet_is_set_to_zero_activity_and_released

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_a_head_ct_of_two_time_points_is_defaced_and_released

  • tests/dicom/test_deidentify_dose_choices.py::test_a_grid_beyond_the_fraction_is_held_or_set_to_zero_as_chosen

  • tests/dicom/test_deidentify_dose_choices.py::test_a_reviewer_chooses_by_the_rt_dose_s_run_position

  • tests/dicom/test_deidentify_dose_choices.py::test_the_face_review_gives_each_choice_by_its_dose_check_s_digest

  • tests/dicom/test_deidentify_dose_choices.py::test_a_verdict_of_absent_covers_a_held_rt_dose_only_chosen_as_written

  • tests/dicom/test_deidentify_dose_choices.py::test_the_report_records_each_rt_dose_by_what_the_run_did_with_it

  • tests/dicom/test_deidentify_own_verdicts.py::test_a_reviewer_gives_an_instance_its_own_verdict_by_its_run_position

  • tests/dicom/test_deidentify_own_verdicts.py::test_a_series_verdict_never_covers_an_undefaced_instance

  • tests/dicom/test_deidentify_own_verdicts.py::test_an_own_verdict_decides_its_instance

  • tests/dicom/test_deidentify_own_verdicts.py::test_the_report_records_each_undefaced_instance_by_its_own_verdict

  • tests/dicom/test_deidentify_qc_contours.py::test_a_verdict_releases_a_structure_set_only_where_its_contours_were_shown

  • tests/dicom/test_deidentify_qc_contours.py::test_a_verdict_releases_a_structure_set_whose_slab_contours_were_shown

  • tests/dicom/test_deidentify_qc_grids.py::test_a_verdict_releases_a_registration_whose_grids_were_shown

  • tests/dicom/test_deidentify_qc_pixel_digests.py::test_the_digest_of_pixel_data_that_are_not_shown_is_left_out

  • tests/dicom/test_deidentify_qc_pixel_digests.py::test_a_run_holds_an_instance_whose_frames_its_pack_did_not_all_show

  • tests/dicom/test_deidentify_qc_pixel_digests.py::test_a_verdict_of_absent_holds_pixel_data_that_the_pack_did_not_show

  • tests/dicom/test_deidentify_qc_pixel_digests.py::test_a_verdict_of_absent_releases_pixel_data_whose_every_frame_was_shown

  • tests/dicom/test_deidentify_qc_pixel_digests.py::test_a_run_releases_an_rt_dose_only_where_its_dose_levels_showed_every_frame

  • tests/dicom/test_deidentify_qc_segment_verdicts.py::test_a_run_releases_a_segmentation_only_where_its_segments_sheet_showed_every_frame

  • tests/dicom/test_deidentify_qc_segment_verdicts.py::test_a_verdict_on_a_segments_sheet_releases_its_segmentation

  • tests/dicom/test_deidentify_qc_segment_verdicts.py::test_a_verdict_on_a_segmentation_that_no_sheet_shows_holds_it

  • tests/dicom/test_deidentify_visual_features_option.py::test_a_series_marked_absent_is_released_with_the_option

  • tests/dicom/test_deidentify_visual_features_option.py::test_every_other_instance_that_can_show_a_face_is_held

  • tests/dicom/test_deidentify_visual_features_option.py::test_a_surface_segmentation_is_held_whatever_its_verdict

  • tests/dicom/test_deidentify_visual_features_option.py::test_with_clean_pixel_data_a_verdict_covers_only_the_pixel_data_seen

Note: The engine claims the option only for an instance in which a reviewer found no recognisable face, by the attested face verdicts of an earlier run’s QC pack, matched by the SHA-256 of each source file and of the pixel data, an RT Structure Set’s contours, and a deformable registration’s grids, that the run writes of it, or one that cannot show a face (D-046). The library’s run under the option, and the private command’s, take the attested QC pack whose face verdicts apply, and refuse to start without one (D-033, D-046). The public-release preset selects the option, as do custom option sets that name it (Presets, D-046). The QC pack shows each RT Structure Set’s contours, as written and as each reading of their retired Contour Slab Thickness and Contour Offset Vector places them, on a contour sheet, with every surface that an ROI hides behind its own and every mark that it would hide, to within two voxels, at most 4 mm, of its edge as seen from the front, the back, and the sides, and records their SHA-256 only where it shows them, and the pixel data’s only where previews that could show a face draw every frame, a volume’s slices by its projection and surface render, a Segmentation’s only by its segments sheet, which renders its segments as written from the same directions, and an RT Dose’s only by renders of its surfaces at each of a ladder of dose levels, from any dose above 0 up, and for a signed grid, such as one of Dose Type ERROR, at each of a second ladder of the magnitudes of its doses below 0, with every surface that a level hides behind its own, or a map of a lone plane by those levels, so a verdict releases only contours and pixel data that the reviewer saw, and an instance the pack does not show in full is held, the run counting an RT Dose’s frames shown from its dose levels alone and a Segmentation’s, by its SOP Class UID or a Modality that may be SEG, from its segments sheet alone, as is a set with a voxel of hidden marks that its rows’ frontal view, view from behind, and profiles cannot draw, even where an oblique view would; the hidden marks are drawn in layers, each contour’s where no other’s lie beside them, so that is only where one contour’s own marks lie side by side within its ROI’s region, as where one line drawn back and forth covers a patch or near the tip of a sharp V (D-017). The QC pack shows each deformation grid of a Deformable Spatial Registration, as written, on five sheets, of its displacement’s magnitude, with where it is defined, of how fast it varies across the grid, and of the sign and magnitude of each of its components, each drawn as an RT Dose’s dose levels are, from every grid point, and records the grids’ SHA-256 only where a sheet of each kind shows each grid, so a verdict releases only grids that the reviewer saw (D-017). Every other instance that can show a face, with pixel data, as an RT Structure Set, or with a deformation grid, is held for review and never released, and so is every Surface Segmentation, or other instance with a surface mesh, whose points can trace the skin and which no preview of the pack draws, so no released instance that claims the option has the visual information that the paragraph requires to be removed or distorted. Under the option, the engine also writes air into the face block of each CT series in which it finds one, of CT Image instances, Enhanced CT Image or Legacy Converted Enhanced CT Image instances whose frames their functional groups place, or both, a series whose slices repeat their positions, as the time points of a respiratory-correlated CT do, by a block that holds the block found from each of its phases, parted by the time point or acquisition that each slice names, where their globes agree, keeping the brain in the cranial cavity above the orbits and each PTV, CTV, or GTV with whatever lies between its contours, by its convex hull, and holds a series with a target whose part in its block reaches beyond its slices, or that cannot be placed; it defaces a series with an eye ROI away from the globes it found and holds it, with each instance that it cleans by that block, until a reviewer confirms the block from the QC pack’s sheet of the globes beside the eye ROIs, drawn from the slices as received, a confirmation bound by SHA-256 to the series’ source files and the block; and it holds each RT Structure Set, RT Dose with pixel data, PET instance, or Segmentation in a defaced series’ Frame of Reference, naming none, or referring to an instance of a defaced series of a frame that it is not cleaned in, and each instance with a deformation grid that names a defaced series’ Frame of Reference as its own or as a Source Frame of Reference, names none, or refers to an instance of a defaced series, which it does not clean, whatever its series’ verdict, until a reviewer gives it a face verdict of its own from the QC pack’s previews of it alone, which the pack accepts only where they draw every frame, contour, and grid of it, bound by SHA-256 to its source file and to the pixel data, contours, and grids that the pack showed (D-050, D-046), but for an RT Dose in a defaced frame, whose dose, by magnitude for Dose Type ERROR, in the block of each defaced series of that frame, less the brain and targets that series keeps, it sets to zero, or holds where that dose is too great until a reviewer chooses, from the QC pack, to write it as received, released on a face verdict on its own dose-levels previews, or to set it to zero anyway, released only on a verdict on a later run’s previews of the grid as set to zero, a choice bound by SHA-256 to its source file and the block and recorded in the QC pack and the release report (D-051), and an RT Structure Set, whose contours that reach the face, in each defaced frame, it cuts by that frame’s blocks, never cutting a kept target’s own contours, less the brain and targets kept where a contour allows, wherever a contour’s retired slab attributes may put it, leaving those of a frame that it declares and no defaced series is in, and a PET image in a defaced frame, whose activity in each block, less the brain and targets kept, it sets to zero, in each frame of an Enhanced PET Image or Legacy Converted Enhanced PET Image that its functional groups place and whose Real World Value Mapping, not its Pixel Value Transformation of unspecified units, gives one stored value of zero activity, and a Segmentation in such a frame, of BINARY frames packed across bytes or not, FRACTIONAL frames, or a MONOCHROME2 or PALETTE COLOR label map, placed by per-frame or shared functional groups, whose pixels in a segment in each block, less the brain and targets kept, it writes as its background (D-052); a defaced instance still claims the option only where a reviewer found no recognisable face in it as defaced. Compressed Pixel Data that a run decodes is defaced from its frames as decoded, and an instance that defacing changes is written uncompressed, in Explicit VR Little Endian, as decoded, while one that it leaves unchanged is written as received (D-037, D-050). A workflow run by hand checks the face block and the air written against the eye and organ masks of the HaN-Seg data set’s 42 head and neck planning CTs, on a hosted runner that alone downloads it, and publishes only counts, which bear on D-050’s placeholder margin and eye reach, though not its cavity floor, since HaN-Seg marks no brain; it claims no result (D-054). A development command run on a person’s own machine applies the same finder, target keeping and holds, eye ROI hold, RT Dose defacing, and contour cutting to a directory of DICOM files, such as planning exports, renders nothing, and writes only counts, which bear on D-050’s placeholder margin and eye reach and D-051’s dose fraction, though not the cavity floor, since it measures no brain; it claims no result either (D-054).

PS3.15-E.3.2-02#

Source: PS3.15 E.3.2, paragraph. Status: implemented.

The Attribute Recognizable Visual Features (0028,0302) shall be added to the Data Set with a Value of "NO".

Decisions: D-012, D-015, D-046.

Implementation:

  • _dicom/deidentify/instance_transform.py

  • _dicom/deidentify/markers.py

Tests:

  • tests/dicom/test_deidentify_api_face_review.py::test_the_library_releases_a_series_marked_absent_with_the_option

  • tests/dicom/test_deidentify_command_face_review.py::test_the_command_releases_a_series_marked_absent_with_the_option

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_recognizable_visual_features_is_no_exactly_where_the_option_is_claimed

  • tests/dicom/test_deidentify_ct_defacing_review_runs.py::test_a_verdict_of_its_own_keeps_the_block_that_cleaned_it_unconfirmed

  • tests/dicom/test_deidentify_ct_defacing_review_runs.py::test_an_rt_dose_not_set_to_zero_is_released_on_its_own_dose_levels

  • tests/dicom/test_deidentify_ct_defacing_review_runs.py::test_a_hot_grid_chosen_as_written_is_released_as_received

  • tests/dicom/test_deidentify_ct_defacing_review_runs.py::test_a_hot_grid_set_to_zero_is_released_on_a_verdict_on_it_so

  • tests/dicom/test_deidentify_ct_defacing_review_runs.py::test_a_hot_grid_chosen_as_written_needs_no_confirmed_block

  • tests/dicom/test_deidentify_ct_defacing_review_runs.py::test_an_rt_dose_of_dose_volume_histograms_alone_is_not_held_for_its_frame

  • tests/dicom/test_deidentify_ct_defacing_review_runs.py::test_a_head_ct_with_a_target_in_the_block_is_defaced_around_it

  • tests/dicom/test_deidentify_ct_defacing_review_runs.py::test_the_structure_set_is_cut_around_a_target_in_the_block

  • tests/dicom/test_deidentify_ct_defacing_review_runs.py::test_dose_and_activity_within_a_target_in_the_block_are_kept

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_a_head_ct_marked_absent_as_defaced_is_released_defaced_with_the_option

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_a_compressed_head_ct_is_released_defaced_as_the_native_one_is

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_compressed_pet_and_rt_dose_in_the_frame_are_cleaned_as_native_ones_are

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_a_registration_whose_grid_is_tied_to_the_defaced_frame_is_held

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_an_undefaced_instance_is_released_on_a_verdict_of_absent_of_its_own

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_a_verdict_of_its_own_is_on_its_pixel_data_whatever_is_defaced

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_a_verdict_of_its_own_needs_no_confirmed_block

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_the_head_structure_set_is_cut_by_the_block_and_released

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_the_head_pet_is_set_to_zero_activity_in_the_block_and_released

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_the_head_segmentation_is_cleared_in_the_block_and_released

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_low_dose_in_the_block_is_set_to_zero_there_and_released

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_two_defaced_series_in_one_frame_are_each_defaced_by_their_own_block

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_what_lies_in_a_frame_of_two_defaced_series_is_cleaned_by_both_blocks

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_a_structure_set_of_two_frames_is_cut_in_the_defaced_one_and_released

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_a_multi_frame_head_ct_gives_its_frame_the_block_of_its_slices

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_a_multi_frame_pet_is_set_to_zero_activity_and_released

  • tests/dicom/test_deidentify_ct_defacing_run.py::test_a_head_ct_of_two_time_points_is_defaced_and_released

  • tests/dicom/test_deidentify_qc_grids.py::test_a_verdict_releases_a_registration_whose_grids_were_shown

  • tests/dicom/test_deidentify_visual_features_option.py::test_a_series_marked_absent_is_released_with_the_option

  • tests/dicom/test_deidentify_visual_features_option.py::test_with_clean_pixel_data_a_verdict_covers_only_the_pixel_data_seen

  • tests/dicom/test_deidentify_visual_features_option.py::test_an_instance_that_cannot_show_a_face_satisfies_the_option

Note: Applies only with the Clean Recognizable Visual Features Option. An instance that satisfies the option has Recognizable Visual Features set to NO, replacing any value present, whether or not its IOD defines the attribute; in the RT Plan, RT Structure Set, RT Beams Treatment Record, RT Ion Plan, RT Ion Beams Treatment Record, RT Brachy Treatment Record, RT Treatment Summary Record, Enhanced RT Image, Enhanced Continuous RT Image, RT Segment Annotation, RT Radiation Set, C-Arm Photon-Electron Radiation, Tomotherapeutic Radiation, Robotic-Arm Radiation, RT Beams Delivery Instruction, RT Brachy Application Setup Delivery Instruction, RT Radiation Set Delivery Instruction, MR Spectroscopy, Spatial Registration, Deformable Spatial Registration, X-Ray Radiation Dose SR, Enhanced X-Ray Radiation Dose SR, Radiopharmaceutical Radiation Dose SR, Patient Radiation Dose SR, Basic Text SR, Enhanced SR, Comprehensive SR, Comprehensive 3D SR, Extensible SR, Key Object Selection Document, Mammography CAD SR, Chest CAD SR, Colon CAD SR, Procedure Log, Spectacle Prescription Report, Macular Grid Thickness and Volume Report, Waveform Annotation SR, and Real World Value Mapping IODs, which do not, it is a standard attribute of a Standard Extended SOP Class (PS3.2 Section 7.3), and the validators’ findings of it there are known differences (D-046). Under any other policy, Recognizable Visual Features is kept as received and never set to NO (D-015).

PS3.15-E.3.3-01#

Source: PS3.15 E.3.3, paragraph. Status: implemented.

When this Option is specified in addition to the Basic Application Level Confidentiality Profile, any information encoded in graphics, text annotations or overlays corresponding to the Attribute information specified to be removed by the Profile and any other Options specified shall also be removed, as described in Table E.1-1.

Decisions: D-041.

Implementation:

  • _dicom/deidentify/clean_graphics.py

  • _dicom/deidentify/element_rules.py

  • _dicom/deidentify/conformance.py

  • _dicom/deidentify/pixel_cleaning.py

Tests:

  • tests/dicom/test_deidentify_clean_graphics.py::test_overlays_are_removed_and_the_instance_claims_clean_graphics

  • tests/dicom/test_deidentify_clean_graphics.py::test_curves_are_removed_and_the_instance_claims_clean_graphics

  • tests/dicom/test_deidentify_clean_graphics.py::test_a_graphic_annotation_sequence_is_removed_where_the_iod_omits_it

  • tests/dicom/test_deidentify_clean_graphics.py::test_an_overlay_that_may_lie_in_pixel_data_sequesters_the_instance

  • tests/dicom/test_deidentify_clean_graphics.py::test_clean_graphics_with_clean_descriptors_claims_both

  • tests/dicom/test_deidentify_clean_graphics.py::test_every_corpus_instance_is_written_as_under_the_basic_profile

  • tests/dicom/test_deidentify_clean_graphics.py::test_the_statement_gives_each_graphics_row_its_basic_profile_action

  • tests/dicom/test_deidentify_embedded_overlays.py::test_clean_graphics_clears_an_overlay_in_unused_bits_and_claims_the_option

  • tests/dicom/test_deidentify_embedded_overlays.py::test_an_overlay_that_cannot_be_cleared_sequesters_the_instance

  • tests/dicom/test_deidentify_embedded_overlays.py::test_an_overlay_in_compressed_pixel_data_sequesters_the_instance

  • tests/dicom/test_deidentify_presentation_states.py::test_every_preset_sequesters_a_presentation_state_with_graphic_annotations

Note: The engine applies the Clean Graphics Option to a custom option set; no preset selects it (D-041). No cleaner of graphics exists yet, so each of the four rows that Table E.1-1 gives C under the option takes its Basic Profile action: Curve Data, Overlay Data, and Overlay Comments are removed, Overlay Data with its overlay group where the IOD’s Overlay Plane Module is user-optional, and Graphic Annotation Sequence takes D, which removes it from every supported IOD but the softcopy Presentation States, where it is Type 1, so one that holds it is sequestered, as under the Basic Profile (D-010, D-021). Every instance written under the option claims it, since E.3.3 specifies what the option removes and Table E.1-1 gives the minimum actions, as for Clean Descriptors (D-009). An overlay group that has no Overlay Data may place its overlay in unused bits of Pixel Data; under the option, that bit plane is cleared above High Bit in native Pixel Data, in the same write as Clean Pixel Data’s redaction, the written Pixel Data is checked to differ from the source in nothing else, and the overlay’s group is removed, so the instance is released with the option’s code (D-041). An instance whose overlay cannot be so cleared, such as one in compressed Pixel Data or within Bits Stored, is sequestered under the option as overlay-in-pixel-data. The conformance statement gives each row its Basic Profile action and why.

PS3.15-E.3.4-01#

Source: PS3.15 E.3.4, paragraph. Status: implemented.

When this Option is specified in addition to the Basic Application Level Confidentiality Profile, any information encoded in SR Content Items or Acquisition Context or Specimen Preparation Sequence Items corresponding to the Attribute information specified to be removed by the Profile and any other Options specified shall also be removed.

Decisions: D-001, D-010, D-047.

Implementation:

  • _dicom/deidentify/content_items.py

  • _dicom/deidentify/removed_items.py

  • _dicom/deidentify/structured_content.py

  • _dicom/deidentify/supplementary_concepts.py

  • _dicom/deidentify/loinc_terms.py

  • _dicom/deidentify/codes.py

  • _dicom/deidentify/walker.py

  • _dicom/deidentify/instance_transform.py

  • _dicom/deidentify/conformance.py

  • _dicom/deidentify/policy.py

Tests:

  • tests/dicom/test_deidentify_content_items.py::test_the_engine_applies_clean_structured_content

  • tests/dicom/test_deidentify_content_items.py::test_a_retired_snomed_code_is_looked_up_as_its_snomed_ct_code

  • tests/dicom/test_deidentify_content_items.py::test_items_whose_concepts_the_table_does_not_list_keep_codes_and_numbers

  • tests/dicom/test_deidentify_content_items.py::test_a_listed_concept_gives_its_value_its_action

  • tests/dicom/test_deidentify_content_items.py::test_an_item_that_cannot_be_kept_gives_its_sequence_its_basic_action

  • tests/dicom/test_deidentify_content_items.py::test_an_x_d_item_is_kept_with_a_dummy_value

  • tests/dicom/test_deidentify_content_items.py::test_modifiers_are_content_items_too

  • tests/dicom/test_deidentify_content_items.py::test_an_unlisted_loinc_concept_is_kept_as_an_unlisted_dicom_one_is

  • tests/dicom/test_deidentify_content_items.py::test_full_dates_keep_a_loinc_edd

  • tests/dicom/test_deidentify_content_items.py::test_specimen_preparation_content_items_are_cleaned

  • tests/dicom/test_deidentify_content_items.py::test_specimen_preparation_with_an_item_to_remove_takes_its_basic_action

  • tests/dicom/test_deidentify_content_items.py::test_content_sequence_items_are_cleaned_at_any_depth

  • tests/dicom/test_deidentify_content_items.py::test_an_item_to_remove_deep_in_a_report_is_removed_on_its_own

  • tests/dicom/test_deidentify_content_items.py::test_an_item_to_remove_beside_a_section_leaves_the_section_kept

  • tests/dicom/test_deidentify_content_items.py::test_a_retired_snomed_code_deep_in_a_report_takes_its_options_action

  • tests/dicom/test_deidentify_content_items.py::test_a_log_whose_entries_cannot_be_shown_in_order_is_not_kept

  • tests/dicom/test_deidentify_content_items.py::test_each_log_entry_takes_the_dummy_datetime_of_its_rank

  • tests/dicom/test_deidentify_content_items.py::test_observation_context_is_not_a_log_entry

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_the_patient_dose_report_names_the_ct_dose_report_as_written

  • tests/dicom/test_deidentify_dose_report_content.py::test_which_presets_with_the_option_keep_a_report_of_required_rows

  • tests/dicom/test_deidentify_dose_report_content.py::test_which_retain_options_with_the_option_keep_a_report_of_required_rows

  • tests/dicom/test_deidentify_dose_report_content.py::test_which_retain_options_keep_a_report_observed_by_a_device

  • tests/dicom/test_deidentify_dose_report_content.py::test_which_presets_keep_a_report_observed_by_a_device

  • tests/dicom/test_deidentify_dose_report_content.py::test_the_optional_rows_that_are_removed_on_their_own

  • tests/dicom/test_deidentify_dose_report_content.py::test_a_kept_x_ray_tree_gives_each_listed_value_its_action

  • tests/dicom/test_deidentify_dose_report_content.py::test_a_kept_tree_of_required_rows_gives_each_listed_value_its_action

  • tests/dicom/test_deidentify_dose_report_content.py::test_a_written_radiopharmaceutical_report_holds_no_source_value

  • tests/dicom/test_deidentify_dose_report_content.py::test_a_report_with_every_optional_row_is_written_without_those_removed

  • tests/dicom/test_deidentify_dose_report_content.py::test_a_written_x_ray_report_keeps_the_references_between_its_uids

  • tests/dicom/test_deidentify_further_dose_report_content.py::test_every_option_set_keeps_a_report_of_required_rows

  • tests/dicom/test_deidentify_further_dose_report_content.py::test_every_preset_with_the_option_keeps_a_report_of_required_rows

  • tests/dicom/test_deidentify_further_dose_report_content.py::test_the_optional_rows_that_are_removed_on_their_own

  • tests/dicom/test_deidentify_further_dose_report_content.py::test_a_kept_tree_gives_each_listed_value_its_action

  • tests/dicom/test_deidentify_further_dose_report_content.py::test_a_written_report_is_released

  • tests/dicom/test_deidentify_further_dose_report_content.py::test_a_report_with_every_optional_row_is_written_without_those_removed

  • tests/dicom/test_deidentify_further_dose_report_content.py::test_a_device_named_as_the_header_names_it_holds_a_report_for_review

  • tests/dicom/test_deidentify_further_dose_report_content.py::test_a_verified_report_is_released_with_a_dummy_verifier

  • tests/dicom/test_deidentify_procedure_report_content.py::test_every_option_set_keeps_a_report_of_required_rows

  • tests/dicom/test_deidentify_procedure_report_content.py::test_an_echo_measurement_coded_in_loinc_is_kept

  • tests/dicom/test_deidentify_procedure_report_content.py::test_the_optional_rows_that_are_removed_on_their_own

  • tests/dicom/test_deidentify_procedure_report_content.py::test_a_kept_tree_gives_each_listed_value_its_action

  • tests/dicom/test_deidentify_procedure_report_content.py::test_a_written_report_holds_no_source_value_and_is_released

  • tests/dicom/test_deidentify_procedure_report_content.py::test_a_written_performed_administration_references_its_plan

  • tests/dicom/test_deidentify_procedure_report_content.py::test_a_udi_is_not_released

  • tests/dicom/test_deidentify_single_item_removal.py::test_a_by_reference_relationship_to_a_removed_item_is_removed_with_it

  • tests/dicom/test_deidentify_single_item_removal.py::test_a_by_reference_relationship_inside_a_removed_item_names_nothing_kept

  • tests/dicom/test_deidentify_single_item_removal.py::test_a_reference_to_an_item_after_a_removed_one_is_renumbered

  • tests/dicom/test_deidentify_single_item_removal.py::test_a_reference_is_renumbered_at_each_level_it_passes

  • tests/dicom/test_deidentify_single_item_removal.py::test_a_reference_to_an_item_before_a_removed_one_is_unchanged

  • tests/dicom/test_deidentify_single_item_removal.py::test_a_section_whose_items_are_all_removed_is_removed_up_the_tree

  • tests/dicom/test_deidentify_single_item_removal.py::test_a_section_left_with_an_item_is_kept

  • tests/dicom/test_deidentify_single_item_removal.py::test_an_item_whose_items_are_all_removed_loses_its_content_sequence

  • tests/dicom/test_deidentify_single_item_removal.py::test_a_root_left_with_no_items_takes_the_tree_out

  • tests/dicom/test_deidentify_single_item_removal.py::test_a_reference_that_names_no_item_takes_the_tree_out_where_an_item_goes

  • tests/dicom/test_deidentify_single_item_removal.py::test_an_item_with_a_modifier_that_cannot_be_kept_is_removed

  • tests/dicom/test_deidentify_single_item_removal.py::test_a_table_cell_that_names_a_removed_item_is_removed_on_its_own

  • tests/dicom/test_deidentify_single_item_removal.py::test_a_table_whose_cells_are_all_removed_is_removed

  • tests/dicom/test_deidentify_single_item_removal.py::test_a_table_cell_is_renumbered_as_a_reference_is

  • tests/dicom/test_deidentify_single_item_removal.py::test_a_concept_modifier_that_cannot_be_kept_removes_the_item_it_modifies

  • tests/dicom/test_deidentify_single_item_removal.py::test_a_section_is_kept_without_a_concept_modifier_that_cannot_be_kept

  • tests/dicom/test_deidentify_single_item_removal.py::test_the_root_is_kept_without_a_concept_modifier_that_cannot_be_kept

  • tests/dicom/test_deidentify_single_item_removal.py::test_a_written_table_keeps_the_cells_that_name_kept_items

  • tests/dicom/test_deidentify_single_item_removal.py::test_a_written_report_numbers_its_references_as_its_items

  • tests/dicom/test_deidentify_single_item_removal.py::test_a_written_report_leaves_out_a_content_sequence_whose_items_are_removed

  • tests/dicom/test_deidentify_specialised_report_content.py::test_which_option_sets_keep_a_report_of_required_rows

  • tests/dicom/test_deidentify_specialised_report_content.py::test_a_macular_grid_report_keeps_its_loinc_coded_rows

  • tests/dicom/test_deidentify_specialised_report_content.py::test_the_optional_rows_that_are_removed_on_their_own

  • tests/dicom/test_deidentify_specialised_report_content.py::test_a_kept_tree_gives_each_listed_value_its_action

  • tests/dicom/test_deidentify_specialised_report_content.py::test_a_written_report_holds_no_source_value_and_is_released

  • tests/dicom/test_deidentify_specialised_report_content.py::test_a_report_with_every_optional_row_is_written_without_those_removed

  • tests/dicom/test_deidentify_specialised_report_content.py::test_a_macular_grid_report_whose_algorithm_maker_is_the_device_s_is_held

  • tests/dicom/test_deidentify_specialised_report_content.py::test_a_written_colon_report_names_the_replaced_study_and_series

  • tests/dicom/test_deidentify_specialised_report_content.py::test_a_written_waveform_annotation_keeps_its_references

  • tests/dicom/test_deidentify_specialised_report_content.py::test_the_log_events_times_take_their_dates_action

  • tests/dicom/test_deidentify_specialised_report_content.py::test_a_written_log_shifts_its_event_times_under_modified_dates

  • tests/dicom/test_deidentify_specialised_report_content.py::test_a_written_log_gives_its_events_increasing_dummy_times_under_basic

  • tests/dicom/test_deidentify_specialised_report_content.py::test_a_log_whose_events_are_out_of_order_is_sequestered

  • tests/dicom/test_deidentify_structured_report_content.py::test_which_presets_with_the_option_keep_a_report_of_required_rows

  • tests/dicom/test_deidentify_structured_report_content.py::test_which_retain_options_with_the_option_keep_a_report_of_required_rows

  • tests/dicom/test_deidentify_structured_report_content.py::test_a_concept_name_that_cannot_be_looked_up_is_removed_on_its_own

  • tests/dicom/test_deidentify_structured_report_content.py::test_a_root_whose_concept_name_cannot_be_looked_up_takes_the_tree_out

  • tests/dicom/test_deidentify_structured_report_content.py::test_a_loinc_concept_name_is_looked_up_as_a_dicom_one_is

  • tests/dicom/test_deidentify_structured_report_content.py::test_a_finding_site_coded_by_its_snomed_ids_is_kept_as_in_snomed_ct

  • tests/dicom/test_deidentify_structured_report_content.py::test_the_optional_rows_that_are_removed_on_their_own

  • tests/dicom/test_deidentify_structured_report_content.py::test_a_kept_tree_gives_each_listed_value_its_action

  • tests/dicom/test_deidentify_structured_report_content.py::test_a_written_report_holds_no_source_value_and_is_released

  • tests/dicom/test_deidentify_structured_report_content.py::test_a_report_with_every_optional_row_is_written_without_those_removed

  • tests/dicom/test_deidentify_structured_report_content.py::test_a_written_cad_report_keeps_its_references

  • tests/dicom/test_deidentify_supplementary_concepts.py::test_each_row_takes_the_actions_of_its_attribute

  • tests/dicom/test_deidentify_supplementary_concepts.py::test_a_characteristic_in_a_dose_report_is_kept_only_with_the_characteristics

  • tests/dicom/test_deidentify_supplementary_concepts.py::test_a_body_mass_index_or_surface_area_in_a_dose_report_is_removed_on_its_own

  • tests/dicom/test_deidentify_supplementary_concepts.py::test_a_udi_is_kept_only_with_device_identity

  • tests/dicom/test_deidentify_supplementary_concepts.py::test_each_preset_writes_a_udi_only_with_device_identity

  • tests/dicom/test_deidentify_supplementary_concepts.py::test_each_subject_row_is_kept_only_where_its_attribute_is

Note: The engine applies the Clean Structured Content Option to the tps-import and public-release presets and to a custom option set (D-047). Of the Structured Reports, the X-Ray, Enhanced X-Ray, Radiopharmaceutical, and Patient Radiation Dose SRs, Basic Text SR, Enhanced SR, Comprehensive SR, Comprehensive 3D SR, Extensible SR, Mammography CAD SR, Chest CAD SR, Colon CAD SR, Procedure Log, Spectacle Prescription Report, Macular Grid Thickness and Volume Report, and Waveform Annotation SR are supported, as are the Key Object Selection Document and the Encapsulated PDF, and the others sequestered (D-010); of the supported IODs, only those eighteen define Content Sequence, at their root, and none Waveform Annotation Sequence, and the Encapsulated PDF is sequestered by its document whatever its tree. Waveform Annotation Sequence takes its Basic Profile action; Content Sequence is cleaned wherever an IOD defines it, at any depth of its nested Content Sequences, with the root content item that holds it, and a report whose tree cannot be kept is sequestered, since the Basic Profile gives Content Sequence D and a sequence has no dummy value (D-021). Acquisition Context Sequence and Specimen Preparation Sequence are kept where the IOD defines them, and each content item is cleaned by its Value Type and concept name, as Table E.3.4-1 gives them, generated for the concept names that DICOM, SNOMED CT, and LOINC code, with each LOINC code’s LOINC name in a reviewed file (D-001). A reviewed list gives the table’s actions to the concepts that it omits where PS3.16’s templates use them for a patient characteristic that Table E.1-1 removes, among them the patient’s height, weight, and body surface area, which PS3.16 codes in LOINC, each removed, and kept under Retain Patient Characteristics where Table E.1-1 keeps the attribute that holds it, to a device’s UDI as a CODE, removed, and kept under Retain Device Identity as Table E.1-1 keeps UDI Sequence (0018,100A), and to Subject ID as a CODE, which TID 1007 gives it and the table lists only as TEXT, removed under every option (D-047). An item whose concept name neither the table nor the list gives keeps its codes and numbers, and its other values take their own rules, as in any kept sequence. A concept name coded by a retired SNOMED designator, SRT, SNM3, or 99SDM, is looked up as the SNOMED CT code that PS3.16 Table O-1 maps it to (D-001). X/D gives D wherever the concept appears, as though a template required the item there, and so do the X rows of DateTime Ended and X-Ray Attenuator Model Data, which Enhanced X-Ray Radiation Dose reports’ templates require; D writes the dummy value of the value’s VR, and gives a UIDREF item’s UID the replacement that U gives, which keeps a registered UID such as a public frame of reference. An item cannot be kept where the selected options remove it, where its concept name is coded in a scheme whose rows the table does not hold, or where it is coded by a retired SNOMED designator with a code that Table O-1 does not map. In Acquisition Context Sequence and Specimen Preparation Sequence, such an item, or one that cannot be read as one Value Type and one concept name, makes the whole sequence take its Basic Profile action. In Content Sequence, such an item is removed on its own, with everything in it, and so is each item that names a removed item by Referenced Content Item Identifier, but for a TABLE, which loses only each such cell and goes only where it loses every cell, each item other than a CONTAINER whose HAS CONCEPT MOD item is removed, and each CONTAINER left with no items; any other item left with no items loses its Content Sequence, which is Type 1C, and each kept Referenced Content Item Identifier is renumbered to name the same item in the output (D-047). A report whose root cannot be kept or is left with no items, whose tree holds an item that cannot be read, or whose tree loses an item while holding a Referenced Content Item Identifier that has no value or names no item of it, is sequestered. The synthetic collection’s five dose reports, the X-Ray Radiation Dose SR’s of a projection radiograph (TID 10001) and of a CT scan with its dose checks (TID 10011 and TID 10015), and the Enhanced X-Ray, Radiopharmaceutical, and Patient Radiation Dose SRs, carry markers in their content-item values and are released only where Clean Structured Content keeps their trees; each value is kept or replaced by its concept’s row of Table E.3.4-1, and the patient dose report’s estimate names the CT dose report by the UID that the written CT dose report carries. Its five general reports, each a TID 2000 or TID 1500 tree with a marker in the value of each PNAME and UIDREF item, are released under the option alone, their Person Observer Names and source series replaced. Its Key Object Selection Document and seven other reports, each built from its IOD’s root template, are released under the option alone too, the Macular Grid Thickness and Volume Report with its findings coded in LOINC. A Procedure Log’s entries, which PS3.3 Section A.35.7.3.1.2 orders by increasing Observation DateTime, take the dummy datetimes of their ranks where the options neither keep nor modify that datetime (D-021); a log whose entries are not in that order is not kept.

PS3.15-E.3.5-01#

Source: PS3.15 E.3.5, paragraph. Status: implemented.

When this Option is specified in addition to the Basic Application Level Confidentiality Profile, any information that is embedded in text or string Attributes corresponding to the Attribute information specified to be removed by the Profile and any other Options specified shall also be removed, as described in Table E.1-1.

Decisions: D-009, D-022, D-036.

Implementation:

  • _dicom/deidentify/descriptor_cleaning.py

  • _dicom/deidentify/roi_names.py

  • _dicom/deidentify/reviewed_roi_names.py

  • _dicom/deidentify/reviewed_names_command.py

  • _dicom/deidentify/reviewed_descriptors.py

  • _dicom/deidentify/reviewed_descriptors_command.py

  • _dicom/deidentify/technique_descriptors.py

  • _dicom/deidentify/common_names.py

  • _dicom/deidentify/run_report.py

  • _dicom/deidentify/run_qc.py

  • _dicom/deidentify/qc_pack.py

Tests:

  • tests/dicom/test_deidentify_api.py::test_an_institutional_list_is_used_as_the_command_line_uses_it

  • tests/dicom/test_deidentify_command.py::test_an_institutional_list_converted_from_csv_is_used

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_every_released_instance_claims_the_policys_options

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_roi_names_take_the_vocabulary_spelling_or_the_reviewed_decision

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_without_a_reviewed_decision_the_structure_set_is_held

  • tests/dicom/test_deidentify_descriptor_agreement.py::test_a_series_whose_values_differ_takes_the_action_without_the_option

  • tests/dicom/test_deidentify_descriptor_agreement.py::test_a_series_whose_values_agree_writes_them_in_every_instance

  • tests/dicom/test_deidentify_descriptor_agreement.py::test_an_identifier_of_one_instance_takes_its_series_value_out_of_all

  • tests/dicom/test_deidentify_descriptor_agreement.py::test_an_instance_without_the_attribute_takes_it_out_of_its_series

  • tests/dicom/test_deidentify_descriptor_agreement.py::test_a_disagreement_in_one_series_leaves_another_series_alone

  • tests/dicom/test_deidentify_descriptor_agreement.py::test_a_value_that_echoes_a_person_is_still_searched_for

  • tests/dicom/test_deidentify_descriptor_agreement.py::test_a_study_descriptor_is_written_alike_across_its_series

  • tests/dicom/test_deidentify_descriptor_agreement.py::test_the_qc_pack_shows_a_disagreeing_value_was_not_applied

  • tests/dicom/test_deidentify_descriptor_agreement.py::test_a_withheld_instance_leaves_out_a_kept_value_whatever_its_series

  • tests/dicom/test_deidentify_descriptor_agreement.py::test_an_instance_of_a_series_that_was_not_prepared_decides_alone

  • tests/dicom/test_deidentify_descriptor_agreement.py::test_an_unprepared_instance_of_a_prepared_series_must_agree

  • tests/dicom/test_deidentify_descriptor_cleaning.py::test_roi_names_are_renamed_or_given_their_reviewed_decision

  • tests/dicom/test_deidentify_descriptor_cleaning.py::test_roi_names_are_cleaned_beside_a_free_text_descriptor

  • tests/dicom/test_deidentify_descriptor_cleaning.py::test_a_roi_name_without_a_decision_holds_the_instance_for_review

  • tests/dicom/test_deidentify_descriptor_cleaning.py::test_the_method_digest_and_the_report_cover_the_reviewed_names

  • tests/dicom/test_deidentify_descriptor_cleaning.py::test_decisions_recorded_after_the_transform_is_made_do_not_apply

  • tests/dicom/test_deidentify_descriptor_cleaning.py::test_a_run_holds_the_structure_set_and_releases_the_rest

  • tests/dicom/test_deidentify_descriptor_cleaning.py::test_the_runs_release_report_counts_its_roi_names_naming_none

  • tests/dicom/test_deidentify_descriptor_cleaning.py::test_an_identical_copy_of_a_structure_set_is_counted_once

  • tests/dicom/test_deidentify_descriptor_cleaning.py::test_a_structure_set_refused_by_descriptor_cleaning_counts_no_name

  • tests/dicom/test_deidentify_descriptor_cleaning.py::test_held_names_are_emptied_where_the_user_chooses_so

  • tests/dicom/test_deidentify_descriptor_cleaning.py::test_a_kept_name_that_echoes_the_patient_is_held

  • tests/dicom/test_deidentify_descriptor_cleaning.py::test_other_descriptors_take_their_basic_profile_action_and_keep_the_claim

  • tests/dicom/test_deidentify_descriptor_cleaning.py::test_a_descriptor_that_the_fallback_keeps_loses_the_claim

  • tests/dicom/test_deidentify_descriptor_cleaning.py::test_the_synthetic_rt_plan_claims_the_option

  • tests/dicom/test_deidentify_descriptor_cleaning.py::test_a_roi_name_that_was_not_collected_sequesters_its_instance

  • tests/dicom/test_deidentify_descriptor_cleaning.py::test_a_run_shows_the_reviewer_the_institutional_names_a_held_name_matched

  • tests/dicom/test_deidentify_descriptor_free_text.py::test_a_technique_descriptor_of_vocabulary_words_is_kept_and_retained

  • tests/dicom/test_deidentify_descriptor_free_text.py::test_a_value_that_neither_tier_cleans_takes_its_action_and_is_listed

  • tests/dicom/test_deidentify_descriptor_free_text.py::test_reviewed_decisions_keep_or_map_a_value_and_take_precedence

  • tests/dicom/test_deidentify_descriptor_free_text.py::test_a_kept_value_that_echoes_the_patient_is_not_cleaned

  • tests/dicom/test_deidentify_descriptor_free_text.py::test_each_value_of_a_multi_valued_descriptor_is_cleaned_alone

  • tests/dicom/test_deidentify_descriptor_free_text.py::test_a_kept_value_beside_a_mapped_or_emptied_one_takes_its_action

  • tests/dicom/test_deidentify_descriptor_free_text.py::test_the_method_digest_and_the_report_cover_the_list_and_the_vocabulary

  • tests/dicom/test_deidentify_descriptor_free_text.py::test_a_withheld_instance_leaves_out_only_the_descriptors_cleaning_would_keep

  • tests/dicom/test_deidentify_descriptor_free_text.py::test_a_kept_descriptor_of_a_withheld_instance_does_not_hold_its_series

  • tests/dicom/test_deidentify_descriptor_free_text.py::test_a_withheld_instance_leaves_out_a_descriptor_only_where_it_would_be_kept

  • tests/dicom/test_deidentify_descriptor_free_text.py::test_a_withheld_instance_keeps_in_a_kept_descriptor_that_its_sequence_may_lose

  • tests/dicom/test_deidentify_descriptor_reports.py::test_the_pack_lists_each_descriptor_with_its_values_and_whether_written

  • tests/dicom/test_deidentify_descriptor_reports.py::test_the_report_counts_descriptors_by_outcome_and_reason_naming_none

  • tests/dicom/test_deidentify_qc_pack.py::test_a_held_roi_name_lists_the_institutional_names_it_matched

  • tests/dicom/test_deidentify_release_report_markdown.py::test_the_roi_names_are_counted_by_outcome_and_held_reason

  • tests/dicom/test_deidentify_release_report_reasons.py::test_roi_names_are_counted_by_outcome_and_held_names_by_reason

  • tests/dicom/test_deidentify_release_report_reasons.py::test_a_run_without_roi_names_counts_none

  • tests/dicom/test_deidentify_release_report_reasons.py::test_roi_name_counts_that_could_hold_a_value_are_refused

  • tests/dicom/test_deidentify_reviewed_descriptors.py::test_reviewed_decisions_are_reused_on_later_runs

  • tests/dicom/test_deidentify_reviewed_descriptors.py::test_the_list_never_shows_a_value

  • tests/dicom/test_deidentify_reviewed_descriptors.py::test_the_list_is_refused_inside_a_protected_directory

  • tests/dicom/test_deidentify_reviewed_descriptors.py::test_a_reviewers_decision_to_keep_map_or_empty_a_value_is_applied

  • tests/dicom/test_deidentify_reviewed_descriptors.py::test_a_value_the_list_does_not_cover_is_held_and_emptied_only_when_chosen

  • tests/dicom/test_deidentify_reviewed_descriptors.py::test_a_reviewed_value_that_echoes_an_identifier_is_held

  • tests/dicom/test_deidentify_reviewed_descriptors_command.py::test_decisions_from_a_csv_start_a_new_list_that_cleaning_applies

  • tests/dicom/test_deidentify_reviewed_descriptors_command.py::test_later_decisions_are_added_and_a_different_one_needs_replace

  • tests/dicom/test_deidentify_reviewed_descriptors_command.py::test_a_value_longer_than_the_csv_modules_default_limit_is_recorded

  • tests/dicom/test_deidentify_reviewed_descriptors_command.py::test_a_malformed_decisions_file_is_refused_and_nothing_is_written

  • tests/dicom/test_deidentify_reviewed_descriptors_command.py::test_the_list_is_refused_inside_a_protected_directory

  • tests/dicom/test_deidentify_reviewed_names_command.py::test_decisions_from_a_csv_start_a_new_list_that_a_run_applies

  • tests/dicom/test_deidentify_reviewed_names_command.py::test_later_decisions_are_added_to_the_list

  • tests/dicom/test_deidentify_reviewed_names_command.py::test_a_different_decision_on_a_listed_name_is_refused_unless_replaced

  • tests/dicom/test_deidentify_reviewed_names_command.py::test_different_decisions_on_one_name_in_the_file_are_refused

  • tests/dicom/test_deidentify_reviewed_names_command.py::test_a_malformed_decisions_file_is_refused_and_nothing_is_written

  • tests/dicom/test_deidentify_reviewed_names_command.py::test_the_list_is_refused_inside_a_protected_directory

  • tests/dicom/test_deidentify_reviewed_roi_names.py::test_reviewed_decisions_are_reused_on_later_runs

  • tests/dicom/test_deidentify_reviewed_roi_names.py::test_the_list_never_shows_a_name

  • tests/dicom/test_deidentify_reviewed_roi_names.py::test_the_list_is_refused_inside_a_protected_directory

  • tests/dicom/test_deidentify_reviewed_roi_names.py::test_a_reviewers_decision_to_keep_map_or_empty_a_name_is_applied

  • tests/dicom/test_deidentify_reviewed_roi_names.py::test_a_bulk_rename_applies_to_every_structure_set_with_the_name

  • tests/dicom/test_deidentify_reviewed_roi_names.py::test_a_name_neither_renamed_nor_reviewed_is_held_with_the_automatic_reason

  • tests/dicom/test_deidentify_reviewed_roi_names.py::test_a_held_name_is_emptied_only_when_the_user_chooses_so

  • tests/dicom/test_deidentify_reviewed_roi_names.py::test_a_reviewed_name_that_echoes_an_identifier_is_held

  • tests/dicom/test_deidentify_reviewed_roi_names.py::test_different_names_that_would_be_written_as_one_name_are_held

  • tests/dicom/test_deidentify_reviewed_roi_names.py::test_the_review_queue_lists_each_distinct_held_name_once

  • tests/dicom/test_deidentify_reviewed_roi_names.py::test_the_keyed_digest_is_the_hmac_of_the_lists_canonical_form

  • tests/dicom/test_deidentify_reviewed_roi_names.py::test_a_snapshot_keeps_the_decisions_as_they_stood_and_is_never_saved

  • tests/dicom/test_deidentify_reviewed_roi_names.py::test_any_change_to_the_list_changes_the_keyed_digest

  • tests/dicom/test_deidentify_reviewed_roi_names.py::test_the_report_counts_give_held_names_by_reason_and_names_by_outcome

  • tests/dicom/test_deidentify_reviewed_roi_names.py::test_a_held_name_shows_the_reviewer_the_institutional_names_it_matches

  • tests/dicom/test_deidentify_reviewed_roi_names.py::test_an_exact_match_of_an_institutional_name_is_never_written_unreviewed

  • tests/dicom/test_deidentify_roi_names.py::test_a_name_that_matches_once_case_whitespace_and_separators_are_disregarded_is_written_in_the_vocabularys_spelling

  • tests/dicom/test_deidentify_roi_names.py::test_a_near_match_goes_to_review

  • tests/dicom/test_deidentify_roi_names.py::test_a_name_with_a_leading_underscore_or_hyphen_goes_to_review

  • tests/dicom/test_deidentify_roi_names.py::test_a_name_that_matches_more_than_one_vocabulary_name_goes_to_review

  • tests/dicom/test_deidentify_roi_names.py::test_different_names_that_would_be_written_as_one_vocabulary_name_go_to_review

  • tests/dicom/test_deidentify_roi_names.py::test_a_name_that_echoes_a_known_identifier_goes_to_review

  • tests/dicom/test_deidentify_roi_names.py::test_a_nomenclature_without_the_tg263_attribution_is_rejected

  • tests/dicom/test_deidentify_roi_names.py::test_a_vocabulary_name_with_a_hyphen_is_not_matched_without_it

  • tests/dicom/test_deidentify_roi_names.py::test_an_unpublished_vocabulary_is_refused

  • tests/dicom/test_deidentify_roi_names.py::test_an_institutional_list_is_matched_as_the_automatic_tier_matches

  • tests/dicom/test_deidentify_run_report.py::test_roi_names_are_counted_from_each_structure_sets_qc_material

  • tests/dicom/test_deidentify_run_report.py::test_the_report_counts_the_runs_roi_names_without_naming_them

  • tests/dicom/test_deidentify_technique_descriptors.py::test_technique_values_made_of_vocabulary_words_are_kept

  • tests/dicom/test_deidentify_technique_descriptors.py::test_values_with_names_dates_numbers_or_other_words_are_not_kept

  • tests/dicom/test_deidentify_technique_descriptors.py::test_study_description_comments_and_diagnoses_are_never_kept_automatically

  • tests/dicom/test_deidentify_technique_descriptors.py::test_cleaning_keeps_vocabulary_values_and_holds_echoes

  • tests/dicom/test_deidentify_technique_descriptors.py::test_every_clock_time_written_with_hour_and_minute_units_is_held

  • tests/dicom/test_deidentify_technique_descriptors.py::test_a_reviewers_decision_takes_precedence_over_the_vocabulary

Note: The first supported release includes the Option for ROI names (D-009). The automatic tier decides, for the ROI names of one structure set, which are written in the spelling of the TG-263 spreadsheet’s published edition, whose entries’ digest the module records, because they match one of its names once case, spaces, and the separators _ and - are disregarded (a hyphen in a vocabulary name, which TG-263 writes for a subtraction, must be matched by one, and a name starting with _, which TG-263 gives a structure not used for dose evaluation, or - goes to review), and which go to review, because they match none or several, echo a known patient or other person identifier, or would duplicate a differently spelt name of the same structure set. A name so written is cleaned, not retained, so it needs no review before the claim. The reviewed-names list, which the custodian keeps outside output and QC directories and which names no value in its errors or repr, records a reviewer’s decision to keep, map, or empty each other name, for reuse on later runs. The reviewed tier applies the list’s decisions to the names the automatic tier does not rename; checks what a kept or mapped name would write for echoes and for duplicates, ignoring case, as the automatic tier does; holds every other name, or empties it where the user chooses so explicitly; and collects the distinct held names for review, with counts by reason that carry no name, and counts every name by what is written for it. The list’s keyed digest, an HMAC under the key in a domain of its own, which reveals no name, is an input to the method digest and recorded in the release report (D-024). In a run, descriptor cleaning applies these decisions to the ROI Names of each instance, holds an instance with a held name for review, or empties the name where the user chooses so and then does not claim the Option for it; gives every other attribute given C its action without the Option, and still claims the Option where that action removes or replaces each, since this Option specifies what is removed, not what is retained; and gives the QC pack what it wrote for each ROI Name, or why it held it; the run’s method digest and release report take the keyed digest, under the de-identification key, of the list’s decisions as they stood when the run’s transform was made, which are the decisions it applies, and the report counts the instances held for review by reason. A private command records a reviewer’s decisions from a CSV file in the list, all or nothing, refusing a decision that differs from the list’s unless told to replace it. The release report counts the ROI Names of the structure sets that descriptor cleaning cleaned, each by its outcome, and the distinct names sent for review by reason, whether then held or emptied unreviewed, without a name. A run can also be given an institutional list converted from CSV, as the maintainer decided on 6 October 2026; it renames nothing, and the QC pack shows the reviewer of a held name the list’s names it matched. A ROI Name kept or mapped by a reviewer’s decision in the reviewed-names list has passed pooled human review, since each distinct name is reviewed once for the collection. A reviewed descriptors list, kept as the reviewed-names list is and filled by a private command of its own, records a reviewer’s decision to keep, map, or empty one value of each other text attribute given C, and the reviewed tier for those values checks what a kept or mapped value would write for echoes of identifiers, also within its letters and digits, and holds every other value. For the technique descriptors of D-036, such as Series Description and Protocol Name, an automatic tier keeps a value that the list has no decision on where every word of it is in the reviewed technique vocabulary, which leaves out common personal names that are not reviewed exceptions, and it echoes no identifier. In a run, descriptor cleaning applies both tiers to each such descriptor whose decoded value it collected in its dictionary VR, each value of a multi-valued VR alone, and writes the element with what they give only where every value is cleaned, at least one has text, and no kept value is beside a mapped or emptied one; every other such element takes its action without the Option, and still claims it where that action removes or replaces it. A kept value is left out of the residual search and listed among the retained strings; the QC pack lists each element’s values, outcomes, and whether it was written; the release report counts them without a value; and the method digest takes the list’s keyed digest and the vocabulary’s SHA-256 (D-024). An instance withheld before cleaning leaves the descriptors that cleaning would keep out of its subject’s search in the same way, so they do not hold the rest of its series.

PS3.15-E.3.5-02#

Source: PS3.15 E.3.5, paragraph. Status: implemented.

The manner of cleaning shall be described in the Conformance Statement.

Decisions: D-009, D-036.

Implementation:

  • _dicom/deidentify/conformance.py

  • _dicom/deidentify/conformance_markdown.py

Tests:

  • tests/dicom/test_deidentify_conformance.py::test_cleaning_is_pending_only_for_a_policy_that_cleans

  • tests/dicom/test_deidentify_conformance.py::test_the_first_supported_releases_presets_have_complete_statements

  • tests/dicom/test_deidentify_conformance.py::test_every_listed_action_is_the_one_the_engine_applies

  • tests/dicom/test_deidentify_conformance_roi_names.py::test_roi_name_cleaning_is_described_only_where_roi_name_is_cleaned

  • tests/dicom/test_deidentify_conformance_roi_names.py::test_without_a_vocabulary_no_roi_name_is_renamed_automatically

  • tests/dicom/test_deidentify_conformance_roi_names.py::test_an_unpublished_vocabulary_renames_no_roi_name

  • tests/dicom/test_deidentify_conformance_roi_names.py::test_a_published_vocabulary_is_named_with_its_edition

  • tests/dicom/test_deidentify_conformance_roi_names.py::test_the_described_automatic_renaming_is_the_engines

  • tests/dicom/test_deidentify_conformance_roi_names.py::test_every_review_reason_is_described

  • tests/dicom/test_deidentify_conformance_roi_names.py::test_every_reviewer_decision_and_outcome_is_described

  • tests/dicom/test_deidentify_conformance_roi_names.py::test_the_manner_of_cleaning_roi_names_is_described_not_pending

  • tests/dicom/test_deidentify_conformance_roi_names.py::test_each_other_attribute_given_c_is_described_by_its_fallback_action

  • tests/dicom/test_deidentify_conformance_roi_names.py::test_the_described_fallback_is_the_maintainers_decision

  • tests/dicom/test_deidentify_conformance_roi_names.py::test_the_described_holding_of_a_roi_name_is_the_engines

  • tests/dicom/test_deidentify_conformance_roi_names.py::test_the_described_condition_for_the_clean_descriptors_code_is_the_engines

  • tests/dicom/test_deidentify_conformance_roi_names.py::test_every_preset_with_clean_descriptors_has_a_fallback

  • tests/dicom/test_deidentify_conformance_roi_names.py::test_the_described_hyphen_and_reverse_order_matching_is_the_engines

  • tests/dicom/test_deidentify_conformance_roi_names.py::test_the_described_echo_check_is_the_engines

  • tests/dicom/test_deidentify_conformance_roi_names.py::test_the_described_checks_of_reviewed_names_are_the_engines

  • tests/dicom/test_deidentify_conformance_roi_names.py::test_a_run_with_a_reviewed_list_records_a_different_digest

  • tests/dicom/test_deidentify_conformance_roi_names.py::test_the_described_institutional_list_is_the_engines

  • tests/dicom/test_deidentify_descriptor_cleaning.py::test_other_descriptors_take_their_basic_profile_action_and_keep_the_claim

  • tests/dicom/test_deidentify_descriptor_reports.py::test_the_conformance_statement_describes_free_text_cleaning

Note: The conformance statement describes how ROI Name (3006,0026) is cleaned under Clean Descriptors: automatic renaming against the published TG-263 edition that it names, or none without such a vocabulary, the reasons a name goes to review, the reviewers’ decisions, the checks of what a reviewed decision would write, that a run’s method digest covers its reviewed list (D-024), and what cleaning writes for each name, including that a held name holds its instance for review. It also says that a run can be given an institutional list of ROI names, which renames nothing and only shows the reviewer of a held name the list’s names it matched. It describes how free-text descriptors are cleaned: the reviewed descriptors list’s decisions, the technique descriptors and the rules by which the technique vocabulary keeps a value (D-036), the echo checks, and when an element is written with what cleaning gives it. Every other attribute given C under Clean Descriptors, and a free-text descriptor that cleaning does not write, takes the action that the policy gives it without that option, as the maintainer decided on 6 October 2026 (D-009); the statement lists that action for each, at each place, as the one the engine applies, so for basic-clean-descriptors it lists no manner of cleaning as pending. The manner of cleaning an attribute to which the policy gives C without Clean Descriptors too and that no cleaner cleans is listed as pending until its option is implemented; the dates and times under Modified Dates are described with their cleaner (PS3.15-E.3.6-04), and the free text that Retain Patient Characteristics gives C is described with the Option’s cleaner, which settles Allergies, Patient State, and Sex Parameters for Clinical Use Category Comment before Clean Descriptors where both are selected, and leaves each value to the reviewed descriptors list, which decides on Pre-Medication and Special Needs too (PS3.15-E.3.7-02).

PS3.15-E.3.6-01#

Source: PS3.15 E.3.6, paragraph. Status: implemented.

When the Retain Longitudinal Temporal Information With Full Dates Option is specified in addition to the Basic Application Level Confidentiality Profile, any dates and times present in the Attributes shall be retained, as described in Table E.1-1. The Attribute Longitudinal Temporal Information Modified (0028,0303) shall be added to the Data Set with a Value of "UNMODIFIED".

Decisions: D-012, D-023, D-042, D-043.

Implementation:

  • _dicom/deidentify/actions.py

  • _dicom/deidentify/markers.py

  • _dicom/deidentify/option_kept.py

  • _dicom/deidentify/policy.py

Tests:

  • tests/dicom/test_deidentify_full_dates.py::test_full_dates_and_modified_dates_together_are_rejected

  • tests/dicom/test_deidentify_full_dates.py::test_every_date_and_time_is_kept_unmodified_and_released

  • tests/dicom/test_deidentify_full_dates.py::test_a_stricter_marker_from_an_earlier_run_is_kept

  • tests/dicom/test_deidentify_full_dates.py::test_the_statement_describes_kept_dates_and_needs_nothing_else

Note: A custom policy can select Full Dates, which keeps every date and time that Table E.1-1 gives K under it and the supplementary rules keep under it (D-023); it cannot be selected with Modified Dates. Longitudinal Temporal Information Modified is UNMODIFIED, unless MODIFIED or REMOVED is already present from an earlier run, which is kept because the dates that run modified or removed are not restored (D-012). A removed or replaced source value, other than the kept element’s own, that equals the whole of a kept date is explained by it and does not withhold the instance, and so is a removed datetime whose date equals the whole of a kept date, and a removed date, or a removed datetime’s date, that is the date that begins a kept datetime, such as a vendor’s private scan time of the day of a kept Radiopharmaceutical Start DateTime; one found inside a longer kept value, such as a removed datetime written to the minute at the start of a kept datetime, still does (D-042, D-043).

PS3.15-E.3.6-02#

Source: PS3.15 E.3.6, paragraph. Status: implemented.

When the Retain Longitudinal Temporal Information With Modified Dates Option is specified in addition to the Basic Application Level Confidentiality Profile, any dates and times present in the Attributes listed in Table E.1-1 shall be modified. The modification of the dates and times shall be performed in a manner that:

- aggregates or transforms dates so as to reduce the possibility of matching for re-identification
- preserves the gross longitudinal temporal relationships between images obtained on different dates to the extent necessary for the application
- preserves the fine temporal relationships between images and real-world events to the extent necessary for analysis of the images for the application

Decisions: D-004, D-006, D-007, D-034, D-039.

Implementation:

  • _dicom/deidentify/cleaners.py

  • _dicom/deidentify/dates.py

  • _dicom/deidentify/instance_transform.py

  • _dicom/deidentify/modified_dates.py

  • _dicom/deidentify/temporal_roles.py

Tests:

  • tests/dicom/test_deidentify_dates.py::test_an_offset_is_52_to_520_whole_weeks_and_never_zero

  • tests/dicom/test_deidentify_dates.py::test_shifting_preserves_intervals_and_weekdays

  • tests/dicom/test_deidentify_dates.py::test_a_datetime_moves_its_date_and_keeps_its_time_and_utc_offset

  • tests/dicom/test_deidentify_dates.py::test_local_times_keep_their_interval_to_values_with_their_own_offset

  • tests/dicom/test_deidentify_dates.py::test_converting_to_local_time_keeps_every_interval

  • tests/dicom/test_deidentify_dates.py::test_frame_origin_timestamps_keep_their_intervals

  • tests/dicom/test_deidentify_modified_dates.py::test_subject_dates_move_back_by_whole_weeks_keeping_intervals_and_weekdays

  • tests/dicom/test_deidentify_modified_dates.py::test_times_are_kept_and_left_out_of_the_search

  • tests/dicom/test_deidentify_modified_dates.py::test_a_sources_decay_interval_to_treatment_is_kept

  • tests/dicom/test_deidentify_modified_dates.py::test_datetimes_move_their_date_in_local_time_with_the_nominal_offset

  • tests/dicom/test_deidentify_modified_dates.py::test_device_vocabulary_and_other_values_take_d_dummy_values

  • tests/dicom/test_deidentify_modified_dates.py::test_an_instance_is_written_with_every_date_modified_and_released

  • tests/dicom/test_deidentify_policy.py::test_tps_import_modifies_the_eleven_device_identity_and_date_conflicts

  • tests/dicom/test_deidentify_profiles.py::test_profiles_persist_across_runs

  • tests/dicom/test_deidentify_profiles.py::test_a_stored_offset_wins_over_a_changed_derivation

  • tests/dicom/test_deidentify_project_key_runs.py::test_both_runs_move_the_subjects_dates_by_one_offset

  • tests/dicom/test_deidentify_project_key_runs.py::test_the_other_studies_keep_their_intervals

  • tests/dicom/test_deidentify_project_keys.py::test_dates_move_by_the_offset_recorded_in_the_subjects_profile

  • tests/dicom/test_deidentify_project_keys.py::test_a_later_run_under_the_project_key_extends_the_earlier_one

  • tests/dicom/test_deidentify_residual_shifts.py::test_a_shifted_date_that_equals_another_source_date_is_released

  • tests/dicom/test_deidentify_temporal_roles.py::test_every_temporal_attribute_in_the_dictionary_has_exactly_one_role

  • tests/dicom/test_deidentify_temporal_roles.py::test_every_attribute_modified_dates_cleans_has_a_role

  • tests/dicom/test_deidentify_temporal_roles.py::test_modified_dates_shifts_subject_events_and_normalises_time_zones

Note: Under Modified Dates, a cleaner of the whole instance modifies every value of the 184 attributes that Table E.1-1 and the supplementary rules give C, by its attribute’s reviewed temporal role (D-007, D-039). A subject-event or radiation-source DA or DT value moves back by the subject’s whole-week offset, derived from the key and the subject’s identity at first export, which keeps intervals, weekdays, times, and decay intervals; a TM value is kept, since whole weeks keep the time of day (D-006); and Frame Origin Timestamp moves its seconds. Device, vocabulary-version, and other values take D’s dummy value, which discloses nothing of the offset; time zone offsets become a nominal +0000 once each DT value with its own offset is in the instance’s local time, which keeps every interval within the instance. A value that cannot be modified so, such as a DT value without a full date, sequesters its instance. Under tps-import, the eleven device and beam hold dates and times that Retain Device Identity would keep are modified by their role instead. The offset is the one the subject’s profile records (D-004): under a run-scoped key, consistent within a run only; under a project key, read back from the subject profile store in every later run, once project keys are enabled.

PS3.15-E.3.6-03#

Source: PS3.15 E.3.6, paragraph. Status: implemented.

The Attribute Longitudinal Temporal Information Modified (0028,0303) shall be added to the Data Set with a Value of "MODIFIED".

Decisions: D-012, D-039.

Implementation:

  • _dicom/deidentify/instance_transform.py

  • _dicom/deidentify/markers.py

Tests:

  • tests/dicom/test_deidentify_instance_transform.py::test_each_output_carries_the_de_identification_markers

  • tests/dicom/test_deidentify_markers.py::test_each_preset_adds_exactly_its_markers

  • tests/dicom/test_deidentify_markers.py::test_a_custom_option_set_with_modified_dates_marks_dates_as_modified

  • tests/dicom/test_deidentify_markers.py::test_temporal_information_modified_keeps_the_stricter_value

  • tests/dicom/test_deidentify_modified_dates.py::test_an_instance_is_written_with_every_date_modified_and_released

Note: Every policy that applies Modified Dates, including tps-import, sets Longitudinal Temporal Information Modified to MODIFIED in its markers, unless REMOVED is already present from an earlier run, which is kept because the dates that run removed are not restored (D-012). The run’s instance transform adds it to each instance it de-identifies, which under Modified Dates is written only once every date and time in it is modified (D-039).

PS3.15-E.3.6-04#

Source: PS3.15 E.3.6, paragraph. Status: implemented.

The manner of date modification shall be described in the Conformance Statement.

Decisions: D-006, D-007, D-039.

Implementation:

  • _dicom/deidentify/conformance.py

  • _dicom/deidentify/conformance_values.py

  • _dicom/deidentify/modified_dates.py

Tests:

  • tests/dicom/test_deidentify_conformance_values.py::test_dates_under_modified_dates_are_modified_by_role_as_described

  • tests/dicom/test_deidentify_modified_dates.py::test_the_statement_describes_how_dates_are_modified_and_needs_nothing_else

Note: The statement of a policy that selects Modified Dates gives the attributes that its cleaner modifies and the cleaner’s method: the offset’s range and derivation, how each role’s values are modified, how local time is chosen, and which values sequester their instance; under Dates and times, it counts the attributes given C by role (D-039).

PS3.15-E.3.7-01#

Source: PS3.15 E.3.7, paragraph. Status: implemented.

When this Option is specified in addition to the Basic Application Level Confidentiality Profile, information about age, sex, height and weight and other characteristics present in the Attributes shall be retained, as described in Table E.1-1.

Decisions: D-009, D-011, D-012, D-022, D-034, D-038, D-042.

Implementation:

  • _dicom/deidentify/cleaners.py

  • _dicom/deidentify/descriptor_cleaning.py

  • _dicom/deidentify/instance_transform.py

  • _dicom/deidentify/patient_characteristics.py

  • _dicom/deidentify/release_gate.py

  • _dicom/deidentify/reviewed_descriptors.py

Tests:

  • tests/dicom/test_deidentify_kept_text_names.py::test_a_patients_name_equal_to_a_kept_ethnic_group_goes_to_review

  • tests/dicom/test_deidentify_patient_characteristics.py::test_the_option_keeps_the_patients_characteristics

  • tests/dicom/test_deidentify_patient_characteristics.py::test_the_option_keeps_the_sex_parameters_and_writes_an_empty_comment_empty

Note: The fourteen attributes that the Option gives K are kept as read, wherever they are in the data set, with the Option’s code (D-038). Its five attributes of free text, which it gives C, are written with no value where they have none. Where the policy also selects Clean Descriptors, as tps-import does, each other value is kept, mapped, or emptied by a reviewer’s decision in the custodian’s reviewed descriptors list, and an instance with a value that the list does not clean is held for review, as for ROI Names (D-009); without Clean Descriptors, such an instance is sequestered. So a released instance retains every characteristic it held, its free text as a reviewer decided (PS3.15-E.3.7-02). A removed person name found in a kept characteristic, such as a family name equal to the kept Ethnic Group, holds the instance for review rather than withholding it, and is never explained (D-042). The measures of the body that Table E.1-1 omits, such as Patient’s Body Mass Index, are removed under every option (D-022). Of the presets, only tps-import selects the Option, and it is not yet enabled.

PS3.15-E.3.7-02#

Source: PS3.15 E.3.7, paragraph. Status: implemented.

The manner of cleaning of retained Attributes shall be described in the Conformance Statement.

Decisions: D-009, D-034, D-038.

Implementation:

  • _dicom/deidentify/cleaners.py

  • _dicom/deidentify/conformance.py

  • _dicom/deidentify/conformance_markdown.py

  • _dicom/deidentify/descriptor_cleaning.py

  • _dicom/deidentify/patient_characteristics.py

Tests:

  • tests/dicom/test_deidentify_patient_characteristics.py::test_every_attribute_that_the_option_gives_c_has_a_cleaner

  • tests/dicom/test_deidentify_patient_characteristics.py::test_an_attribute_of_free_text_with_a_value_holds_its_instance

  • tests/dicom/test_deidentify_patient_characteristics.py::test_a_sex_parameters_comment_with_a_value_holds_its_instance

  • tests/dicom/test_deidentify_patient_characteristics.py::test_an_attribute_of_free_text_with_no_value_is_written_with_none

  • tests/dicom/test_deidentify_patient_characteristics.py::test_under_clean_descriptors_too_empty_values_are_written_with_none

  • tests/dicom/test_deidentify_patient_characteristics.py::test_under_clean_descriptors_a_reviewed_decision_settles_the_free_text

  • tests/dicom/test_deidentify_patient_characteristics.py::test_under_clean_descriptors_an_unreviewed_value_holds_the_instance

  • tests/dicom/test_deidentify_patient_characteristics.py::test_an_unreviewed_sex_parameters_comment_holds_the_instance

  • tests/dicom/test_deidentify_patient_characteristics.py::test_a_decision_that_echoes_an_identifier_holds_the_instance

  • tests/dicom/test_deidentify_patient_characteristics.py::test_a_kept_value_beside_a_mapped_one_sequesters_the_instance

  • tests/dicom/test_deidentify_patient_characteristics.py::test_without_clean_descriptors_a_reviewed_value_still_sequesters

  • tests/dicom/test_deidentify_patient_characteristics.py::test_the_run_reports_an_instance_held_for_its_free_text_naming_no_value

  • tests/dicom/test_deidentify_patient_characteristics.py::test_the_statement_describes_how_the_free_text_is_settled

Note: The statement of a policy that selects the Option lists the fourteen attributes that it keeps, with K, and gives the five that it gives C with their method: an attribute with no value is written with none; without Clean Descriptors, no other value is written and its instance is sequestered; with Clean Descriptors, each value takes a reviewer’s decision from the reviewed descriptors list, checked for echoes of identifiers, and an instance with a value that the list does not clean is held for review (D-038). No method cleans their free text automatically, and the automatic tier of technique descriptors does not apply to them; the maintainer decided on 25 October 2026 that the reviewed descriptors list decides on them (D-038).

PS3.15-E.3.8-01#

Source: PS3.15 E.3.8, paragraph. Status: implemented.

When this Option is specified in addition to the Basic Application Level Confidentiality Profile, information about the identity of the device in the Attributes shall be retained, as described in Table E.1-1.

Decisions: D-003, D-007, D-022, D-034, D-042.

Implementation:

  • _dicom/deidentify/ae_titles.py

  • _dicom/deidentify/cleaners.py

  • _dicom/deidentify/instance_transform.py

  • _dicom/deidentify/option_kept.py

  • _dicom/deidentify/policy.py

Tests:

  • tests/dicom/test_deidentify_cleaners.py::test_every_attribute_that_retain_device_identity_gives_c_has_a_cleaner

  • tests/dicom/test_deidentify_cleaners.py::test_retain_device_identity_keeps_the_device_and_tokenises_ae_titles

  • tests/dicom/test_deidentify_cleaners.py::test_ae_titles_are_cleaned_before_descriptors_fall_back

  • tests/dicom/test_deidentify_option_kept.py::test_a_station_named_as_its_ae_title_and_in_private_values_is_released

  • tests/dicom/test_deidentify_option_kept.py::test_a_station_named_after_its_removed_institution_goes_to_review

Note: The attributes that the Option gives K are kept, and the eleven AE titles that it gives C are each replaced by a keyed token, the same for the same application entity throughout a run (D-034). A removed AE title or private value found in a text attribute that the Option keeps, such as an AE title equal to the Station Name, does not send the file to review (D-042). tps-import also selects Modified Dates, which conflicts with this Option for device dates, so its output claims neither (D-007).

PS3.15-E.3.9-01#

Source: PS3.15 E.3.9, paragraph. Status: implemented.

When this Option is specified in addition to an Application Level Confidentiality Profile, UIDs shall be retained, as described in Table E.1-1.

Decisions: D-003, D-040, D-042.

Implementation:

  • _dicom/deidentify/instance_transform.py

  • _dicom/deidentify/option_kept.py

  • _dicom/deidentify/policy.py

  • _dicom/deidentify/release_gate.py

  • _dicom/deidentify/residual_exemptions.py

  • _dicom/deidentify/retained_uids.py

  • _dicom/deidentify/written_references.py

Tests:

  • tests/dicom/test_deidentify_removed_uids.py::test_a_removed_private_copy_of_a_kept_uid_is_explained

  • tests/dicom/test_deidentify_removed_uids.py::test_another_removed_value_of_the_copy_not_in_the_kept_uid_is_no_obstacle

  • tests/dicom/test_deidentify_removed_uids.py::test_a_uid_that_a_removed_private_copy_does_not_explain_still_withholds

  • tests/dicom/test_deidentify_removed_uids.py::test_a_removed_private_copy_anywhere_else_still_withholds

  • tests/dicom/test_deidentify_removed_uids.py::test_a_replaced_reference_to_a_kept_sop_instance_still_withholds

  • tests/dicom/test_deidentify_removed_uids.py::test_under_retain_uids_a_removed_private_copy_of_the_frame_withholds_nothing

  • tests/dicom/test_deidentify_retain_uids.py::test_the_engine_applies_the_option

  • tests/dicom/test_deidentify_retain_uids.py::test_the_kept_uids_are_the_tables_uids_under_the_option

  • tests/dicom/test_deidentify_retain_uids.py::test_the_rules_keep_the_tables_uids_and_replace_every_other

  • tests/dicom/test_deidentify_retain_uids.py::test_an_instance_keeps_its_uids_and_references_and_claims_the_option

  • tests/dicom/test_deidentify_retain_uids.py::test_a_kept_uid_in_a_removed_sequence_is_left_out_of_the_search

  • tests/dicom/test_deidentify_retain_uids.py::test_a_collection_keeps_its_uids_and_still_refers_to_itself

  • tests/dicom/test_deidentify_unwritten_sources.py::test_a_uid_from_unwritten_instances_alone_in_a_kept_uid_is_explained

  • tests/dicom/test_deidentify_unwritten_sources.py::test_a_uid_that_a_written_instance_or_no_option_explains_still_withholds

  • tests/dicom/test_deidentify_unwritten_sources.py::test_a_uid_from_unwritten_instances_anywhere_else_still_withholds

  • tests/dicom/test_deidentify_unwritten_sources.py::test_under_retain_uids_a_sequestered_reports_study_uid_withholds_nothing

Note: The 55 UIDs that Table E.1-1 gives K under the Option are kept wherever they occur, and its five reference sequences are kept as containers whose items’ elements take their own rules; every other UID is replaced with the run’s key, including those the table omits, such as Multi-frame Source SOP Instance UID (D-040). Media Storage SOP Instance UID is the kept SOP Instance UID, output files are named by the kept UIDs’ keyed replacements, the reference graph’s second pass expects the kept UIDs, and the residual search leaves a kept UID out where its enclosing sequence is removed. A removed UID that only instances the run does not write held, such as a sequestered dose report’s scope of accumulation, does not withhold a sibling that keeps it, while a reference that a written instance replaced still withholds the instances that keep the UID it names (D-042). Nor does a private UID that every instance which held it removed, such as a vendor’s private copy of a PET image’s kept Frame of Reference UID, equal to a whole kept UID, while a private copy that a written instance holds replaced, one of which the kept UID is only a part, and every standard UID source still withhold (D-042). No preset selects the Option.

PS3.15-E.3.10-01#

Source: PS3.15 E.3.10, paragraph. Status: implemented.

When this Option is specified in addition to the Basic Application Level Confidentiality Profile, Private Attributes that are known by the de-identifier to be safe from identity leakage shall be retained, together with the Private Creator IDs that are required to fully define the retained Private Attributes; all other Private Attributes shall be removed or processed in the element-specific manner recommended by Deidentification Action (0008,0307), if present within Private Data Element Characteristics Sequence (0008,0300) (see PS3.3 Section C.12.1).

Decisions: D-001, D-028, D-035.

Implementation:

  • _dicom/deidentify/descriptor_cleaning.py

  • _dicom/deidentify/element_rules.py

  • _dicom/deidentify/retained_private.py

  • _dicom/deidentify/reviewed_descriptors.py

  • _dicom/deidentify/safe_private.py

  • _dicom/deidentify/tcia_private_tags.py

  • _dicom/deidentify/walker.py

Tests:

  • tests/dicom/test_deidentify_declared_private.py::test_a_block_declared_safe_keeps_what_the_handling_of_each_vr_keeps

  • tests/dicom/test_deidentify_declared_private.py::test_without_a_declaration_an_unlisted_block_is_removed

  • tests/dicom/test_deidentify_declared_private.py::test_a_mixed_block_keeps_only_its_nonidentifying_elements

  • tests/dicom/test_deidentify_declared_private.py::test_an_element_declared_possibly_identifying_is_removed_despite_its_rule

  • tests/dicom/test_deidentify_declared_private.py::test_a_reviewed_rule_still_settles_an_element_that_is_declared_safe

  • tests/dicom/test_deidentify_declared_private.py::test_a_declaration_that_does_not_declare_the_block_safe_keeps_nothing

  • tests/dicom/test_deidentify_declared_private.py::test_a_declared_element_is_removed_where_its_vr_is_unknown_or_disputed

  • tests/dicom/test_deidentify_declared_private.py::test_definitions_that_disagree_remove_the_element

  • tests/dicom/test_deidentify_declared_private.py::test_a_declared_number_has_at_most_the_values_its_vm_allows

  • tests/dicom/test_deidentify_declared_private.py::test_a_declared_uid_has_at_most_the_values_its_vm_allows

  • tests/dicom/test_deidentify_declared_private.py::test_an_element_that_no_item_defines_has_one_value

  • tests/dicom/test_deidentify_declared_private.py::test_a_declared_value_that_is_not_valid_for_its_vr_is_removed

  • tests/dicom/test_deidentify_declared_private.py::test_a_declaration_applies_to_its_block_in_the_items_of_a_sequence

  • tests/dicom/test_deidentify_declared_private.py::test_a_declaration_in_the_items_of_a_sequence_is_ignored_and_removed

  • tests/dicom/test_deidentify_declared_private.py::test_the_option_keeps_declared_attributes_end_to_end

  • tests/dicom/test_deidentify_declared_private.py::test_a_declared_vendor_block_is_released_and_another_reviewed

  • tests/dicom/test_deidentify_private_text.py::test_the_plan_cleans_private_text_only_under_clean_descriptors_with_its_vr

  • tests/dicom/test_deidentify_private_text.py::test_private_text_written_as_un_is_removed

  • tests/dicom/test_deidentify_private_text.py::test_private_text_that_a_reviewer_keeps_is_kept_with_its_creator

  • tests/dicom/test_deidentify_private_text.py::test_private_text_with_more_values_than_its_vm_is_removed

  • tests/dicom/test_deidentify_private_text.py::test_a_reviewer_s_mapping_is_written

  • tests/dicom/test_deidentify_private_text.py::test_a_decision_applies_wherever_the_creator_reserves_its_block

  • tests/dicom/test_deidentify_private_text.py::test_private_text_that_no_reviewer_decided_on_is_removed_and_listed

  • tests/dicom/test_deidentify_private_text.py::test_a_creator_removed_with_its_text_is_not_listed_as_retained

  • tests/dicom/test_deidentify_private_text.py::test_a_creator_is_kept_for_what_else_its_block_keeps

  • tests/dicom/test_deidentify_private_text.py::test_kept_text_that_echoes_the_patient_is_removed

  • tests/dicom/test_deidentify_private_text.py::test_private_text_that_cannot_be_read_as_its_character_set_is_removed

  • tests/dicom/test_deidentify_private_text.py::test_undecodable_private_text_is_removed_rather_than_sequestering

  • tests/dicom/test_deidentify_private_text.py::test_a_parsed_sequence_holding_only_text_is_kept_only_for_kept_text

  • tests/dicom/test_deidentify_private_text.py::test_text_that_an_instance_declares_safe_is_cleaned

  • tests/dicom/test_deidentify_private_text.py::test_declared_text_is_cleaned_only_where_its_decoded_values_meet_the_vm

  • tests/dicom/test_deidentify_private_text.py::test_text_that_tcia_s_dictionary_keeps_is_cleaned

  • tests/dicom/test_deidentify_private_text.py::test_a_withheld_instance_leaves_out_only_private_text_that_would_be_kept

  • tests/dicom/test_deidentify_retained_creators.py::test_a_value_found_in_a_creator_that_the_list_keeps_is_released

  • tests/dicom/test_deidentify_retained_creators.py::test_a_run_records_its_list_and_releases_the_kept_block

  • tests/dicom/test_deidentify_retained_creators.py::test_a_device_manufacturer_found_in_the_kept_philips_pet_creator_is_released

  • tests/dicom/test_deidentify_retained_private.py::test_numbers_and_code_strings_are_kept_with_their_creators

  • tests/dicom/test_deidentify_retained_private.py::test_a_rule_applies_in_whichever_block_its_creator_reserves

  • tests/dicom/test_deidentify_retained_private.py::test_a_creator_matches_exactly_apart_from_trailing_padding

  • tests/dicom/test_deidentify_retained_private.py::test_only_a_creator_of_printable_iso_646_is_compared

  • tests/dicom/test_deidentify_retained_private.py::test_a_creator_written_with_another_vr_matches_no_rule

  • tests/dicom/test_deidentify_retained_private.py::test_a_value_of_another_vr_or_vm_is_removed

  • tests/dicom/test_deidentify_retained_private.py::test_a_value_that_its_check_refuses_is_removed

  • tests/dicom/test_deidentify_retained_private.py::test_a_binary_value_must_be_whole_values_within_the_vm

  • tests/dicom/test_deidentify_retained_private.py::test_text_removed_rules_and_elements_without_a_rule_are_removed

  • tests/dicom/test_deidentify_retained_private.py::test_a_uid_is_replaced_where_it_is_written_as_ui

  • tests/dicom/test_deidentify_retained_private.py::test_a_private_sequence_is_parsed_and_each_element_judged

  • tests/dicom/test_deidentify_retained_private.py::test_a_kept_private_sequence_that_cannot_be_read_reports_its_vr_as_written

  • tests/dicom/test_deidentify_retained_private.py::test_a_sequence_rule_without_items_is_removed

  • tests/dicom/test_deidentify_retained_private.py::test_a_private_block_in_a_standard_sequence_is_judged_there

  • tests/dicom/test_deidentify_retained_private.py::test_the_option_keeps_safe_private_attributes_end_to_end

  • tests/dicom/test_deidentify_retained_private.py::test_each_kept_private_string_is_given_to_the_qc_pack

  • tests/dicom/test_deidentify_retained_private.py::test_each_value_of_a_kept_private_string_is_reviewed

  • tests/dicom/test_deidentify_retained_private.py::test_each_creator_written_as_un_is_kept_and_reviewed_as_lo

  • tests/dicom/test_deidentify_retained_private.py::test_a_kept_string_is_read_alike_with_or_without_its_vr

  • tests/dicom/test_deidentify_retained_private.py::test_the_plan_gives_the_vr_that_reads_each_kept_value_without_one

  • tests/dicom/test_deidentify_reviewed_descriptors_command.py::test_a_decision_on_private_text_is_recorded_by_its_key

  • tests/dicom/test_deidentify_safe_private.py::test_every_row_of_table_e3_10_1_has_one_rule_with_its_vr_and_vm

  • tests/dicom/test_deidentify_safe_private.py::test_each_rule_takes_the_handling_of_its_vr_unless_its_note_says_why

  • tests/dicom/test_deidentify_safe_private.py::test_bulk_binary_data_and_attributes_without_one_vr_are_removed

  • tests/dicom/test_deidentify_safe_private.py::test_the_examples_that_e3_10_gives_are_retained_as_numbers

  • tests/dicom/test_deidentify_safe_private.py::test_a_rule_applies_in_whichever_block_its_creator_reserves

  • tests/dicom/test_deidentify_tcia_private_tags.py::test_the_shipped_file_is_the_pinned_original_with_its_licence

  • tests/dicom/test_deidentify_tcia_private_tags.py::test_top_level_rows_are_found_by_creator_group_and_element

  • tests/dicom/test_deidentify_tcia_private_tags.py::test_an_altered_or_missing_copy_is_refused

  • tests/dicom/test_deidentify_tcia_safe_private.py::test_a_kept_row_takes_the_handling_of_its_vr_and_one_value

  • tests/dicom/test_deidentify_tcia_safe_private.py::test_a_row_that_does_not_keep_removes_its_attribute

  • tests/dicom/test_deidentify_tcia_safe_private.py::test_the_rule_is_that_of_the_vr_as_written_or_the_one_readable_vr

  • tests/dicom/test_deidentify_tcia_safe_private.py::test_a_reviewed_rule_for_the_same_vr_gives_its_handling_and_vm

  • tests/dicom/test_deidentify_tcia_safe_private.py::test_an_attribute_without_a_top_level_row_has_no_rule

  • tests/dicom/test_deidentify_tcia_safe_private.py::test_a_creator_is_compared_without_padding

  • tests/dicom/test_deidentify_tcia_safe_private.py::test_the_shipped_dictionary_gives_tcia_rules

  • tests/dicom/test_deidentify_tcia_safe_private.py::test_each_list_keeps_what_its_rules_keep

  • tests/dicom/test_deidentify_tcia_safe_private.py::test_a_tcia_rule_reads_a_value_whose_vr_is_not_written_by_its_vr

  • tests/dicom/test_deidentify_tcia_safe_private.py::test_a_value_that_two_vrs_read_is_kept_only_where_its_vr_is_written

  • tests/dicom/test_deidentify_tcia_safe_private.py::test_a_tcia_rule_checks_the_vr_and_number_of_values

  • tests/dicom/test_deidentify_tcia_safe_private.py::test_a_block_declared_unsafe_is_removed_under_tcia_s_dictionary

  • tests/dicom/test_deidentify_tcia_safe_private.py::test_a_run_that_chooses_tcia_s_dictionary_writes_what_it_keeps

Note: Reviewed rules give every row of Table E.3.10-1 a handling, keyed by private creator and element within the block: numbers and code strings are checked and kept, UIDs replaced, text cleaned as descriptors under Clean Descriptors and otherwise removed, private sequences parsed, and bulk binary data and three timestamps removed (D-035). Under the Option, the engine settles each private attribute by its rule, at the top level and in the items of every sequence that is kept, matching creators only in printable ISO 646 and checking each kept value against its VR and VM; it keeps a private creator only where its block keeps something, and removes every other private attribute, which E.3.10 allows in place of following Deidentification Action (0008,0307) (D-035). Under Clean Descriptors too, text that a rule retains, written with its VR and within its VM, is cleaned by the reviewed descriptors list’s decisions on its private creator, group, element within the block, and VR, and removed where they do not clean it, where it cannot be decoded, or where it is written without its VR (D-009, D-035). An attribute that the instance’s Private Data Element Characteristics Sequence (0008,0300) declares safe, in a block whose Block Identifying Information Status is SAFE or in Nonidentifying Private Elements, and that no rule lists, takes the handling of its VR, as written or as the sequence defines it; one that the sequence declares may identify is removed, even where a rule would keep it (D-035). TCIA’s Private Tag Dictionary ships unchanged, pinned by its length and SHA-256 and compared with TCIA’s file by a monthly edition check, as a second list that a run may choose in place of the reviewed rules; its keep is a basis for retention, its rules are chosen by the VR that reads each value, and kept values still pass the handling of their VR, with a single value unless a reviewed rule gives the VM; the method digest, the release report, and the conformance statement record which list a run used (D-024, D-035).

PS3.15-E.3.10-02#

Source: PS3.15 E.3.10, paragraph. Status: implemented.

When this Option is not specified, all Private Attributes shall be removed, as described in Table E.1-1.

Decisions: D-028.

Implementation:

  • _dicom/deidentify/element_rules.py

  • _dicom/deidentify/file_layout.py

  • _dicom/deidentify/instance_transform.py

  • _dicom/deidentify/sequences.py

  • _dicom/deidentify/source.py

  • _dicom/deidentify/walker.py

Tests:

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_no_published_file_holds_a_marker

  • tests/dicom/test_deidentify_element_rules.py::test_each_preset_removes_private_attributes_unless_it_retains_safe_ones

  • tests/dicom/test_deidentify_element_rules.py::test_private_attributes_take_only_the_action_their_option_gives

  • tests/dicom/test_deidentify_private_removal.py::test_the_basic_profile_removes_every_private_attribute

  • tests/dicom/test_deidentify_private_removal.py::test_every_odd_group_element_is_removed

  • tests/dicom/test_deidentify_private_removal.py::test_private_attributes_in_a_sequence_read_as_unknown_are_removed

  • tests/dicom/test_deidentify_private_removal.py::test_private_attributes_in_a_known_sequence_written_as_unknown_are_removed

  • tests/dicom/test_deidentify_private_removal.py::test_a_sequence_read_as_unknown_that_is_not_only_items_is_refused

  • tests/dicom/test_deidentify_private_removal.py::test_an_item_character_set_does_not_stop_private_attributes_being_removed

Note: Every element of an odd group, including private creators and the elements that PS3.5 does not allow, has a rule that removes it, at the top level and in the items of every standard sequence, including one written as UN or without a VR, whose items are read where the pinned data dictionary gives its attribute VR SQ, and a private sequence is removed with everything in it. A source file in which such a value does not hold only items is refused, wherever the value is, and a kept standard sequence written with a VR that the dictionary does not give it, such as OB, sequesters its instance (D-028). A policy that gives Private Attributes another action than X without the Retain Safe Private Option is refused. The run’s instance transform removes them from each instance it writes.

PS3.15-E.3.10-03#

Source: PS3.15 E.3.10, paragraph. Status: implemented.

When an entire Sequence (SQ VR) Private Attribute is not known to always only contain safe content, whether that content consists of Standard Attributes or Private Attributes or a combination of both, then when the Retain Safe Private Option is used, any such Sequence Attribute shall be parsed in its entirety and each of the nested Attributes handled on its own merits, rather than removing the Sequence in its entirety without further consideration. Reviewing the content may be a sensible practice to apply even to nominally known safe Private Sequence Attributes.

Decisions: D-028, D-035.

Implementation:

  • _dicom/deidentify/retained_private.py

  • _dicom/deidentify/walker.py

Tests:

  • tests/dicom/test_deidentify_declared_private.py::test_a_declaration_applies_to_its_block_in_the_items_of_a_sequence

  • tests/dicom/test_deidentify_declared_private.py::test_a_sequence_is_parsed_whatever_its_block_is_declared

  • tests/dicom/test_deidentify_private_text.py::test_a_parsed_sequence_holding_only_text_is_kept_only_for_kept_text

  • tests/dicom/test_deidentify_retained_private.py::test_a_private_sequence_is_parsed_and_each_element_judged

  • tests/dicom/test_deidentify_retained_private.py::test_a_private_sequence_without_a_rule_is_parsed

  • tests/dicom/test_deidentify_retained_private.py::test_a_parsed_sequence_that_keeps_nothing_goes_with_its_creator

  • tests/dicom/test_deidentify_retained_private.py::test_a_kept_value_keeps_each_parsed_sequence_that_holds_it

  • tests/dicom/test_deidentify_retained_private.py::test_a_parsed_sequence_keeps_a_creator_that_no_rule_names_for_review

  • tests/dicom/test_deidentify_retained_private.py::test_a_private_sequence_without_a_compared_creator_is_removed_whole

  • tests/dicom/test_deidentify_retained_private.py::test_a_kept_private_sequence_that_cannot_be_read_reports_its_vr_as_written

  • tests/dicom/test_deidentify_retained_private.py::test_the_option_keeps_safe_private_attributes_end_to_end

  • tests/dicom/test_deidentify_retained_private.py::test_each_creator_written_as_un_is_kept_and_reviewed_as_lo

Note: Under the Option, every private sequence in a block whose private creator is compared is parsed, whether or not a reviewed rule lists it, and each attribute in its items, standard or private, takes its own rule, at every depth (D-035). A private sequence that no rule lists is kept, with its creator, only where an attribute in its items other than a private creator is kept, replaced, or cleaned, and is otherwise removed with everything in it; a kept creator is among the retained strings that the QC pack lists for review (D-017, D-035). Without the Option, every private sequence is removed with everything in it (D-028).

PS3.15-E.3.10-04#

Source: PS3.15 E.3.10, paragraph. Status: implemented.

The safe Private Attributes that are retained shall be described in the Conformance Statement.

Decisions: D-035.

Implementation:

  • _dicom/deidentify/conformance.py

  • _dicom/deidentify/conformance_safe_private.py

Tests:

  • tests/dicom/test_deidentify_declared_private.py::test_the_statement_describes_the_blocks_an_instance_declares_safe

  • tests/dicom/test_deidentify_retained_private.py::test_the_statement_lists_the_safe_private_attributes_retained

Note: Under the Option, the conformance statement describes how the reviewed rules are matched and how each handling settles an attribute, and lists every rule that can retain one, with its private creator, tag, VR, VM, and handling, and the meaning that Table E.3.10-1 gives it or the source of a reviewed addition; the rules for text are among them only under Clean Descriptors, which cleans private text (D-035).

PS3.15-E.3.11-01#

Source: PS3.15 E.3.11, paragraph. Status: implemented.

When this Option is specified in addition to the Basic Application Level Confidentiality Profile, information about the identity of the institution in the Attributes shall be retained, as described in Table E.1-1.

Decisions: D-011, D-020, D-022, D-042, D-044.

Implementation:

  • _dicom/deidentify/conformance.py

  • _dicom/deidentify/instance_transform.py

  • _dicom/deidentify/policy.py

  • _dicom/deidentify/release_gate.py

Tests:

  • tests/dicom/test_deidentify_institution_identity.py::test_the_option_keeps_the_institutions_identity

  • tests/dicom/test_deidentify_institution_identity.py::test_the_ethics_committees_name_is_still_removed_with_its_approval_number

  • tests/dicom/test_deidentify_institution_identity.py::test_other_organisations_and_original_attributes_stay_removed

  • tests/dicom/test_deidentify_institution_identity.py::test_the_statement_keeps_the_institution_and_removes_the_committees_name

  • tests/dicom/test_deidentify_kept_text_names.py::test_a_physicians_name_in_a_kept_institution_name_goes_to_review

  • tests/dicom/test_deidentify_kept_text_names.py::test_a_name_in_a_kept_institution_name_in_an_item_goes_to_review

  • tests/dicom/test_deidentify_kept_text_names.py::test_under_retain_institution_identity_the_slice_is_held_for_review

Note: The ten attributes that the Option gives K are kept as read, wherever they are in the data set, including Institution Name in the items of Contributing Equipment Sequence, with the Option’s code (D-044). A removed person name found in a kept institution value, as in an institution named after a person, holds the instance for review rather than withholding it, and is never explained (D-042). Clinical Trial Protocol Ethics Committee Name, which the Option gives K, is still removed, since its Type 1C condition needs the approval number that Table E.1-1 removes under every option, and keeping it would make the instance invalid (D-020); the instance still claims the Option, and the conformance statement says so. The attributes that Table E.1-1 omits and that name other organisations, such as Coding Scheme Responsible Organization, are removed under every option (D-022). No preset selects the Option.

MIDI-BP-01#

Source: MIDI report §1.6. Status: implemented.

For public sharing, remove or replace all known direct identifiers and relevant indirect identifiers and sensitive information wherever they occur: collection descriptions, supporting data, structured and unstructured text, pixel data, and overlays. Justify retaining any indirect identifier with a collection-specific expert statistical analysis of residual risk against a predetermined threshold, which considers other public information about the subjects and holds only at the time it was done.

Decisions: D-001, D-015, D-016, D-017, D-022, D-023, D-027, D-032, D-042, D-053.

Implementation:

  • _dicom/deidentify/api.py

  • _dicom/deidentify/command.py

  • _dicom/deidentify/file_layout.py

  • _dicom/deidentify/instance_transform.py

  • _dicom/deidentify/option_kept.py

  • _dicom/deidentify/readiness.py

  • _dicom/deidentify/release_gate.py

  • _dicom/deidentify/release_report.py

  • _dicom/deidentify/residual_exemptions.py

  • _dicom/deidentify/residuals.py

  • _dicom/deidentify/risk_assessment.py

  • _dicom/deidentify/risk_assessment_command.py

  • _dicom/deidentify/run_report.py

Tests:

  • tests/dicom/test_deidentify_command_readiness.py::test_a_second_run_given_its_reviewed_and_assessed_pack_is_ready

  • tests/dicom/test_deidentify_command_readiness.py::test_a_pack_attested_or_assessed_otherwise_fails_its_gate_alone

  • tests/dicom/test_deidentify_command_readiness.py::test_a_run_of_other_files_fails_every_gate

  • tests/dicom/test_deidentify_command_readiness.py::test_a_run_under_another_list_of_safe_private_rules_fails_every_gate

  • tests/dicom/test_deidentify_command_readiness.py::test_a_run_with_another_reviewed_names_list_fails_the_statistical_gate

  • tests/dicom/test_deidentify_command_readiness.py::test_a_string_that_an_edited_list_now_keeps_fails_the_attestation_gate

  • tests/dicom/test_deidentify_conformance_values.py::test_the_qc_pack_and_report_describe_a_disclosed_body_weight

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_each_run_releases_all_but_the_instances_it_must_withhold

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_no_published_file_holds_a_marker

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_no_released_file_keeps_a_numeric_marker_at_its_place

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_no_released_file_keeps_a_removed_sequence_without_a_marker

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_no_warning_or_log_record_shows_a_marker

  • tests/dicom/test_deidentify_declared_private.py::test_a_declared_vendor_block_is_released_and_another_reviewed

  • tests/dicom/test_deidentify_instance_transform.py::test_text_outside_iso_646_under_an_unsupported_character_set_is_not_guessed

  • tests/dicom/test_deidentify_instance_transform.py::test_a_removed_value_read_as_latin_1_makes_collection_incomplete

  • tests/dicom/test_deidentify_instance_transform.py::test_a_value_read_as_latin_1_withholds_its_siblings

  • tests/dicom/test_deidentify_instance_transform.py::test_a_value_in_a_declared_character_set_is_not_read_as_latin_1

  • tests/dicom/test_deidentify_instance_transform.py::test_a_value_left_out_of_the_search_is_not_read_as_latin_1

  • tests/dicom/test_deidentify_instance_transform.py::test_a_run_with_the_transform_and_gate_releases_the_collection

  • tests/dicom/test_deidentify_instance_transform.py::test_a_value_removed_from_one_instance_withholds_its_siblings_that_keep_it

  • tests/dicom/test_deidentify_kept_text_names.py::test_a_physicians_name_in_a_kept_institution_name_goes_to_review

  • tests/dicom/test_deidentify_kept_text_names.py::test_a_name_in_a_kept_institution_name_in_an_item_goes_to_review

  • tests/dicom/test_deidentify_kept_text_names.py::test_a_patients_name_equal_to_a_kept_ethnic_group_goes_to_review

  • tests/dicom/test_deidentify_kept_text_names.py::test_a_name_from_a_private_source_in_kept_text_goes_to_review

  • tests/dicom/test_deidentify_kept_text_names.py::test_a_name_in_kept_text_with_options_pooled_across_the_subject_goes_to_review

  • tests/dicom/test_deidentify_kept_text_names.py::test_a_name_in_an_institution_name_that_no_option_keeps_still_withholds

  • tests/dicom/test_deidentify_kept_text_names.py::test_a_name_in_a_value_that_the_selected_option_does_not_keep_as_text_withholds

  • tests/dicom/test_deidentify_kept_text_names.py::test_a_name_also_found_outside_the_data_set_withholds

  • tests/dicom/test_deidentify_kept_text_names.py::test_a_name_also_found_in_text_that_no_option_keeps_withholds

  • tests/dicom/test_deidentify_kept_text_names.py::test_a_name_in_kept_text_with_a_person_name_not_collected_withholds

  • tests/dicom/test_deidentify_kept_text_names.py::test_an_identifier_uid_or_date_in_kept_text_still_withholds

  • tests/dicom/test_deidentify_kept_text_names.py::test_under_retain_institution_identity_the_slice_is_held_for_review

  • tests/dicom/test_deidentify_kept_text_names.py::test_under_the_basic_profile_the_same_slice_is_released

  • tests/dicom/test_deidentify_option_kept.py::test_kept_device_identity_found_from_the_option_or_a_private_value_is_released

  • tests/dicom/test_deidentify_option_kept.py::test_kept_institution_identity_found_from_a_private_value_is_released

  • tests/dicom/test_deidentify_option_kept.py::test_text_that_no_selected_option_keeps_still_goes_to_review

  • tests/dicom/test_deidentify_option_kept.py::test_kept_text_holding_a_value_of_another_kind_goes_to_review

  • tests/dicom/test_deidentify_option_kept.py::test_a_person_name_found_in_kept_text_goes_to_review_unexplained

  • tests/dicom/test_deidentify_option_kept.py::test_other_kept_text_or_text_outside_iso_646_goes_to_review

  • tests/dicom/test_deidentify_option_kept.py::test_a_removed_date_equal_to_a_kept_date_is_released

  • tests/dicom/test_deidentify_option_kept.py::test_a_removed_date_in_a_date_that_no_option_keeps_still_withholds

  • tests/dicom/test_deidentify_option_kept.py::test_a_kept_date_equal_to_its_own_source_still_withholds

  • tests/dicom/test_deidentify_option_kept.py::test_a_kept_datetime_explaining_each_pooled_value_found_is_released

  • tests/dicom/test_deidentify_option_kept.py::test_a_pooled_value_found_as_a_kept_datetimes_prefix_still_withholds

  • tests/dicom/test_deidentify_option_kept.py::test_a_removed_datetime_whose_date_is_a_whole_kept_date_is_released

  • tests/dicom/test_deidentify_option_kept.py::test_a_removed_datetime_by_its_date_in_a_date_no_option_keeps_withholds

  • tests/dicom/test_deidentify_option_kept.py::test_a_removed_datetime_by_its_date_inside_other_kept_text_withholds

  • tests/dicom/test_deidentify_option_kept.py::test_a_removed_date_found_by_another_spelling_of_itself_still_withholds

  • tests/dicom/test_deidentify_option_kept.py::test_a_removed_date_that_begins_a_kept_datetime_is_released

  • tests/dicom/test_deidentify_option_kept.py::test_a_removed_date_that_begins_a_datetime_no_option_keeps_withholds

  • tests/dicom/test_deidentify_option_kept.py::test_a_removed_date_or_time_that_a_kept_datetime_does_not_explain_withholds

  • tests/dicom/test_deidentify_option_kept.py::test_a_removed_date_inside_a_kept_value_after_its_date_withholds

  • tests/dicom/test_deidentify_option_kept.py::test_removed_text_that_begins_a_kept_datetime_goes_to_review

  • tests/dicom/test_deidentify_option_kept.py::test_single_valued_text_across_two_kept_times_goes_to_review

  • tests/dicom/test_deidentify_option_kept.py::test_a_long_value_found_by_its_first_characters_goes_to_review

  • tests/dicom/test_deidentify_option_kept.py::test_a_value_found_by_its_encoded_bytes_goes_to_review

  • tests/dicom/test_deidentify_option_kept.py::test_a_utf_8_value_equal_to_a_whole_kept_time_is_released

  • tests/dicom/test_deidentify_option_kept.py::test_text_equal_to_whole_kept_times_is_released

  • tests/dicom/test_deidentify_option_kept.py::test_a_removed_uid_equal_to_a_kept_uid_still_withholds

  • tests/dicom/test_deidentify_out_of_scope_siblings.py::test_an_out_of_scope_sibling_no_longer_withholds_its_subject

  • tests/dicom/test_deidentify_out_of_scope_siblings.py::test_a_value_of_an_out_of_scope_sibling_is_searched_for

  • tests/dicom/test_deidentify_out_of_scope_siblings.py::test_an_out_of_scope_sibling_whose_values_are_not_all_collected_withholds

  • tests/dicom/test_deidentify_out_of_scope_siblings.py::test_an_unlisted_sop_class_still_withholds_its_subject

  • tests/dicom/test_deidentify_pixel_risk_mr_pet.py::test_suv_scaled_pet_may_disclose_body_weight

  • tests/dicom/test_deidentify_pixel_risk_mr_pet.py::test_a_mapping_to_suv_may_disclose_body_weight

  • tests/dicom/test_deidentify_pixel_risk_parametric_maps.py::test_a_parametric_map_s_functional_group_mapping_to_suv_is_found

  • tests/dicom/test_deidentify_pixel_risk_parametric_maps.py::test_a_real_world_value_mapping_to_suv_may_disclose_body_weight

  • tests/dicom/test_deidentify_pixel_risk_parametric_maps.py::test_each_suv_mapping_of_a_real_world_value_mapping_is_found

  • tests/dicom/test_deidentify_readiness.py::test_a_run_is_ready_where_all_three_gates_pass

  • tests/dicom/test_deidentify_readiness.py::test_without_a_review_every_gate_fails

  • tests/dicom/test_deidentify_readiness.py::test_a_pack_not_attested_fails_both_review_gates_but_not_the_statistical

  • tests/dicom/test_deidentify_readiness.py::test_without_an_assessment_only_the_statistical_gate_fails

  • tests/dicom/test_deidentify_readiness.py::test_a_later_run_with_the_same_region_rules_passes_every_gate

  • tests/dicom/test_deidentify_readiness.py::test_the_report_records_the_gates_by_reason_and_reference_alone

  • tests/dicom/test_deidentify_readiness.py::test_a_second_run_of_a_reviewed_and_assessed_collection_is_ready

  • tests/dicom/test_deidentify_release_gate.py::test_the_rule_selects_exactly_the_direct_identifiers_the_design_lists

  • tests/dicom/test_deidentify_release_gate.py::test_a_patient_id_equal_to_a_refused_options_code_is_withheld_where_found

  • tests/dicom/test_deidentify_release_gate.py::test_nothing_collected_with_values_to_collect_is_withheld

  • tests/dicom/test_deidentify_release_gate.py::test_an_uncollected_value_of_a_required_kind_is_withheld

  • tests/dicom/test_deidentify_release_gate.py::test_other_uncollected_values_go_to_qc_review

  • tests/dicom/test_deidentify_release_gate.py::test_a_removed_name_read_only_as_latin_1_is_withheld_after_a_clean_search

  • tests/dicom/test_deidentify_release_gate.py::test_a_removed_description_read_only_as_latin_1_goes_to_qc_review

  • tests/dicom/test_deidentify_release_gate.py::test_a_name_collected_as_lo_and_found_is_withheld_as_a_name

  • tests/dicom/test_deidentify_release_gate.py::test_a_name_collected_under_a_vr_the_search_excludes_is_withheld

  • tests/dicom/test_deidentify_release_gate.py::test_a_siblings_uncollected_name_withholds_this_file

  • tests/dicom/test_deidentify_release_gate.py::test_a_siblings_unreported_name_is_not_hidden_by_this_instances_copy

  • tests/dicom/test_deidentify_release_gate.py::test_the_search_covers_the_pooled_values

  • tests/dicom/test_deidentify_release_gate.py::test_exclusions_are_listed_and_never_count_as_incomplete

  • tests/dicom/test_deidentify_release_gate.py::test_an_unreadable_file_is_withheld

  • tests/dicom/test_deidentify_release_gate.py::test_a_finding_of_a_required_kind_is_withheld

  • tests/dicom/test_deidentify_release_gate.py::test_a_text_finding_outside_the_data_set_is_withheld

  • tests/dicom/test_deidentify_release_gate.py::test_a_text_finding_inside_the_data_set_goes_to_qc_review

  • tests/dicom/test_deidentify_release_gate.py::test_a_trial_site_series_or_time_point_id_found_goes_to_qc_review

  • tests/dicom/test_deidentify_residual_exemptions.py::test_a_removed_value_found_in_a_cs_term_is_released

  • tests/dicom/test_deidentify_residual_exemptions.py::test_a_cs_value_with_a_value_that_is_no_term_goes_to_review

  • tests/dicom/test_deidentify_residual_exemptions.py::test_a_removed_description_in_a_body_part_is_released_only_from_a_term

  • tests/dicom/test_deidentify_residual_exemptions.py::test_an_exemption_never_releases_a_person_name

  • tests/dicom/test_deidentify_residual_exemptions.py::test_a_removed_value_in_a_code_of_a_scheme_of_table_8_1_is_released

  • tests/dicom/test_deidentify_residual_exemptions.py::test_a_removed_value_in_a_local_code_goes_to_review

  • tests/dicom/test_deidentify_residual_exemptions.py::test_a_removed_designator_found_in_a_kept_designator_is_released_if_standard

  • tests/dicom/test_deidentify_residual_exemptions.py::test_a_code_element_outside_an_item_goes_to_review

  • tests/dicom/test_deidentify_residual_exemptions.py::test_an_equipment_name_from_equipment_or_a_private_value_is_released

  • tests/dicom/test_deidentify_residual_exemptions.py::test_an_equipment_name_holding_an_identifying_value_goes_to_review

  • tests/dicom/test_deidentify_residual_exemptions.py::test_other_kept_text_still_goes_to_review

  • tests/dicom/test_deidentify_residual_exemptions.py::test_a_vendor_private_creator_found_anywhere_is_released

  • tests/dicom/test_deidentify_residual_exemptions.py::test_another_private_value_found_in_a_local_designator_goes_to_review

  • tests/dicom/test_deidentify_residual_exemptions.py::test_a_private_creator_reference_to_a_vendor_creator_is_released

  • tests/dicom/test_deidentify_residuals.py::test_a_value_hidden_in_the_preamble_is_found

  • tests/dicom/test_deidentify_residuals.py::test_a_value_in_the_file_meta_information_is_found

  • tests/dicom/test_deidentify_residuals.py::test_a_value_in_data_set_trailing_padding_is_found

  • tests/dicom/test_deidentify_residuals.py::test_bytes_after_the_data_set_are_searched

  • tests/dicom/test_deidentify_residuals.py::test_a_value_in_an_ob_value_is_found

  • tests/dicom/test_deidentify_residuals.py::test_a_value_in_a_un_value_is_found_in_utf_16

  • tests/dicom/test_deidentify_residuals.py::test_a_value_in_a_private_element_is_found

  • tests/dicom/test_deidentify_residuals.py::test_a_value_in_a_nested_sequence_item_is_found

  • tests/dicom/test_deidentify_residuals.py::test_encapsulated_fragments_are_searched_for_every_form

  • tests/dicom/test_deidentify_residuals.py::test_entropy_coded_data_hold_numbers_and_marker_segments_are_text

  • tests/dicom/test_deidentify_residuals.py::test_entropy_coded_data_are_searched_for_forms_of_six_bytes

  • tests/dicom/test_deidentify_residuals.py::test_a_source_value_equal_to_a_written_constant_is_skipped_and_recorded

  • tests/dicom/test_deidentify_residuals.py::test_a_source_value_that_differs_from_every_constant_is_searched

  • tests/dicom/test_deidentify_residuals.py::test_a_form_equal_to_a_constant_is_skipped_and_the_others_searched

  • tests/dicom/test_deidentify_residuals.py::test_a_constant_among_several_values_is_skipped_alone

  • tests/dicom/test_deidentify_residuals.py::test_a_clean_file_has_no_findings

  • tests/dicom/test_deidentify_retained_creators.py::test_a_value_found_in_a_creator_that_the_list_keeps_is_released

  • tests/dicom/test_deidentify_retained_creators.py::test_a_creator_kept_in_an_item_explains_as_one_at_the_top_level

  • tests/dicom/test_deidentify_retained_creators.py::test_every_pooled_value_need_not_be_the_creator

  • tests/dicom/test_deidentify_retained_creators.py::test_another_source_found_in_a_kept_creator_goes_to_review

  • tests/dicom/test_deidentify_retained_creators.py::test_a_creator_that_the_selected_list_does_not_keep_explains_nothing

  • tests/dicom/test_deidentify_retained_creators.py::test_text_found_anywhere_else_goes_to_review

  • tests/dicom/test_deidentify_retained_creators.py::test_a_person_name_uid_or_direct_identifier_in_a_kept_creator_withholds

  • tests/dicom/test_deidentify_retained_creators.py::test_a_run_records_its_list_and_releases_the_kept_block

  • tests/dicom/test_deidentify_retained_creators.py::test_a_device_manufacturer_found_in_the_kept_philips_pet_creator_is_released

  • tests/dicom/test_deidentify_risk_assessment.py::test_the_record_is_written_beside_the_pack_and_read_back

  • tests/dicom/test_deidentify_risk_assessment.py::test_the_gate_passes_a_bound_assessment_within_its_threshold

  • tests/dicom/test_deidentify_risk_assessment.py::test_the_gate_fails_without_an_assessment

  • tests/dicom/test_deidentify_risk_assessment.py::test_the_threshold_is_met_at_its_boundary

  • tests/dicom/test_deidentify_risk_assessment.py::test_the_gate_fails_for_another_collection

  • tests/dicom/test_deidentify_risk_assessment.py::test_the_gate_fails_for_another_policy

  • tests/dicom/test_deidentify_risk_assessment.py::test_the_gate_fails_for_other_lists

  • tests/dicom/test_deidentify_risk_assessment.py::test_the_gate_fails_for_other_region_rules

  • tests/dicom/test_deidentify_risk_assessment.py::test_the_record_binds_the_lists_that_the_run_applied

  • tests/dicom/test_deidentify_risk_assessment.py::test_a_pack_that_records_no_lists_is_refused

  • tests/dicom/test_deidentify_risk_assessment.py::test_a_pack_is_assessed_once

  • tests/dicom/test_deidentify_risk_assessment.py::test_a_record_that_appears_after_binding_is_not_overwritten

  • tests/dicom/test_deidentify_risk_assessment.py::test_a_pack_changed_after_it_was_assessed_is_refused

  • tests/dicom/test_deidentify_risk_assessment.py::test_a_pack_that_binds_no_collection_is_refused

  • tests/dicom/test_deidentify_risk_assessment_command.py::test_an_assessment_is_recorded_beside_the_pack

  • tests/dicom/test_deidentify_risk_assessment_command.py::test_a_collection_reviewed_and_assessed_with_the_commands_is_ready

  • tests/dicom/test_deidentify_run_report.py::test_the_qc_pack_binds_the_lists_that_the_reporter_checks

  • tests/dicom/test_deidentify_run_report.py::test_the_report_counts_released_instances_with_exempted_findings

  • tests/dicom/test_deidentify_several_patient_studies.py::test_the_studys_values_reach_the_gate_of_every_patient_it_names

  • tests/dicom/test_deidentify_several_patient_studies.py::test_instances_without_a_patient_id_are_a_patient_that_the_study_names

  • tests/dicom/test_deidentify_several_patient_studies.py::test_an_instance_of_the_study_that_changes_withholds_every_named_patient

  • tests/dicom/test_deidentify_several_patient_studies.py::test_a_named_patients_file_that_holds_the_studys_value_is_withheld

  • tests/dicom/test_deidentify_source.py::test_the_layout_still_reads_a_file_with_an_odd_length

  • tests/dicom/test_deidentify_tcia_safe_private.py::test_a_run_that_chooses_tcia_s_dictionary_writes_what_it_keeps

  • tests/dicom/test_deidentify_unwritten_sources.py::test_a_standard_codes_meaning_from_unwritten_instances_alone_is_exempt

  • tests/dicom/test_deidentify_unwritten_sources.py::test_other_text_from_unwritten_instances_still_goes_to_review

  • tests/dicom/test_deidentify_unwritten_sources.py::test_a_meaning_is_qualified_by_its_own_item_not_another_instances

  • tests/dicom/test_deidentify_unwritten_sources.py::test_another_instances_local_designator_does_not_disqualify_a_standard_meaning

  • tests/dicom/test_deidentify_unwritten_sources.py::test_a_person_name_from_unwritten_instances_still_withholds

  • tests/dicom/test_deidentify_unwritten_sources.py::test_a_sequestered_reports_target_region_holds_no_kept_description

Note: The Basic Profile’s actions remove or replace identifiers, and reviewed rules act on the text attributes, dates, and times that Table E.1-1 omits (D-022, D-023). Every byte of a written file, from the preamble to any bytes after its last readable element, can be searched for the source values that had to be removed or replaced, and each one found is reported by its source attribute and its place in the file, never by its value. A source value, or a form of one, equal to a constant that the engine writes is not searched, and is recorded by its source attribute (D-027). The release gate runs that search itself, for exactly the values collected across the file’s subject, and checks the collection against the values the walker planned to collect. It withholds any output file that still holds a source person name, UID, date, datetime, or direct identifier, or a source value outside its data set, or whose collection of person names, UIDs, dates, datetimes, direct identifiers, or sequences was incomplete, and sends other text findings and other incomplete collection, including of private values, to confidential review; a search that finds nothing does not on its own release a file (D-027). A text finding is not sent to review where it is in a kept CS value made only of its attribute’s terms in the pinned PS3.3 (for Body Part Examined, PS3.16 Annex L), in a code item of a scheme that PS3.16 Table 8-1 lists, or in Manufacturer, Manufacturer’s Model Name, or Software Versions with a private or equipment source, or where its source is a private creator that PS3.15 Table E.3.10-1 lists, and the QC pack still lists it (D-032). Nor is a finding inside a value that a selected option keeps, where its source is a private data element or an attribute of that option, or where it equals a whole kept date or time, is a datetime whose date equals a whole kept date, or is a date, or a datetime whose date is, that begins a whole kept datetime, and in that case a date finding does not withhold the file either (D-042). Nor is a finding whose source is the Code Meaning of a code of a scheme that PS3.16 Table 8-1 lists, judged by the meaning’s own item in its own instance, where only instances that the run does not write, such as a sequestered dose report, held it for the subject; free text from such an instance, and a local code’s meaning, still go to review (D-032). A UID equal to a whole UID that a selected option keeps does not withhold the file where only instances that the run does not write held it for the subject; one that a written instance also held still does, unless its source is a private data element that every instance which held it removed (D-042). Under Retain Safe Private, nor is text found in a private creator whose whole value is one that the run’s safe private list keeps in its group, where its source is a private creator, a private data element, or an equipment attribute; Institution Name, Station Name, and every other source found there still go to review, and the coverage records the list (D-042). A person name found inside the data set in an attribute that a selected option keeps and that the pinned dictionary gives only VRs of text, such as a physician’s name that is also a word of the Institution Name that Retain Institution Identity keeps, goes to review rather than withholding the file, and is never explained; the same name in a value that the Basic Profile keeps, in a private element, in a UID, in other text, or outside the data set still withholds the file (D-042). The release report counts the released instances with each finding that these exemptions keep from review, by exemption, source attribute, and the attribute it was found in, naming no value (D-032). An external statistical assessment of a run’s collection is recorded beside its QC pack, bound to the pack, to the source files it records, to the policy, and to the reviewed lists and region rules that the run applied, by the salted digest that the run records in its pack, and the statistical gate passes only for the same files, policy, lists, and rules, the run’s number of distinct subjects, and a maximum per-subject re-identification probability within the threshold of 0.09 or 0.05 that the assessment states (D-017, D-053). A deterministic synthetic collection, with an instance of each IOD of the first supported release, plants a conspicuous marker wherever its IODs define an attribute of Table E.1-1, down to a nesting depth of 4 items, and records each deeper place as not planted; Responsible Person and Responsible Organization, whose removal sequesters an instance (D-020), are planted in one instance only, so the others can be released, and the Enhanced PET Image’s Radiopharmaceutical Start DateTime, whose removal sequesters it, in none; the Institution Code Sequence of the RT Beams Treatment Record’s and the RT Ion Beams Treatment Record’s interlock resolution users, whose dummy item a reviewed rule writes there (D-021), is planted in both; the Structured Reports are VERIFIED, and their Verifying Observer Sequence, which a reviewed rule keeps with dummy values (D-021), and Requested Procedure ID in their Referenced Request Sequence, which a plain X empties there (D-020), are planted in each report, and Source Manufacturer in the RT Brachy Treatment Record’s Recorded Source Sequence, which a plain X empties there too, in the record; each report’s content tree is built by its template, with a marker in the value of each content item that can identify a person, a device, or a time. The run’s instance transform removes and replaces identifiers in each data set and gives the gate its coverage, including each value read as ISO 8859-1, and the run acts on the gate’s decision. An instance out of scope whose IOD the pinned tables define is read only to collect its values for its subject’s search, and is never written; an instance whose values could not be collected at all withholds every file of its subject. The Basic Profile removes Patient’s Weight but keeps PET values scaled to an SUV and mappings of values to one, which, with the Radionuclide Total Dose it also keeps, may disclose the weight, depending on the SUV normalisation, activity calibration, and compatible dose timing; each instance with either is reported as an indicator of a possibly disclosed body weight, in the QC pack and as counts in the release report (D-015). An end-to-end test runs basic and basic-clean-descriptors over that collection and searches every published file for its markers, independently of the gate. A public-release run’s release report says whether it is ready for release, which it is only where the statistical gate, the pixel and face review gate, and the attestation gate all pass, each decided from the run’s own QC pack against an earlier run’s attested pack of the same files and policy, and each failing on its own with a reason code (D-017). The pixel and face review gate needs every released instance at high risk to have been reviewed as one, and every released instance that can show a face to have a verdict that it shows none, on the pixel data and contours that the run writes, or a passing assessment that covers facial reconstruction; the attestation gate needs the releaser’s acceptance of the residual risk, and every released instance and every string that it retains to have been in the reviewed pack (D-017). The library’s run under the preset, and the private command’s, take that pack, which must be their face review’s, and the earlier run’s release report as their readiness review, and say whether the run is ready, with each failed gate’s reason code; a run given neither is not ready on any gate (D-017). Collection descriptions and supporting data outside the DICOM files are not written by the engine, and whoever releases the data reviews them. Pixel data are assessed for risk, not cleaned (D-015).

MIDI-BP-02#

Source: MIDI report §1.6. Status: not-applicable.

Define re-use utility for the widest range of conceivable uses, including those beyond the releasing entity's own, and quantify utility where possible when assessing residual risk.

Note: A decision for whoever releases the data. The engine’s Options and presets are the means it offers.

MIDI-BP-03#

Source: MIDI report §1.6. Status: implemented.

Do not let de-identification compromise conformance with the standards that define the content or reduce functionality. DICOM output should still conform to its original IOD, with dummy values synthesised where needed and replacement values consistent across files within a defined scope, such as replaced UIDs that keep references intact. This covers optional attributes critical to functionality as well as required ones.

Decisions: D-001, D-003, D-005, D-008, D-011, D-020, D-021, D-022, D-026, D-031.

Implementation:

  • _dicom/deidentify/birth_dates.py

  • _dicom/deidentify/common_instance_reference.py

  • _dicom/deidentify/compound_actions.py

  • _dicom/deidentify/dummy_values.py

  • _dicom/deidentify/edits.py

  • _dicom/deidentify/elements.py

  • _dicom/deidentify/file_layout.py

  • _dicom/deidentify/instance_transform.py

  • _dicom/deidentify/iod_conformance.py

  • _dicom/deidentify/iods.py

  • _dicom/deidentify/preserving_writer.py

  • _dicom/deidentify/profiles.py

  • _dicom/deidentify/qc_pack.py

  • _dicom/deidentify/reference_graph.py

  • _dicom/deidentify/references.py

  • _dicom/deidentify/release_report.py

  • _dicom/deidentify/run.py

  • _dicom/deidentify/run_qc.py

  • _dicom/deidentify/run_report.py

  • _dicom/deidentify/run_results.py

  • _dicom/deidentify/run_written.py

  • _dicom/deidentify/sequences.py

  • _dicom/deidentify/sop_classes.py

  • _dicom/deidentify/values.py

  • _dicom/deidentify/walker.py

  • _dicom/deidentify/written_references.py

Tests:

  • tests/dicom/test_deidentify_birth_dates.py::test_several_studies_at_one_age_give_one_birth_date

  • tests/dicom/test_deidentify_birth_dates.py::test_the_earliest_study_is_the_reference_across_a_birthday

  • tests/dicom/test_deidentify_birth_dates.py::test_every_instance_of_a_subject_takes_its_birth_date

  • tests/dicom/test_deidentify_birth_dates.py::test_incremental_runs_write_the_same_birth_date

  • tests/dicom/test_deidentify_compound_actions.py::test_an_attribute_gets_the_action_of_its_strictest_type_across_modules

  • tests/dicom/test_deidentify_compound_actions.py::test_no_required_attribute_with_a_compound_action_is_removed

  • tests/dicom/test_deidentify_compound_actions.py::test_no_required_attribute_with_a_compound_action_is_removed_in_any_generated_iod

  • tests/dicom/test_deidentify_compound_actions.py::test_x_z_gives_d_only_to_referenced_study_sequence_in_two_generated_iods

  • tests/dicom/test_deidentify_compound_actions.py::test_no_attribute_with_x_z_is_type_1_in_the_first_release_iods

  • tests/dicom/test_deidentify_conformance_values.py::test_the_release_report_counts_reported_reference_findings_by_kind

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_each_run_releases_all_but_the_instances_it_must_withhold

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_each_registration_ties_its_images_to_the_frame_as_written

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_the_rt_image_names_its_plan_as_written

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_the_enhanced_rt_images_name_their_plan_as_written

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_the_patient_dose_report_names_the_ct_dose_report_as_written

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_the_rt_ion_plan_and_its_record_name_what_they_reference_as_written

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_each_interlock_users_institution_code_sequence_is_released_as_reviewed

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_the_brachy_and_summary_records_name_what_they_reference_as_written

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_the_delivery_instructions_name_what_they_reference_as_written

  • tests/dicom/test_deidentify_dose_report_content.py::test_a_written_report_made_for_a_request_holds_an_empty_procedure_id

  • tests/dicom/test_deidentify_dummy_values.py::test_every_dummy_value_is_valid_for_its_vr_and_the_attributes_vm

  • tests/dicom/test_deidentify_dummy_values.py::test_table_e1_1_attributes_without_a_generic_dummy_value_are_refused

  • tests/dicom/test_deidentify_dummy_values.py::test_the_person_identification_item_is_valid_for_each_vr_and_vm

  • tests/dicom/test_deidentify_dummy_values.py::test_the_institution_item_is_valid_for_each_vr_and_vm

  • tests/dicom/test_deidentify_edits.py::test_d_writes_the_dummy_value_and_the_second_where_the_source_equals_it

  • tests/dicom/test_deidentify_edits.py::test_a_series_number_takes_its_rank_in_its_studys_numbering

  • tests/dicom/test_deidentify_element_rules.py::test_series_number_resolves_to_z_in_the_supported_iods_but_where_type_1

  • tests/dicom/test_deidentify_elements.py::test_us_or_ss_is_resolved_from_pixel_representation

  • tests/dicom/test_deidentify_elements.py::test_the_voi_lut_descriptor_of_a_ct_in_hounsfield_units_is_ss

  • tests/dicom/test_deidentify_elements.py::test_without_a_vr_in_the_file_the_voi_lut_input_decides_its_descriptor

  • tests/dicom/test_deidentify_elements.py::test_written_elements_carry_the_pinned_vr_and_round_trip

  • tests/dicom/test_deidentify_elements.py::test_a_written_value_problem_is_described_without_quoting_it

  • tests/dicom/test_deidentify_encapsulated_pdf.py::test_every_policy_sequesters_an_encapsulated_pdf_at_its_document

  • tests/dicom/test_deidentify_frames_of_reference.py::test_the_record_holds_the_frames_of_reference

  • tests/dicom/test_deidentify_frames_of_reference.py::test_frames_of_reference_that_agree_have_no_findings

  • tests/dicom/test_deidentify_frames_of_reference.py::test_a_series_in_several_frames_of_reference_is_reported

  • tests/dicom/test_deidentify_frames_of_reference.py::test_an_roi_whose_frame_of_reference_is_not_listed_once_is_reported

  • tests/dicom/test_deidentify_frames_of_reference.py::test_a_contour_image_in_another_frame_of_reference_is_reported

  • tests/dicom/test_deidentify_frames_of_reference.py::test_a_structure_set_roi_sequence_that_cannot_be_read_is_refused

  • tests/dicom/test_deidentify_frames_of_reference.py::test_a_run_reports_frame_findings_and_releases_their_instances

  • tests/dicom/test_deidentify_instance_transform.py::test_a_required_attribute_without_a_value_to_write_sequesters_an_added_iod

  • tests/dicom/test_deidentify_iod_conformance.py::test_a_removed_type_1_attribute_is_found

  • tests/dicom/test_deidentify_iod_conformance.py::test_an_emptied_type_1_attribute_is_found

  • tests/dicom/test_deidentify_iod_conformance.py::test_a_removed_type_2_attribute_is_found_but_an_emptied_one_is_not

  • tests/dicom/test_deidentify_iod_conformance.py::test_an_attribute_missing_or_empty_in_the_source_is_not_the_engines

  • tests/dicom/test_deidentify_iod_conformance.py::test_a_required_attribute_in_a_kept_item_is_found_at_its_path

  • tests/dicom/test_deidentify_iod_conformance.py::test_a_required_attribute_goes_with_its_removed_type_3_sequence

  • tests/dicom/test_deidentify_iod_conformance.py::test_the_common_instance_reference_may_go_once_no_other_reference_remains

  • tests/dicom/test_deidentify_iod_conformance.py::test_the_common_instance_reference_may_not_go_while_another_reference_remains

  • tests/dicom/test_deidentify_iod_conformance.py::test_clinical_trial_protocol_ethics_committee_name_may_go_alone

  • tests/dicom/test_deidentify_iod_conformance.py::test_an_instance_loses_its_ethics_committee_name_with_its_approval_number

  • tests/dicom/test_deidentify_iod_conformance.py::test_each_written_instance_of_a_collection_keeps_its_requirements

  • tests/dicom/test_deidentify_iod_conformance.py::test_an_instance_whose_de_identification_loses_a_requirement_is_sequestered

  • tests/dicom/test_deidentify_iod_conformance.py::test_an_instance_whose_de_identification_keeps_part_of_an_overlay_is_sequestered

  • tests/dicom/test_deidentify_iod_conformance.py::test_an_instance_whose_only_other_reference_is_removed_drops_the_module

  • tests/dicom/test_deidentify_iod_conformance.py::test_an_instance_that_keeps_another_reference_keeps_the_module

  • tests/dicom/test_deidentify_iod_conformance.py::test_a_descriptor_whose_basic_x_needs_its_sequence_takes_the_sequence

  • tests/dicom/test_deidentify_iods.py::test_the_first_supported_release_iods_are_generated

  • tests/dicom/test_deidentify_iods.py::test_an_attribute_can_have_a_different_type_in_each_module

  • tests/dicom/test_deidentify_iods.py::test_types_depend_on_the_enclosing_sequence

  • tests/dicom/test_deidentify_iods.py::test_macros_are_expanded_where_they_are_included

  • tests/dicom/test_deidentify_iods.py::test_functional_group_macros_are_expanded_in_each_functional_group_sequence

  • tests/dicom/test_deidentify_plain_action_validity.py::test_the_places_where_a_plain_action_cannot_stand_in_the_brachy_and_summary_records

  • tests/dicom/test_deidentify_plain_action_validity.py::test_the_places_where_a_plain_action_cannot_stand_in_the_second_generation_rt_iods

  • tests/dicom/test_deidentify_plain_actions.py::test_a_plain_z_gives_d_only_to_rt_accessory_holder_slot_id_in_five_generated_iods

  • tests/dicom/test_deidentify_plain_actions.py::test_a_plain_x_skips_each_enclosing_sequence_that_is_required

  • tests/dicom/test_deidentify_plain_actions.py::test_a_plain_x_on_a_required_attribute_outside_a_type_3_sequence_sequesters

  • tests/dicom/test_deidentify_plain_actions.py::test_overlay_data_is_type_1_only_at_the_top_level_of_a_user_optional_module

  • tests/dicom/test_deidentify_plain_actions.py::test_the_overlay_plane_module_is_conditional_only_in_the_known_iods

  • tests/dicom/test_deidentify_plain_actions.py::test_a_plain_x_on_roi_interpreter_sequence_removes_it_alone

  • tests/dicom/test_deidentify_plain_actions.py::test_each_lapsed_condition_needs_an_attribute_that_every_option_removes

  • tests/dicom/test_deidentify_plain_actions.py::test_every_plain_action_removes_clinical_trial_protocol_ethics_committee_name

  • tests/dicom/test_deidentify_plain_actions.py::test_a_plain_x_on_a_required_attribute_in_the_first_release_iods

  • tests/dicom/test_deidentify_preserving_writer.py::test_pydicom_resolves_the_descriptor_again_where_this_writer_does_not

  • tests/dicom/test_deidentify_preserving_writer.py::test_a_replacement_that_does_not_fit_its_place_is_refused

  • tests/dicom/test_deidentify_preserving_writer.py::test_a_replacement_whose_vr_a_reader_cannot_tell_is_refused

  • tests/dicom/test_deidentify_preserving_writer.py::test_items_from_pydicom_are_never_resolved_again

  • tests/dicom/test_deidentify_preserving_writer.py::test_text_that_cannot_be_written_in_its_character_set_is_refused

  • tests/dicom/test_deidentify_preserving_writer.py::test_a_default_repertoire_value_outside_iso_646_is_refused

  • tests/dicom/test_deidentify_preserving_writer.py::test_a_value_that_pydicom_would_write_with_another_vr_is_refused

  • tests/dicom/test_deidentify_preserving_writer.py::test_an_introduced_sequence_in_implicit_vr_takes_a_replaced_character_set

  • tests/dicom/test_deidentify_qc_pack.py::test_a_reference_finding_entry_is_checked

  • tests/dicom/test_deidentify_record_content.py::test_copies_with_the_same_source_bytes_have_the_same_digest

  • tests/dicom/test_deidentify_record_content.py::test_copies_in_implicit_and_explicit_vr_have_different_digests

  • tests/dicom/test_deidentify_record_content.py::test_any_change_to_the_data_set_changes_the_digest

  • tests/dicom/test_deidentify_record_content.py::test_a_file_whose_bytes_cannot_be_shown_sound_has_no_digest

  • tests/dicom/test_deidentify_record_content.py::test_a_file_whose_sequence_cannot_be_read_as_items_has_no_record

  • tests/dicom/test_deidentify_record_content.py::test_the_digest_is_of_the_transfer_syntax_and_the_data_set_bytes

  • tests/dicom/test_deidentify_record_content.py::test_a_left_out_element_that_holds_items_gives_no_digest

  • tests/dicom/test_deidentify_record_content.py::test_padding_in_an_item_changes_the_digest

  • tests/dicom/test_deidentify_record_content.py::test_copies_whose_bytes_cannot_be_shown_sound_conflict_each_alone

  • tests/dicom/test_deidentify_record_content.py::test_a_copy_whose_pixel_data_is_encoded_otherwise_conflicts

  • tests/dicom/test_deidentify_record_content.py::test_the_same_bytes_in_another_byte_order_conflict

  • tests/dicom/test_deidentify_record_content.py::test_a_private_number_in_another_vr_with_the_same_bytes_conflicts

  • tests/dicom/test_deidentify_record_content.py::test_an_element_read_as_unknown_conflicts_with_a_copy_that_has_its_vr

  • tests/dicom/test_deidentify_reference_graph.py::test_a_consistent_collection_has_no_findings

  • tests/dicom/test_deidentify_reference_graph.py::test_each_reference_resolves_to_its_instance

  • tests/dicom/test_deidentify_reference_graph.py::test_a_reference_to_an_instance_not_in_the_collection_is_dangling

  • tests/dicom/test_deidentify_reference_graph.py::test_a_reference_to_an_input_of_a_class_outside_table_b5_1_resolves

  • tests/dicom/test_deidentify_reference_graph.py::test_copies_of_one_instance_are_duplicates

  • tests/dicom/test_deidentify_reference_graph.py::test_copies_in_implicit_and_explicit_vr_conflict

  • tests/dicom/test_deidentify_reference_graph.py::test_copies_in_implicit_and_explicit_vr_with_a_private_element_conflict

  • tests/dicom/test_deidentify_reference_graph.py::test_one_sop_instance_uid_with_different_content_conflicts

  • tests/dicom/test_deidentify_reference_graph.py::test_a_series_in_two_studies_is_inconsistent

  • tests/dicom/test_deidentify_reference_graph.py::test_a_study_with_two_patients_is_inconsistent

  • tests/dicom/test_deidentify_reference_graph.py::test_a_study_whose_instances_all_lack_a_patient_id_has_one_patient

  • tests/dicom/test_deidentify_reference_graph.py::test_the_instances_without_a_patient_id_are_grouped_as_one_patient

  • tests/dicom/test_deidentify_references.py::test_reference_sites_include_the_rt_references

  • tests/dicom/test_deidentify_references.py::test_an_unknown_value_whose_items_cannot_be_read_is_refused

  • tests/dicom/test_deidentify_references.py::test_a_sequence_whose_items_cannot_be_read_is_refused

  • tests/dicom/test_deidentify_release_report_checks.py::test_each_check_is_given_by_the_reasons_it_sequesters_with

  • tests/dicom/test_deidentify_release_report_markdown.py::test_the_reference_findings_are_counted_by_kind

  • tests/dicom/test_deidentify_release_report_reasons.py::test_every_finding_the_run_does_not_act_on_is_reported

  • tests/dicom/test_deidentify_release_report_reasons.py::test_reference_findings_count_each_instance_once_for_each_kind

  • tests/dicom/test_deidentify_release_report_reasons.py::test_reference_findings_must_be_given_by_instance_and_reported_only

  • tests/dicom/test_deidentify_release_report_reasons.py::test_reference_finding_counts_that_could_hold_a_value_are_refused

  • tests/dicom/test_deidentify_retain_uids.py::test_a_collection_keeps_its_uids_and_still_refers_to_itself

  • tests/dicom/test_deidentify_run.py::test_a_study_with_several_patients_stops_the_run_before_anything_is_created

  • tests/dicom/test_deidentify_run.py::test_findings_name_inputs_by_run_position

  • tests/dicom/test_deidentify_run.py::test_an_identical_duplicate_is_processed_once

  • tests/dicom/test_deidentify_run.py::test_conflicting_copies_and_a_series_in_several_studies_are_sequestered

  • tests/dicom/test_deidentify_run.py::test_an_instance_without_a_uid_it_needs_is_sequestered

  • tests/dicom/test_deidentify_run_report.py::test_a_dangling_reference_is_counted_in_the_report_and_listed_in_the_pack

  • tests/dicom/test_deidentify_run_report.py::test_a_dangling_reference_at_an_identical_copy_counts_once

  • tests/dicom/test_deidentify_run_report.py::test_an_identical_copy_of_a_sequestered_input_is_counted_once

  • tests/dicom/test_deidentify_run_report.py::test_the_run_acts_on_every_finding_that_it_does_not_report_only

  • tests/dicom/test_deidentify_run_report.py::test_each_reported_finding_is_added_once_to_each_input_it_names

  • tests/dicom/test_deidentify_run_report.py::test_the_report_counts_each_instance_once_for_each_kind_of_finding

  • tests/dicom/test_deidentify_run_report.py::test_the_structural_checks_count_each_reported_instance_once

  • tests/dicom/test_deidentify_run_report.py::test_each_written_finding_reported_only_is_added_once_to_each_input_it_names

  • tests/dicom/test_deidentify_run_written_references.py::test_a_consistently_written_collection_is_released_without_findings

  • tests/dicom/test_deidentify_run_written_references.py::test_an_instance_written_with_its_source_uid_is_sequestered

  • tests/dicom/test_deidentify_run_written_references.py::test_a_reference_written_wrongly_sequesters_only_its_instance

  • tests/dicom/test_deidentify_run_written_references.py::test_the_report_counts_what_the_check_withheld_and_reported

  • tests/dicom/test_deidentify_run_written_references.py::test_a_reference_whose_replacement_is_shared_is_sequestered

  • tests/dicom/test_deidentify_run_written_references.py::test_a_check_that_raises_publishes_nothing

  • tests/dicom/test_deidentify_run_written_references.py::test_a_fault_that_names_no_released_instance_publishes_nothing

  • tests/dicom/test_deidentify_run_written_references.py::test_each_file_is_recorded_before_the_next_is_read

  • tests/dicom/test_deidentify_run_written_references.py::test_a_released_file_that_changes_before_it_is_read_back_is_sequestered

  • tests/dicom/test_deidentify_series_numbers.py::test_the_transform_writes_each_series_numbers_rank_in_its_studys_numbering

  • tests/dicom/test_deidentify_series_numbers.py::test_a_source_series_of_another_study_takes_its_own_studys_rank

  • tests/dicom/test_deidentify_several_patient_studies.py::test_the_default_still_stops_the_run_before_anything_is_created

  • tests/dicom/test_deidentify_several_patient_studies.py::test_a_choice_that_is_not_a_several_patients_is_refused_before_anything

  • tests/dicom/test_deidentify_several_patient_studies.py::test_every_instance_of_the_study_is_sequestered_and_the_rest_released

  • tests/dicom/test_deidentify_several_patient_studies.py::test_each_instance_of_the_study_is_transformed_but_not_prepared

  • tests/dicom/test_deidentify_several_patient_studies.py::test_the_studys_values_reach_the_gate_of_every_patient_it_names

  • tests/dicom/test_deidentify_several_patient_studies.py::test_instances_without_a_patient_id_are_a_patient_that_the_study_names

  • tests/dicom/test_deidentify_several_patient_studies.py::test_an_instance_of_the_study_that_changes_withholds_every_named_patient

  • tests/dicom/test_deidentify_several_patient_studies.py::test_identical_copies_in_the_study_are_all_sequestered

  • tests/dicom/test_deidentify_several_patient_studies.py::test_a_series_also_in_another_study_keeps_both_reasons

  • tests/dicom/test_deidentify_several_patient_studies.py::test_the_qc_pack_lists_each_study_by_patient_in_order

  • tests/dicom/test_deidentify_several_patient_studies.py::test_the_report_counts_the_studies_and_gives_their_instances_reasons

  • tests/dicom/test_deidentify_several_patient_studies.py::test_a_named_patients_file_that_holds_the_studys_value_is_withheld

  • tests/dicom/test_deidentify_several_patient_studies.py::test_the_sequestered_study_names_no_source_path_or_value

  • tests/dicom/test_deidentify_several_patient_studies.py::test_the_command_sequesters_only_when_asked

  • tests/dicom/test_deidentify_several_patient_studies.py::test_the_api_refuses_a_choice_that_is_not_a_bool

  • tests/dicom/test_deidentify_several_patient_studies.py::test_the_api_passes_its_choice_to_the_run

  • tests/dicom/test_deidentify_several_patient_studies.py::test_the_pilot_counts_the_study_and_measures_the_rest

  • tests/dicom/test_deidentify_sop_classes.py::test_the_first_supported_release_sop_classes_find_their_iods

  • tests/dicom/test_deidentify_sop_classes.py::test_every_storage_sop_class_finds_its_iod

  • tests/dicom/test_deidentify_source_gaps.py::test_a_type_1_attribute_of_a_mandatory_module_that_the_source_lacks_is_a_gap

  • tests/dicom/test_deidentify_source_gaps.py::test_only_unconditional_requirements_of_mandatory_modules_are_gaps

  • tests/dicom/test_deidentify_source_gaps.py::test_a_requirement_in_an_item_the_source_holds_is_a_gap_at_its_path

  • tests/dicom/test_deidentify_source_gaps.py::test_a_run_reports_its_source_gaps_in_the_report_and_the_qc_pack

  • tests/dicom/test_deidentify_values.py::test_valid_values

  • tests/dicom/test_deidentify_values.py::test_invalid_values

  • tests/dicom/test_deidentify_values.py::test_values_are_checked_for_their_vm_and_vr

  • tests/dicom/test_deidentify_walker.py::test_x_z_on_a_type_1_attribute_gives_d

  • tests/dicom/test_deidentify_walker.py::test_a_plain_z_on_a_type_1_attribute_writes_and_compares_a_dummy_value

  • tests/dicom/test_deidentify_walker.py::test_the_common_instance_reference_goes_where_no_other_reference_is_kept

  • tests/dicom/test_deidentify_walker.py::test_the_common_instance_reference_stays_while_another_reference_is_kept

  • tests/dicom/test_deidentify_written_references.py::test_a_consistently_replaced_collection_has_no_findings

  • tests/dicom/test_deidentify_written_references.py::test_a_slice_written_in_another_series_than_its_input_series_is_mismatched

  • tests/dicom/test_deidentify_written_references.py::test_a_kept_reference_is_an_original_uid

  • tests/dicom/test_deidentify_written_references.py::test_a_reference_to_another_written_instance_is_mismatched

  • tests/dicom/test_deidentify_written_references.py::test_a_reference_to_a_written_instance_with_a_mismatched_uid_is_unresolved

  • tests/dicom/test_deidentify_written_references.py::test_a_shared_replacement_is_reported

Note: The Type of each attribute of the first supported release’s IODs, and of every other composite IOD but the three real-time ones, in each module and sequence, including within each of an IOD’s Functional Group Macros, is generated from PS3.3, so the engine can tell where an IOD requires a value; PS3.4 Table B.5-1 gives the IOD of an instance from its SOP Class UID; and a value can be checked against its VR and VM before it is written. Compound actions resolve from the strictest of these Types, so in those IODs they never remove an attribute where it is Type 1, 1C, 2, or 2C, or empty one where it is Type 1 or 1C, and a plain Z on a Type 1 or 1C attribute gives D’s dummy value too. A plain X on an attribute that the IOD requires at its place removes with it the innermost enclosing sequence that the IOD makes Type 3 at its own place, or sequesters the instance where none encloses it, so it never leaves the output without an attribute that the IOD requires; Overlay Data takes its whole repeating group, whose module is user-optional in the CT Image, MR Image, and Positron Emission Tomography Image IODs, and ROI Interpreter Sequence, whose condition lapses once ROI Creator Sequence is removed, goes alone, as does Clinical Trial Protocol Ethics Committee Name, whose condition lapses once its approval number is removed and which PS3.5 Section 7.4.2 then does not allow (D-020). Requested Procedure ID, Type 2 in a Structured Report’s Referenced Request Sequence, and Source Manufacturer, Type 2 in an RT Brachy Treatment Record’s Recorded Source Sequence, are emptied there rather than removed, so the instance keeps them (D-020). Every dummy value is valid for its VR and for the VM of each attribute of Table E.1-1 that can take it, as is each element of the dummy item of Person Identification Code Sequence, and an attribute whose VR has no generic dummy value and no reviewed rule is refused rather than given an invalid value. Under tps-import, which planning systems import, Patient’s Birth Date takes each subject’s synthetic birth date rather than an empty value: the earliest study with a consistent Study Date and Patient’s Age in years gives it, the subject profile keeps it for every instance and later run, an empty value is written and kept where the ages are missing or inconsistent, and a later run that contradicts the reference study stops the subject for review (D-008). Before anything is written, a first pass over a collection follows the reference attributes that PS3.3 defines inside a sequence of each instance’s IOD, and reports each reference that names no instance, series, or study in the collection, and each instance without its SOP Instance, Series Instance, or Study Instance UID. It also reports instances that share a SOP Instance UID, as identical copies only where their source bytes, the transfer syntax and the data set as the file encodes it, are shown to be the same, and as conflicting copies otherwise; a series whose instances name several studies; and a study whose instances name several patients, each a Patient ID with its issuer, with the instances without a Patient ID as one more. It reports each frame of reference mismatch too: a series whose instances name several Frame of Reference UIDs, an RT Structure Set ROI whose frame of reference the structure set does not list once and only once, and an image that a structure set lists under a frame of reference other than the image’s own. An element to be written is built with a VR that the pinned dictionary gives it, of alternatives the one that applies in its data set, only once its values have been checked against that VR and the attribute’s VM, against the data set’s character set, and, for a person name, against the repertoire of its first component group, and it reads back unchanged from Implicit and Explicit VR Little Endian. A data set is written from its source’s bytes, so a kept element keeps the VR its source gives it, such as SS for the VOI LUT Descriptor of a CT image in Hounsfield Units, where pydicom’s writer can resolve it again as US. A sequence kept as UN keeps its items in implicit VR, and a replacement in implicit VR is written only where the pinned dictionary gives its VR alone, since a reader takes the VR from its dictionary. A run acts on those findings before it writes anything: a study with several patients stops it, unless the run is told to sequester such a study, when every instance of the study is sequestered, its values are searched for in the released files of every patient that it names, the release report gives the choice and how many studies it sequestered, and the QC pack lists each study’s instances by patient; every conflicting copy, every instance of a series in several studies, and every instance without one of those UIDs is sequestered, an identical duplicate is written once, and a dangling reference or a frame of reference mismatch is only reported. The release report counts the instances with each kind of finding that the run reports only, and the QC pack lists each such finding at each instance it names, with the attribute’s tags and its count. A second pass checks what was written against the first: that each written instance’s SOP Instance, Series Instance, and Study Instance UIDs, and each value at its reference sites, are the keyed replacements of its input’s, never a source UID; that each reference that named an input before writing names a written instance, series, or study after it; that no two source UIDs share a replacement; and that no two written instances share a SOP Instance UID. A run makes that check of every file that its gate releases, before anything is published: each instance that a finding names is sequestered, and what is left is checked again, a reference to an instance that was withheld being reported only, while a check that fails, or a finding that no sequestration can isolate, publishes nothing. Each written instance is checked against its IOD before it can be released: every attribute that its source holds where the IOD requires it, by the same strictest Type, must still be there where the item that held it is, and a Type 1 or 1C one must not have been emptied, or the instance is sequestered, since the engine caused it; the removals that a plain X allows on their own are not findings, and what the source already lacks is not acted on. Instead, each attribute that the IOD unconditionally requires, a Type 1 or 2 attribute of a mandatory module at the top level or of an item that the source holds, and that the source lacks, is reported: the release report counts the instances by attribute and Type, and the QC pack lists each by instance and place. The Common Instance Reference Module’s Type 1C sequences, Referenced Series Sequence and Studies Containing Other Referenced Instances Sequence, are removed with their items where the instance keeps no other reference to an instance, since their conditions then do not hold, and the check against the IOD allows their removal only where the output holds no such reference (D-022). A gap is never filled with a made-up value, so a released instance fails a MIDI-B check that an attribute its IOD requires is present, or has a value, only where its source lacks it (D-031). A kept value keeps its bytes, so a released instance fails a MIDI-B check that a kept value is unchanged only where its source fails it too, as where Smallest Image Pixel Value is written as US although Pixel Representation makes it SS (D-031). The walker gives D’s dummy value wherever a compound action, or a plain Z, resolves to it on a Type 1 or 1C attribute, and the run’s instance transform writes it, so a written instance keeps a valid value wherever its IOD requires one (D-020, D-021). Independently of the engine, dciodvfy, dcentvfy, and dicom-validator check each instance that a run over the synthetic corpus releases, and its input, under each preset in continuous integration, and every finding that an output introduces must be explained by a reviewed known difference (D-030).

MIDI-BP-04#

Source: MIDI report §1.6. Status: partial, remaining work in M4.

Preserve as much information about image acquisition as possible, including machine identity, characteristics, and settings, unless it is shown to raise residual risk above the predetermined threshold.

Decisions: D-007, D-017, D-022, D-034, D-036, D-042, D-053.

Implementation:

  • _dicom/deidentify/ae_titles.py

  • _dicom/deidentify/element_rules.py

  • _dicom/deidentify/instance_transform.py

  • _dicom/deidentify/option_kept.py

  • _dicom/deidentify/policy.py

  • _dicom/deidentify/readiness.py

  • _dicom/deidentify/risk_assessment.py

  • _dicom/deidentify/risk_assessment_command.py

  • _dicom/deidentify/technique_descriptors.py

Tests:

  • tests/dicom/test_deidentify_cleaners.py::test_retain_device_identity_keeps_the_device_and_tokenises_ae_titles

  • tests/dicom/test_deidentify_element_rules.py::test_the_default_keeps_the_vrs_that_the_design_names

  • tests/dicom/test_deidentify_option_kept.py::test_a_station_named_as_its_ae_title_and_in_private_values_is_released

  • tests/dicom/test_deidentify_readiness.py::test_no_run_is_ready_without_a_statement_on_keeping_machine_identity

  • tests/dicom/test_deidentify_risk_assessment_command.py::test_a_statement_that_is_not_text_is_refused

  • tests/dicom/test_deidentify_technique_descriptors.py::test_cleaning_keeps_vocabulary_values_and_holds_echoes

Note: Under every option, an attribute that no rule covers is kept where its VR is CS, AT, or numeric, so acquisition settings and characteristics, such as KVP, Image Type, and Acquisition Number, are kept as read (design Architecture). Under Clean Descriptors, a technique descriptor such as Protocol Name or Series Description is kept where every word of it is in the reviewed technique vocabulary and it echoes no identifier (D-036). The Retain Device Identity Option keeps machine identity, such as Station Name and Device Serial Number, as Table E.1-1 gives it, replacing each AE title by a keyed token (D-034), and a removed AE title or private value found in a text attribute that the Option keeps, such as an AE title equal to the Station Name, does not send the file to review (D-042). tps-import retains device identity, and modifies the device dates and times that the Option would keep by their role under Modified Dates (D-007). public-release omits it whatever an assessment of the collection finds, as the maintainer decided on 24 October 2026, because device serial numbers and machine names can identify an institution and, with dates, individual treatments (D-017). A public-release run is ready for release only where its statistical gate passes with a recorded external assessment of the collection, and the record cannot be made without a statement of whether keeping machine identity, such as Station Name and Device Serial Number, would raise the risk above the threshold (D-017, D-053); the engine records that statement and does not interpret it, so whoever reviews the record sees whether the omission was needed. Remaining: a policy that keeps machine identity in a public release, released only where an assessment of its own output, with machine identity among the matching variables, finds the risk within the threshold, which public-release deliberately is not (D-017) (M4).

MIDI-BP-05#

Source: MIDI report §1.6. Status: implemented.

Remove or replace the identities of other persons, departments, institutions, and organisations, except where they are needed for utility and a residual risk assessment supports keeping them.

Decisions: D-001, D-016, D-017, D-022, D-053.

Implementation:

  • _dicom/deidentify/conformance_markdown.py

  • _dicom/deidentify/element_rules.py

  • _dicom/deidentify/instance_transform.py

  • _dicom/deidentify/readiness.py

  • _dicom/deidentify/risk_assessment.py

  • _dicom/deidentify/risk_assessment_command.py

  • _dicom/deidentify/supplementary_actions.py

Tests:

  • tests/dicom/test_deidentify_conformance_values.py::test_kept_local_codes_are_said_to_be_listed_in_the_qc_pack

  • tests/dicom/test_deidentify_conformance_values.py::test_a_kept_manufacturer_and_coding_scheme_url_are_said_to_name_an_institution

  • tests/dicom/test_deidentify_conformance_values.py::test_the_releaser_accepts_the_residual_risk_of_these_values

  • tests/dicom/test_deidentify_conformance_values.py::test_an_unkept_coding_scheme_url_is_not_said_to_be_kept

  • tests/dicom/test_deidentify_conformance_values.py::test_the_removed_private_creators_description_is_described

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_no_published_file_holds_a_marker

  • tests/dicom/test_deidentify_element_rules.py::test_each_new_supplementary_rule_removes_by_type_with_a_note

  • tests/dicom/test_deidentify_element_rules.py::test_the_new_rules_cover_every_uri_that_table_e1_1_omits

  • tests/dicom/test_deidentify_parametric_map_scope.py::test_they_use_no_text_uid_name_url_or_date_that_earlier_iods_do_not

  • tests/dicom/test_deidentify_qc_retained.py::test_kept_values_that_can_name_an_institution_are_given_to_the_qc_pack

  • tests/dicom/test_deidentify_qc_retained.py::test_the_private_creators_of_removed_private_blocks_are_not_kept

  • tests/dicom/test_deidentify_readiness.py::test_no_run_is_ready_without_an_assessment_of_the_institutional_identity_kept

  • tests/dicom/test_deidentify_readiness.py::test_the_attestation_gate_needs_the_residual_risk_accepted

  • tests/dicom/test_deidentify_readiness.py::test_a_retained_string_that_the_reviewed_pack_lacks_was_not_reviewed

  • tests/dicom/test_deidentify_risk_assessment.py::test_a_record_of_the_earlier_format_is_refused

  • tests/dicom/test_deidentify_risk_assessment_command.py::test_a_statement_that_is_not_text_is_refused

  • tests/dicom/test_deidentify_supplementary_actions.py::test_every_person_name_that_table_e1_1_omits_has_an_action

  • tests/dicom/test_deidentify_supplementary_actions.py::test_the_basic_profile_keeps_no_person_name

  • tests/dicom/test_deidentify_supplementary_actions.py::test_every_text_attribute_a_supported_iod_uses_and_table_e1_1_omits_has_a_rule

  • tests/dicom/test_deidentify_supplementary_actions.py::test_the_text_that_only_the_added_ct_mr_and_pet_iods_use_has_reviewed_rules

  • tests/dicom/test_deidentify_supplementary_actions.py::test_the_text_that_only_the_target_iods_use_has_reviewed_rules

  • tests/dicom/test_deidentify_supplementary_actions.py::test_the_text_that_only_the_radiation_dose_sr_iods_use_has_reviewed_rules

  • tests/dicom/test_deidentify_supplementary_actions.py::test_the_text_that_only_the_ion_iods_use_has_reviewed_rules

  • tests/dicom/test_deidentify_supplementary_actions.py::test_the_brachy_and_summary_records_use_no_text_without_a_reviewed_action

  • tests/dicom/test_deidentify_supplementary_actions.py::test_the_text_that_only_the_softcopy_presentation_states_use_has_reviewed_rules

  • tests/dicom/test_deidentify_supplementary_actions.py::test_the_text_that_only_the_encapsulated_pdf_uses_has_reviewed_rules

  • tests/dicom/test_deidentify_supplementary_actions.py::test_the_text_that_only_the_enhanced_rt_images_use_has_its_reviewed_action

  • tests/dicom/test_deidentify_supplementary_actions.py::test_the_text_that_only_the_second_generation_rt_iods_use_has_reviewed_rules

  • tests/dicom/test_deidentify_supplementary_actions.py::test_no_option_keeps_more_than_the_coded_text_and_device_identity

  • tests/dicom/test_deidentify_supplementary_actions.py::test_the_urls_that_name_coding_concepts_and_schemes_are_kept

  • tests/dicom/test_deidentify_supplementary_actions.py::test_the_text_that_only_the_nm_iod_uses_has_reviewed_rules

  • tests/dicom/test_deidentify_supplementary_actions.py::test_the_text_that_only_surface_segmentation_uses_has_reviewed_rules

Note: Every person name in the pinned dictionary has an action that removes or replaces it under the Basic Profile, from Table E.1-1 or from a reviewed supplementary action. Every text attribute that a supported IOD uses is removed or replaced too, other than coded and technical values and De-identification Method, which the engine adds to; the names and identifiers of people, organisations, records, and networks, and identifiers that a device reads, among those that Table E.1-1 omits are removed under every option, and accessory and equipment identifiers are kept only under Retain Device Identity (D-022). URN Code Value and Coding Scheme URL, which name coding concepts and schemes, are kept. Every other URI and URL attribute that Table E.1-1 omits is removed by Type under every option, as are seven sequences that it omits whose content can identify an institution or other patients or say something about the patient, such as Requesting Service Code Sequence (0032,1034), Issuer of Patient ID Qualifiers Sequence (0010,0024), and Group of Patients Identification Sequence (0010,0027), and three measures of the patient’s body, Patient’s Body Mass Index (0010,1022), Measured AP Dimension (0010,1023), and Measured Lateral Dimension (0010,1024) (D-022). Private Data Element Characteristics Sequence (0008,0300) is removed by Type under every option: without Retain Safe Private no private attribute is kept, so the private creators of removed private blocks do not survive in its items (D-028), and under the Option it is read to find the private attributes that the instance declares safe, and then removed, since its descriptions and the creators of blocks that are not kept can name the institution’s software (D-035). The conformance statement says that the kept codes of local coding schemes can carry an institution’s name or abbreviation, a local scheme’s Coding Scheme URL its host, and an in-house device’s Manufacturer its name, and that each kept value is listed among the QC pack’s retained strings. It also says that the engine does not itself assess the residual risk of these values; that public-release has a person review each of them, and is ready for release only where an external risk assessment of the collection states how the institutional identity that the policy keeps was assessed and whether the assessment supports keeping it; and that for the first release the design assigns to whoever releases the data the confirmation that the output’s residual risk, these values included, is acceptable, which the QC pack’s attestation records when they give it (D-016). The maintainer decided on 6 October 2026 that the kept codes of local coding schemes need no further rule beyond that disclosure (D-022). Manufacturer, Manufacturer’s Model Name, and Software Versions are kept because they identify products, not devices; an in-house device can carry the hospital as its Manufacturer, a residual risk that D-022 accepts because the value names an institution, not a patient. The run’s instance transform applies these actions to each instance it de-identifies. As for MIDI-BP-01, an end-to-end test of basic and basic-clean-descriptors over the synthetic collection, which plants markers in the names of other persons, institutions, departments, and stations, finds none of them in the published files; the removals of D-022’s other attributes, such as Requesting Service Code Sequence, rest on the tests of the element and supplementary rules. A public-release run, which keeps that institutional identity in an in-house device’s Manufacturer and in the codes and Coding Scheme URLs of local coding schemes, is ready for release only where its statistical gate passes with the record of an external assessment of the collection, which cannot be made without the assessor’s statement of how the residual risk of the institutional identity that the policy keeps was assessed and whether the assessment supports keeping it (D-053); where its attested QC pack lists each string that the run retains in a released instance, so that the reviewer reviewed it; and where the releaser confirmed in the attestation that the output’s residual risk is acceptable (MIDI-BP-17); otherwise a gate fails (D-017). The engine records the assessor’s statement without interpreting it, as it records every statement of the assessment, so whoever reviews the record reads whether the assessment supports keeping these values; the maintainer decided on 26 October 2026 that this, rather than a gate on the statement’s answer, meets the practice for public-release (D-017). The other presets keep these values without review or assessment, as their conformance statements say, and their output is not thereby ready to share (D-017). Retain Institution Identity, which no preset selects, keeps the institution’s own attributes as Table E.1-1 gives them (D-044).

MIDI-BP-06#

Source: MIDI report §1.6. Status: implemented.

Use the release of PS3.15 current at the time of de-identification as the reference for which data elements to de-identify, augmented by knowledge of other unsafe attributes, including private ones. Categorise retained elements by the PS3.15 Options, and apply the approach to any DICOM object, whether or not its IOD is recognised.

Decisions: D-001, D-010, D-022, D-023.

Implementation:

  • _dev/deid_tables/annex_e.py

  • _dev/deid_tables/edition_check.py

  • _dev/deid_tables/generate.py

  • _dev/deid_tables/ps3_4.py

  • _dev/deid_tables/ps3_6.py

  • _dicom/deidentify/actions.py

  • _dicom/deidentify/element_rules.py

  • _dicom/deidentify/instance_transform.py

  • _dicom/deidentify/policy.py

  • _dicom/deidentify/release_report.py

  • _dicom/deidentify/release_report_markdown.py

  • _dicom/deidentify/run.py

  • _dicom/deidentify/scope.py

  • _dicom/deidentify/sop_classes.py

  • _dicom/deidentify/standard.py

  • _dicom/deidentify/supplementary_actions.py

Tests:

  • tests/dev/test_deid_tables.py::test_generate_writes_table_e1_1_with_its_provenance

  • tests/dev/test_deid_tables.py::test_check_passes_only_when_the_tables_are_current

  • tests/dev/test_deid_tables.py::test_a_source_with_another_digest_is_not_parsed

  • tests/dev/test_deid_tables.py::test_a_changed_cell_changes_exactly_its_table

  • tests/dev/test_deid_tables.py::test_a_changed_paragraph_changes_exactly_the_paragraphs

  • tests/dev/test_deid_tables.py::test_a_page_that_cannot_be_parsed_fails_the_tables_it_serves

  • tests/dicom/test_deidentify_edits.py::test_a_series_number_takes_its_rank_in_its_studys_numbering

  • tests/dicom/test_deidentify_edits.py::test_a_series_number_that_takes_no_rank_is_emptied_under_z

  • tests/dicom/test_deidentify_edits.py::test_a_series_number_that_takes_no_rank_takes_ds_dummy_value_under_d

  • tests/dicom/test_deidentify_edits.py::test_without_a_numbering_a_series_numbers_edit_is_pending

  • tests/dicom/test_deidentify_element_rules.py::test_every_dictionary_attribute_has_the_rule_of_the_first_level_covering_it

  • tests/dicom/test_deidentify_element_rules.py::test_no_name_date_time_or_uid_reaches_the_rule_for_uncovered_attributes

  • tests/dicom/test_deidentify_element_rules.py::test_the_new_rules_cover_every_uri_that_table_e1_1_omits

  • tests/dicom/test_deidentify_element_rules.py::test_series_number_has_x_z_d_under_every_option

  • tests/dicom/test_deidentify_element_rules.py::test_series_number_resolves_to_z_in_the_supported_iods_but_where_type_1

  • tests/dicom/test_deidentify_element_rules.py::test_every_tag_has_one_rule_that_is_the_same_for_the_same_policy

  • tests/dicom/test_deidentify_encapsulated_pdf.py::test_an_encapsulated_pdf_is_supported

  • tests/dicom/test_deidentify_encapsulated_pdf.py::test_every_policy_sequesters_an_encapsulated_pdf_at_its_document

  • tests/dicom/test_deidentify_encapsulated_pdf.py::test_a_run_writes_nothing_of_an_encapsulated_pdf_and_reports_its_reason

  • tests/dicom/test_deidentify_instance_transform.py::test_an_instance_the_release_does_not_support_is_sequestered_with_its_iod

  • tests/dicom/test_deidentify_parametric_map_scope.py::test_they_use_no_text_uid_name_url_or_date_that_earlier_iods_do_not

  • tests/dicom/test_deidentify_presentation_states.py::test_the_basic_profile_sequesters_a_presentation_state_by_its_creation_date

  • tests/dicom/test_deidentify_presentation_states.py::test_a_date_option_releases_a_presentation_state_without_annotations

  • tests/dicom/test_deidentify_private_removal.py::test_every_odd_group_element_is_removed

  • tests/dicom/test_deidentify_release_report.py::test_sequestered_instances_are_listed_by_label_with_their_reasons

  • tests/dicom/test_deidentify_release_report_reasons.py::test_each_stage_that_sequesters_gives_its_reason_code

  • tests/dicom/test_deidentify_release_report_reasons.py::test_an_unsupported_iod_is_named_with_its_reason

  • tests/dicom/test_deidentify_run_report.py::test_the_report_names_the_iod_of_an_input_the_release_does_not_support

  • tests/dicom/test_deidentify_scope.py::test_a_standard_sop_class_of_another_iod_is_sequestered

  • tests/dicom/test_deidentify_scope.py::test_a_sop_class_that_table_b_5_1_does_not_list_is_sequestered

  • tests/dicom/test_deidentify_scope.py::test_only_an_unsupported_iod_has_a_reason_with_its_iod

  • tests/dicom/test_deidentify_series_numbers.py::test_the_transform_writes_each_series_numbers_rank_in_its_studys_numbering

  • tests/dicom/test_deidentify_series_numbers.py::test_a_source_series_of_another_study_takes_its_own_studys_rank

  • tests/dicom/test_deidentify_series_numbers.py::test_a_source_series_that_the_run_does_not_hold_takes_ds_dummy_value

  • tests/dicom/test_deidentify_sop_classes.py::test_an_unlisted_sop_class_has_no_iod

  • tests/dicom/test_deidentify_standard.py::test_each_table_is_generated_from_the_pinned_edition

  • tests/dicom/test_deidentify_standard.py::test_an_altered_row_is_rejected

  • tests/dicom/test_deidentify_standard.py::test_every_table_e1_1_attribute_is_in_the_data_dictionary

  • tests/dicom/test_deidentify_supplementary_actions.py::test_every_person_name_that_table_e1_1_omits_has_an_action

  • tests/dicom/test_deidentify_supplementary_actions.py::test_every_text_attribute_a_supported_iod_uses_and_table_e1_1_omits_has_a_rule

  • tests/dicom/test_deidentify_supplementary_actions.py::test_the_rules_for_omitted_text_fall_in_the_reviewed_groups

  • tests/dicom/test_deidentify_supplementary_actions.py::test_the_text_that_only_the_added_ct_mr_and_pet_iods_use_has_reviewed_rules

  • tests/dicom/test_deidentify_supplementary_actions.py::test_the_text_that_only_the_target_iods_use_has_reviewed_rules

  • tests/dicom/test_deidentify_supplementary_actions.py::test_the_text_that_only_the_radiation_dose_sr_iods_use_has_reviewed_rules

  • tests/dicom/test_deidentify_supplementary_actions.py::test_the_text_that_only_the_ion_iods_use_has_reviewed_rules

  • tests/dicom/test_deidentify_supplementary_actions.py::test_the_brachy_and_summary_records_use_no_text_without_a_reviewed_action

  • tests/dicom/test_deidentify_supplementary_actions.py::test_the_text_that_only_the_softcopy_presentation_states_use_has_reviewed_rules

  • tests/dicom/test_deidentify_supplementary_actions.py::test_the_text_that_only_the_encapsulated_pdf_uses_has_reviewed_rules

  • tests/dicom/test_deidentify_supplementary_actions.py::test_the_text_that_only_the_enhanced_rt_images_use_has_its_reviewed_action

  • tests/dicom/test_deidentify_supplementary_actions.py::test_the_text_that_only_the_second_generation_rt_iods_use_has_reviewed_rules

  • tests/dicom/test_deidentify_supplementary_actions.py::test_each_group_takes_the_action_of_its_option

  • tests/dicom/test_deidentify_supplementary_actions.py::test_every_date_and_time_that_table_e1_1_omits_has_a_rule

  • tests/dicom/test_deidentify_supplementary_actions.py::test_only_the_date_options_override_the_action_of_an_omitted_date

  • tests/dicom/test_deidentify_supplementary_actions.py::test_only_the_text_of_the_supported_iods_needs_rules

  • tests/dicom/test_deidentify_supplementary_actions.py::test_a_new_attribute_that_table_e1_1_omits_needs_a_rule_by_its_vr

  • tests/dicom/test_deidentify_supplementary_actions.py::test_the_text_that_only_the_nm_iod_uses_has_reviewed_rules

  • tests/dicom/test_deidentify_supplementary_actions.py::test_the_text_that_only_surface_segmentation_uses_has_reviewed_rules

Note: The rule tables and the PS3.6 data dictionary are generated from the pinned edition, PS3.4 Table B.5-1 tells an instance of a supported IOD from one of any other SOP Class, so that each instance is classified as de-identified or sequestered with the reason, and reviewed supplementary actions cover the person names, dates, and times that Table E.1-1 omits and every text attribute that it omits and a supported IOD uses. Each such attribute that an option retains or cleans is categorised by that option, as Table E.1-1 does: operator-typed text is cleaned under Clean Descriptors, and accessory and equipment identifiers are kept under Retain Device Identity (D-022); the dates and times are kept under Retain Longitudinal Temporal Information with Full Dates and cleaned under Modified Dates (D-023). Without Retain Safe Private, every private attribute is removed from a data set, at every level (D-028). Every element, whether or not the pinned dictionary lists its attribute, has one rule, and every URI and URL attribute that the table omits has a reviewed one; no person name, date, time, datetime, or UID is left to the default for attributes that no rule covers, which keeps codes, tags, and numbers, keeps a sequence or a binary value only where the IOD defines it, and removes anything else (D-022). pymedphys dev deid-tables --check-current reports when NEMA’s current edition would change a generated table or cannot be read, and .github/workflows/deid-edition-check.yml runs it monthly. The run’s instance transform removes private attributes from, and applies each element’s rule to, each instance it de-identifies, and the run sequesters the instances so classified and lists them in the release report with their reasons. An object of any other SOP Class is handled by classifying and sequestering it, not by de-identifying it. The six softcopy Presentation State IODs are supported, with their text reviewed ahead of support (D-022); one that conforms to its IOD is sequestered under the Basic Profile without a date option, since Table E.1-1’s plain X removes its Type 1 Presentation Creation Date and Time, and one with a Graphic Annotation Sequence under every preset, since a sequence has no dummy value for D (D-010, D-020, D-021). The release report names the IOD of each instance of an unsupported IOD, as PS3.4 Table B.5-1 names it, never from a value of the instance (D-010). Sequestering those objects rather than processing them is a deliberate restriction (D-010).

MIDI-BP-07#

Source: MIDI report §1.6. Status: implemented.

If any patient characteristic or other indirect identifier is retained, perform a collection-specific expert statistical analysis of residual risk, whether elements are kept by an allow-list or removed by a deny-list, and consider characteristics that can be derived from the pixel data.

Decisions: D-017, D-053.

Implementation:

  • _dicom/deidentify/readiness.py

  • _dicom/deidentify/risk_assessment.py

  • _dicom/deidentify/risk_assessment_command.py

Tests:

  • tests/dicom/test_deidentify_command_readiness.py::test_a_second_run_given_its_reviewed_and_assessed_pack_is_ready

  • tests/dicom/test_deidentify_readiness.py::test_a_run_is_ready_where_all_three_gates_pass

  • tests/dicom/test_deidentify_readiness.py::test_without_an_assessment_only_the_statistical_gate_fails

  • tests/dicom/test_deidentify_readiness.py::test_another_count_of_subjects_fails_only_the_statistical_gate

  • tests/dicom/test_deidentify_readiness.py::test_a_second_run_of_a_reviewed_and_assessed_collection_is_ready

  • tests/dicom/test_deidentify_risk_assessment.py::test_the_record_is_written_beside_the_pack_and_read_back

  • tests/dicom/test_deidentify_risk_assessment.py::test_every_statement_is_required

  • tests/dicom/test_deidentify_risk_assessment.py::test_the_model_and_its_implementation_are_versioned

  • tests/dicom/test_deidentify_risk_assessment.py::test_the_matching_variables_are_listed_each_once

  • tests/dicom/test_deidentify_risk_assessment.py::test_the_gate_passes_a_bound_assessment_within_its_threshold

  • tests/dicom/test_deidentify_risk_assessment.py::test_the_threshold_is_met_at_its_boundary

  • tests/dicom/test_deidentify_risk_assessment.py::test_the_gate_counts_subjects_not_instances

  • tests/dicom/test_deidentify_risk_assessment_command.py::test_an_assessment_is_recorded_beside_the_pack

  • tests/dicom/test_deidentify_risk_assessment_command.py::test_the_record_is_bound_as_recording_from_python_binds_it

  • tests/dicom/test_deidentify_risk_assessment_command.py::test_a_reference_population_with_its_sampling_is_recorded

  • tests/dicom/test_deidentify_risk_assessment_command.py::test_the_summary_says_whether_the_risk_is_within_the_threshold

  • tests/dicom/test_deidentify_risk_assessment_command.py::test_a_statement_without_a_member_is_refused

  • tests/dicom/test_deidentify_risk_assessment_command.py::test_a_statement_that_is_not_text_is_refused

  • tests/dicom/test_deidentify_risk_assessment_command.py::test_the_matching_variables_are_checked

  • tests/dicom/test_deidentify_risk_assessment_command.py::test_a_collection_reviewed_and_assessed_with_the_commands_is_ready

Note: tps-import retains patient characteristics for non-clinical databases, not for public sharing, and public-release does not select Retain Patient Characteristics (D-038). public-release keeps other indirect identifiers, such as modified dates, coded values, and characteristics that can be derived from the pixel data. The record of an external statistical assessment of a collection lists its matching variables and how characteristics derivable from the pixel data were considered, with every other statement that D-017 requires, each required, and the statistical gate checks it against the run, counting distinct subjects rather than images, frames, or visits (D-053). An assessor records it with a command that reads their statement from a JSON file of its own format, refuses a statement that lacks any of them or holds anything else, and can make every check without recording, since a record is never overwritten (D-053). A public-release run is ready for release only where that gate passes for the distinct subjects of the instances that it releases and the reviewed lists and region rules that it applied; a run without a record fails it (D-017).

MIDI-BP-08#

Source: MIDI report §1.6. Status: not-applicable.

For non-DICOM images, apply the principles of PS3.15 E.1 where no better reference exists; for clinical data elements, apply the PhUSE De-Identification Standard for CDISC SDTM.

Note: The engine processes DICOM only.

MIDI-BP-09#

Source: MIDI report §1.6. Status: out-of-scope.

De-identify all data linked to the images by a common key, including accompanying spreadsheets and publications, and include them in the risk analysis, together with linked public data about the same subjects that the de-identifier knows of.

Decisions: D-017.

Note: Pseudonymising accompanying spreadsheets with the same key is later work. The statistical assessment already accounts for earlier linked releases (D-017).

MIDI-BP-10#

Source: MIDI report §1.6. Status: implemented.

Assess, for every image type, the risk from burned-in text, objects bearing text, and other identifying features in the pixel data. Where the risk exceeds the threshold, scan and redact, with human review, or discard the image. Do not limit checks to a subset of image types chosen from other data elements.

Decisions: D-015, D-017.

Implementation:

  • _dicom/deidentify/instance_transform.py

  • _dicom/deidentify/pixel_risk.py

  • _dicom/deidentify/qc_pack.py

  • _dicom/deidentify/readiness.py

  • _dicom/deidentify/release_report.py

  • _dicom/deidentify/run_qc.py

  • _dicom/deidentify/run_report.py

Tests:

  • tests/dicom/test_deidentify_command_readiness.py::test_a_second_run_given_its_reviewed_and_assessed_pack_is_ready

  • tests/dicom/test_deidentify_conformance_values.py::test_the_release_report_counts_pixel_risks_by_risk_and_indicator

  • tests/dicom/test_deidentify_pixel_risk.py::test_a_declared_burned_in_annotation_is_an_indicator

  • tests/dicom/test_deidentify_pixel_risk.py::test_a_received_no_is_no_evidence_of_safety

  • tests/dicom/test_deidentify_pixel_risk.py::test_a_secondary_image_is_an_indicator

  • tests/dicom/test_deidentify_pixel_risk.py::test_a_converted_image_is_an_indicator

  • tests/dicom/test_deidentify_pixel_risk.py::test_an_overlay_without_overlay_data_may_be_in_the_pixel_data

  • tests/dicom/test_deidentify_pixel_risk.py::test_an_attribute_held_with_another_vr_is_unreadable_evidence

  • tests/dicom/test_deidentify_qc_preview_pack.py::test_the_run_pack_previews_released_and_held_files_and_lists_high_risk

  • tests/dicom/test_deidentify_qc_preview_pack.py::test_the_transform_gives_the_sources_pixel_risk_as_qc_material

  • tests/dicom/test_deidentify_qc_preview_pack.py::test_a_run_previews_its_images_in_the_qc_pack

  • tests/dicom/test_deidentify_readiness.py::test_a_run_is_ready_where_all_three_gates_pass

  • tests/dicom/test_deidentify_readiness.py::test_a_high_risk_instance_must_have_been_reviewed_as_one

  • tests/dicom/test_deidentify_readiness.py::test_a_second_run_of_a_reviewed_and_assessed_collection_is_ready

  • tests/dicom/test_deidentify_release_report_markdown.py::test_the_pixel_risks_are_counted_by_disposition_risk_and_indicator

  • tests/dicom/test_deidentify_release_report_pixel_risks.py::test_each_instance_counts_once_for_each_risk_and_each_indicator

  • tests/dicom/test_deidentify_release_report_pixel_risks.py::test_the_section_splits_the_risks_from_their_indicators

  • tests/dicom/test_deidentify_release_report_pixel_risks.py::test_the_report_counts_what_the_qc_pack_lists_of_released_and_held_instances

  • tests/dicom/test_deidentify_release_report_pixel_risks.py::test_a_held_copy_is_assessed_with_its_series_but_not_counted_again

  • tests/dicom/test_deidentify_release_report_pixel_risks.py::test_a_run_over_the_synthetic_corpus_reports_what_its_qc_pack_lists

  • tests/dicom/test_deidentify_risk_assessment_command.py::test_a_collection_reviewed_and_assessed_with_the_commands_is_ready

Note: Every instance’s attributes are read for indicators of burned-in text, whatever its IOD: Burned In Annotation of YES, a secondary or converted image, and an overlay without Overlay Data, which may lie in the pixel data; an attribute that cannot be read is itself reported, and a received NO counts for nothing. The indicators are read from attributes, never from the pixel data (D-015), which only the Clean Pixel Data Option changes (D-037). Each run gives the indicators of each source it can decode to the confidential QC pack, which lists the instances at high risk and previews those released or held. The release report counts the released and held instances with each risk and each indicator, naming none, as the QC pack lists them (D-015). A public-release run is ready for release only where each instance at high risk that it releases was listed as high-risk in an attested QC pack of an earlier run of the same files and policy, whose attestation covers every high-risk instance; otherwise its pixel and face review gate fails. A reviewer who finds burned-in text rejects the pack, and the run is then not ready (D-017).

MIDI-BP-11#

Source: MIDI report §1.6. Status: implemented.

Remove or replace all unstructured text, whether in data elements, pixel data, or overlays, or, where it is needed for utility, scan it for identifiers and sensitive information, manually or automatically with human review.

Decisions: D-009, D-017, D-022, D-027.

Implementation:

  • _dicom/deidentify/descriptor_cleaning.py

  • _dicom/deidentify/instance_transform.py

  • _dicom/deidentify/pixel_risk.py

  • _dicom/deidentify/readiness.py

  • _dicom/deidentify/release_gate.py

  • _dicom/deidentify/residuals.py

  • _dicom/deidentify/reviewed_roi_names.py

  • _dicom/deidentify/roi_names.py

  • _dicom/deidentify/run_qc.py

  • _dicom/deidentify/supplementary_actions.py

Tests:

  • tests/dicom/test_deidentify_command_readiness.py::test_a_second_run_given_its_reviewed_and_assessed_pack_is_ready

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_no_published_file_holds_a_marker

  • tests/dicom/test_deidentify_descriptor_cleaning.py::test_a_run_holds_the_structure_set_and_releases_the_rest

  • tests/dicom/test_deidentify_descriptor_cleaning.py::test_a_kept_name_that_echoes_the_patient_is_held

  • tests/dicom/test_deidentify_parametric_map_scope.py::test_they_use_no_text_uid_name_url_or_date_that_earlier_iods_do_not

  • tests/dicom/test_deidentify_qc_retained.py::test_each_string_the_plan_keeps_is_given_to_the_qc_pack

  • tests/dicom/test_deidentify_readiness.py::test_a_run_is_ready_where_all_three_gates_pass

  • tests/dicom/test_deidentify_readiness.py::test_a_retained_string_that_the_reviewed_pack_lacks_was_not_reviewed

  • tests/dicom/test_deidentify_readiness.py::test_a_high_risk_instance_must_have_been_reviewed_as_one

  • tests/dicom/test_deidentify_readiness.py::test_a_second_run_of_a_reviewed_and_assessed_collection_is_ready

  • tests/dicom/test_deidentify_reviewed_roi_names.py::test_a_reviewed_name_that_echoes_an_identifier_is_held

  • tests/dicom/test_deidentify_risk_assessment_command.py::test_a_collection_reviewed_and_assessed_with_the_commands_is_ready

  • tests/dicom/test_deidentify_roi_names.py::test_a_name_that_echoes_a_known_identifier_goes_to_review

  • tests/dicom/test_deidentify_several_patient_studies.py::test_a_named_patients_file_that_holds_the_studys_value_is_withheld

  • tests/dicom/test_deidentify_supplementary_actions.py::test_every_text_attribute_a_supported_iod_uses_and_table_e1_1_omits_has_a_rule

  • tests/dicom/test_deidentify_supplementary_actions.py::test_the_text_that_only_the_added_ct_mr_and_pet_iods_use_has_reviewed_rules

  • tests/dicom/test_deidentify_supplementary_actions.py::test_the_text_that_only_the_target_iods_use_has_reviewed_rules

  • tests/dicom/test_deidentify_supplementary_actions.py::test_the_text_that_only_the_radiation_dose_sr_iods_use_has_reviewed_rules

  • tests/dicom/test_deidentify_supplementary_actions.py::test_the_text_that_only_the_ion_iods_use_has_reviewed_rules

  • tests/dicom/test_deidentify_supplementary_actions.py::test_the_brachy_and_summary_records_use_no_text_without_a_reviewed_action

  • tests/dicom/test_deidentify_supplementary_actions.py::test_the_text_that_only_the_softcopy_presentation_states_use_has_reviewed_rules

  • tests/dicom/test_deidentify_supplementary_actions.py::test_the_text_that_only_the_encapsulated_pdf_uses_has_reviewed_rules

  • tests/dicom/test_deidentify_supplementary_actions.py::test_the_text_that_only_the_enhanced_rt_images_use_has_its_reviewed_action

  • tests/dicom/test_deidentify_supplementary_actions.py::test_the_text_that_only_the_second_generation_rt_iods_use_has_reviewed_rules

  • tests/dicom/test_deidentify_supplementary_actions.py::test_the_basic_profile_keeps_only_the_reviewed_coded_and_technical_text

  • tests/dicom/test_deidentify_supplementary_actions.py::test_the_text_that_only_the_nm_iod_uses_has_reviewed_rules

  • tests/dicom/test_deidentify_supplementary_actions.py::test_the_text_that_only_surface_segmentation_uses_has_reviewed_rules

Note: Under the Basic Profile, every text attribute that a supported IOD uses is removed or replaced, by Table E.1-1 or a reviewed supplementary rule, except coded and technical values and De-identification Method, which the engine adds to (D-022). Under Clean Descriptors, a ROI name is written in the spelling of a TG-263 vocabulary only where it matches one of its names and echoes no known patient or other person identifier, and goes to review otherwise; a reviewer’s decision to keep or map a name is applied only where what it would write echoes no such identifier (D-009). The run’s instance transform applies the actions to each data set, and descriptor cleaning holds for review a ROI name whose decision would echo an identifier. A reviewer’s decision on a ROI name applies to every ROI Name of that spelling, in the collection and in later runs, and each run searches every written file for the source values that had to be removed or replaced (MIDI-BP-01). Indicators of burned-in text are reported in the confidential QC pack (D-015); only under the Clean Pixel Data Option does the engine black out text, and it then holds the instance for review (D-037). A public-release run is ready for release only where an attested review of an earlier run of the same files and policy reviewed each instance at high risk that it releases, as MIDI-BP-10 says, and each string that it retains in a released instance, which the attestation’s coverage confirms; otherwise its pixel and face review gate or its attestation gate fails (D-017).

MIDI-BP-12#

Source: MIDI report §1.6. Status: partial, remaining work in M4.

Replace absolute dates and times while preserving relative temporal relationships across studies, within a useful tolerance, for short-term analyses such as dynamic contrast and nuclide decay and long-term ones such as therapy assessment.

Decisions: D-004, D-006, D-007, D-039.

Implementation:

  • _dicom/deidentify/api.py

  • _dicom/deidentify/cleaners.py

  • _dicom/deidentify/command.py

  • _dicom/deidentify/dates.py

  • _dicom/deidentify/instance_transform.py

  • _dicom/deidentify/key_command.py

  • _dicom/deidentify/keys.py

  • _dicom/deidentify/modified_dates.py

  • _dicom/deidentify/profiles.py

  • _dicom/deidentify/temporal_roles.py

Tests:

  • tests/dicom/test_deidentify_dates.py::test_an_offset_is_52_to_520_whole_weeks_and_never_zero

  • tests/dicom/test_deidentify_dates.py::test_shifting_preserves_intervals_and_weekdays

  • tests/dicom/test_deidentify_dates.py::test_a_datetime_moves_its_date_and_keeps_its_time_and_utc_offset

  • tests/dicom/test_deidentify_modified_dates.py::test_subject_dates_move_back_by_whole_weeks_keeping_intervals_and_weekdays

  • tests/dicom/test_deidentify_modified_dates.py::test_a_sources_decay_interval_to_treatment_is_kept

  • tests/dicom/test_deidentify_modified_dates.py::test_an_instance_is_written_with_every_date_modified_and_released

  • tests/dicom/test_deidentify_modified_dates.py::test_a_subject_moves_by_one_offset_in_every_instance_and_other_subjects_apart

  • tests/dicom/test_deidentify_profiles.py::test_profiles_persist_across_runs

  • tests/dicom/test_deidentify_profiles.py::test_a_stored_offset_wins_over_a_changed_derivation

  • tests/dicom/test_deidentify_project_key_runs.py::test_both_runs_are_under_the_project_key

  • tests/dicom/test_deidentify_project_key_runs.py::test_the_second_run_leaves_the_store_as_the_first_saved_it

  • tests/dicom/test_deidentify_project_key_runs.py::test_an_instance_exported_again_is_written_alike

  • tests/dicom/test_deidentify_project_key_runs.py::test_both_runs_give_the_subject_one_patient_id_and_name

  • tests/dicom/test_deidentify_project_key_runs.py::test_both_runs_give_each_source_uid_one_replacement

  • tests/dicom/test_deidentify_project_key_runs.py::test_the_second_runs_references_resolve_to_the_first_runs_instances

  • tests/dicom/test_deidentify_project_key_runs.py::test_each_value_both_runs_receive_is_written_alike

  • tests/dicom/test_deidentify_project_key_runs.py::test_both_runs_move_the_subjects_dates_by_one_offset

  • tests/dicom/test_deidentify_project_key_runs.py::test_the_other_studies_keep_their_intervals

  • tests/dicom/test_deidentify_project_key_runs.py::test_a_later_run_keeps_the_subjects_birth_date

  • tests/dicom/test_deidentify_project_keys.py::test_dates_move_by_the_offset_recorded_in_the_subjects_profile

  • tests/dicom/test_deidentify_project_keys.py::test_a_later_run_under_the_project_key_extends_the_earlier_one

  • tests/dicom/test_deidentify_temporal_roles.py::test_modified_dates_shifts_subject_events_and_normalises_time_zones

Note: Offsets are whole weeks, derived from the key and the subject’s identity, and every date, time, and datetime attribute has a reviewed role that decides whether it moves with the subject. Under Retain Longitudinal Temporal Information with Modified Dates, a cleaner of the whole instance moves every subject-event and radiation-source date back by the subject’s offset, the same in every instance of the subject in a run, so date-only intervals, weekdays, local times of day, and decay intervals at a common UTC offset are preserved, and replaces device and other dates with dummy values (D-039, PS3.15-E.3.6-02). Intervals between instances with different UTC offsets change by the difference between those offsets (D-007). Times of day are kept, not replaced, since whole weeks keep them and a time alone identifies no one (D-006); only the date of a datetime moves. Each subject’s offset is the one its profile records (D-004). Under a run-scoped key, the profile is the derivation, so offsets are consistent across a subject’s studies within a run but not between separate runs. Under a project key, the subject profile store records the offset at first export and every later run reads it back, so studies de-identified in separate runs keep their intervals; the store is saved before the release is published, so a store that cannot be saved publishes nothing. Project keys are refused until PROJECT_KEYS_ENABLED is set, so no run can use them yet, since the first supported release uses a run-scoped key. Under every preset, two runs of the synthetic collection under one project key and its store, the second holding references to the first’s instances, two of them again, and a prior and a follow-up study of the subject, give the subject one Patient ID and Patient’s Name and each source UID one replacement, so that the second run’s retained references resolve to the first’s instances. Under Modified Dates, every subject-event date moves by one offset, so the other studies keep their intervals; under tps-import, the runs also keep the birth date that the store records. Remaining: enable project keys (PROJECT_KEYS_ENABLED) in the release PR (M4).

MIDI-BP-13#

Source: MIDI report §1.6. Status: implemented.

Retain private data elements only after evaluating their risk against a reliable source of elements known to be safe, such as PS3.15 E.3.10 or manufacturers' documentation; otherwise remove them selectively or entirely.

Decisions: D-001, D-028, D-035.

Implementation:

  • _dicom/deidentify/element_rules.py

  • _dicom/deidentify/instance_transform.py

  • _dicom/deidentify/retained_private.py

  • _dicom/deidentify/safe_private.py

  • _dicom/deidentify/tcia_private_tags.py

Tests:

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_no_published_file_holds_a_marker

  • tests/dicom/test_deidentify_element_rules.py::test_each_preset_removes_private_attributes_unless_it_retains_safe_ones

  • tests/dicom/test_deidentify_element_rules.py::test_private_attributes_take_only_the_action_their_option_gives

  • tests/dicom/test_deidentify_private_removal.py::test_the_basic_profile_removes_every_private_attribute

  • tests/dicom/test_deidentify_private_removal.py::test_every_odd_group_element_is_removed

  • tests/dicom/test_deidentify_safe_private.py::test_each_rule_takes_the_handling_of_its_vr_unless_its_note_says_why

  • tests/dicom/test_deidentify_safe_private.py::test_bulk_binary_data_and_attributes_without_one_vr_are_removed

  • tests/dicom/test_deidentify_tcia_private_tags.py::test_the_shipped_file_is_the_pinned_original_with_its_licence

  • tests/dicom/test_deidentify_tcia_private_tags.py::test_top_level_rows_are_found_by_creator_group_and_element

  • tests/dicom/test_deidentify_tcia_private_tags.py::test_an_altered_or_missing_copy_is_refused

Note: Without Retain Safe Private, every private data element and private creator is removed from a data set, at every level (D-028). The run’s instance transform removes them from each instance it writes. The reviewed rules evaluate every row of PS3.15 Table E.3.10-1, and retain only values that can be checked; under the Option, the engine applies them, and the instance’s own declarations of safe private attributes in Private Data Element Characteristics Sequence, with the same checks, and removes every private attribute that neither retains, or that the instance declares may identify (D-035). TCIA’s Private Tag Dictionary, shipped unchanged and pinned by its SHA-256, is a second source that a run may choose in place of the reviewed rules, and its kept values pass the same checks (D-035).

MIDI-BP-14#

Source: MIDI report §1.6. Status: implemented.

Consider identifying information embedded in compressed bitstreams, such as JPEG APP1 (EXIF) and APP11 (JUMBF) segments or ICC profiles. Either decompress losslessly compressed data and discard the embedded elements or, where the bitstream is kept, scan it and remove or replace the elements at risk.

Decisions: D-010, D-021, D-022, D-027.

Implementation:

  • _dicom/deidentify/codestreams.py

  • _dicom/deidentify/dummy_values.py

  • _dicom/deidentify/element_rules.py

  • _dicom/deidentify/icc_cleaning.py

  • _dicom/deidentify/icc_profiles.py

  • _dicom/deidentify/instance_transform.py

  • _dicom/deidentify/residuals.py

  • _dicom/deidentify/scope.py

  • _dicom/deidentify/supplementary_actions.py

Tests:

  • tests/dicom/test_deidentify_conformance_compressed.py::test_the_statement_says_how_compressed_instances_are_written

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_released_images_keep_their_transfer_syntax_and_pixels

  • tests/dicom/test_deidentify_corpus_end_to_end.py::test_instances_keep_their_reviewed_dummy_values

  • tests/dicom/test_deidentify_dummy_values.py::test_d_on_icc_profile_writes_the_source_cleaned_of_all_but_its_colorimetry

  • tests/dicom/test_deidentify_dummy_values.py::test_d_writes_the_fixed_srgb_profile_where_the_source_cannot_be_cleaned

  • tests/dicom/test_deidentify_dummy_values.py::test_an_icc_profile_that_is_not_rgb_is_refused

  • tests/dicom/test_deidentify_element_rules.py::test_each_new_supplementary_rule_removes_by_type_with_a_note

  • tests/dicom/test_deidentify_element_rules.py::test_the_new_rules_remove_by_type_where_the_supported_iods_allow_it

  • tests/dicom/test_deidentify_icc_profiles.py::test_the_profile_is_a_version_2_4_rgb_input_profile

  • tests/dicom/test_deidentify_icc_reports.py::test_the_report_counts_cleaned_and_replaced_profiles_by_reason

  • tests/dicom/test_deidentify_instance_transform.py::test_d_writes_the_reviewed_procedure_step_and_icc_profile_in_an_added_iod

  • tests/dicom/test_deidentify_instance_transform.py::test_d_writes_an_item_for_each_procedure_step_and_the_second_profile

  • tests/dicom/test_deidentify_instance_transform.py::test_d_writes_the_fixed_srgb_profile_where_the_source_cannot_be_cleaned

  • tests/dicom/test_deidentify_instance_transform.py::test_a_jpeg_comment_is_cut_without_recompression

  • tests/dicom/test_deidentify_instance_transform.py::test_openjpegs_comment_is_cut_from_a_jpeg_2000_image

  • tests/dicom/test_deidentify_release_report_markdown.py::test_the_icc_profiles_are_counted_as_cleaned_or_replaced_by_reason

  • tests/dicom/test_deidentify_scope.py::test_a_supported_sop_class_in_another_transfer_syntax_is_sequestered

Note: ICC Profile (0028,2000), an embedded file, is removed by Type under every option, which removes it wherever the first supported release’s IODs make it Type 3 (D-022); at the top level of an Enhanced CT Image, Enhanced MR Image, or Enhanced MR Color Image instance, where it is Type 1, D replaces an RGB profile with the source profile cleaned of everything but its colorimetry: each colorimetric tag that its ICC version defines is parsed and written again, every text tag, private tag, and other tag is removed, and the header’s identity fields and date are replaced. Where the source cannot be cleaned, D writes a fixed sRGB profile, which nothing of the source profile is written to; and an instance whose profile is of any other colour space is sequestered, since PS3.3 Section C.11.15.1.1 requires RGB (D-021). Where the bitstream is kept, the engine parses each frame’s codestream of encapsulated Pixel Data, accounting for every byte, and cuts its comment and application segments without recompression, keeping only JFIF, Adobe, and JPEG-LS colour segments of fixed format; it shows that every frame then decodes to the same pixels, and sequesters an instance whose codestreams do not parse, hold bytes outside them, or hold a segment it cannot cut (design Architecture item 7). The residual search searches the marker segments of those codestreams for every form, and their entropy-coded data, which can end with bytes that no decoder reads, for forms of at least 6 bytes, but RLE Lossless frames, which hold only samples and offsets, as values that hold numbers (D-027). The first supported release de-identifies instances in each transfer syntax that encapsulates Pixel Data that pydicom’s decoding plugins decode, and writes each with its transfer syntax and coded pixel data kept, its metadata segments cut (D-010).

MIDI-BP-15#

Source: MIDI report §1.6. Status: partial, remaining work in M4.

Quantify the re-identification risk from potentially reconstructable facial information in head and neck cross-sectional images with a collection-specific expert statistical analysis. Above the threshold, remove or modify the facial features, or do not share the images publicly.

Decisions: D-015, D-017, D-046, D-050, D-053.

Implementation:

  • _dicom/deidentify/pixel_risk.py

  • _dicom/deidentify/head_and_neck_regions.toml

  • _dicom/deidentify/instance_transform.py

  • _dicom/deidentify/phases.py

  • _dicom/deidentify/policy.py

  • _dicom/deidentify/readiness.py

  • _dicom/deidentify/run_qc.py

  • _dicom/deidentify/qc_contours.py

  • _dicom/deidentify/qc_pack.py

  • _dicom/deidentify/qc_previews.py

  • _dicom/deidentify/qc_segments.py

  • _dicom/deidentify/qc_surfaces.py

  • _dicom/deidentify/stack_sampling.py

Tests:

  • tests/dicom/test_deidentify_command_readiness.py::test_a_second_run_given_its_reviewed_and_assessed_pack_is_ready

  • tests/dicom/test_deidentify_command_readiness.py::test_a_pack_attested_or_assessed_otherwise_fails_its_gate_alone

  • tests/dicom/test_deidentify_command_readiness.py::test_a_string_that_an_edited_list_now_keeps_fails_the_attestation_gate

  • tests/dicom/test_deidentify_pixel_risk.py::test_declared_recognizable_visual_features_are_an_indicator

  • tests/dicom/test_deidentify_pixel_risk.py::test_an_external_roi_with_contours_is_a_patient_surface

  • tests/dicom/test_deidentify_pixel_risk.py::test_a_bolus_or_fixation_device_marks_a_patient_surface

  • tests/dicom/test_deidentify_pixel_risk.py::test_an_roi_named_external_body_or_skin_is_a_patient_surface

  • tests/dicom/test_deidentify_pixel_risk.py::test_every_ct_volume_may_hold_a_reconstructable_face

  • tests/dicom/test_deidentify_pixel_risk.py::test_localizers_are_reported_apart_from_a_volume

  • tests/dicom/test_deidentify_pixel_risk.py::test_a_localizer_that_names_the_head_says_so

  • tests/dicom/test_deidentify_pixel_risk.py::test_a_grid_of_doses_is_a_volume

  • tests/dicom/test_deidentify_pixel_risk.py::test_every_rt_image_can_show_the_face_in_profile

  • tests/dicom/test_deidentify_pixel_risk.py::test_an_rt_image_labelled_a_localizer_is_an_rt_image

  • tests/dicom/test_deidentify_pixel_risk.py::test_an_rt_image_of_many_frames_is_no_volume

  • tests/dicom/test_deidentify_pixel_risk.py::test_rt_images_are_reported_after_volumes_and_localizers

  • tests/dicom/test_deidentify_pixel_risk.py::test_an_rt_image_that_names_the_head_says_so

  • tests/dicom/test_deidentify_pixel_risk.py::test_a_volume_that_names_the_head_or_neck_says_so

  • tests/dicom/test_deidentify_pixel_risk.py::test_an_anatomic_region_code_can_name_the_head

  • tests/dicom/test_deidentify_pixel_risk.py::test_one_multi_frame_ct_image_can_be_a_volume

  • tests/dicom/test_deidentify_pixel_risk.py::test_shared_frame_anatomy_can_name_the_head

  • tests/dicom/test_deidentify_pixel_risk.py::test_per_frame_anatomy_can_name_the_head

  • tests/dicom/test_deidentify_pixel_risk.py::test_unreadable_evidence_is_reported_without_a_volume

  • tests/dicom/test_deidentify_pixel_risk_mr_pet.py::test_every_mr_and_pet_volume_may_hold_a_reconstructable_face

  • tests/dicom/test_deidentify_pixel_risk_mr_pet.py::test_one_multi_frame_mr_or_pet_image_can_be_a_volume

  • tests/dicom/test_deidentify_pixel_risk_parametric_maps.py::test_every_parametric_map_can_show_the_head_s_outline

  • tests/dicom/test_deidentify_pixel_risk_parametric_maps.py::test_a_parametric_map_is_no_frame_of_a_volume_beside_it

  • tests/dicom/test_deidentify_pixel_risk_parametric_maps.py::test_parametric_maps_are_reported_after_volumes_and_segmentations

  • tests/dicom/test_deidentify_pixel_risk_parametric_maps.py::test_a_parametric_map_that_names_the_head_says_so

  • tests/dicom/test_deidentify_qc_grids.py::test_a_sheet_shows_the_face_that_a_grid_traces

  • tests/dicom/test_deidentify_qc_segments.py::test_every_segmentation_may_trace_a_face

  • tests/dicom/test_deidentify_qc_segments.py::test_a_segment_that_names_the_head_or_neck_says_so

  • tests/dicom/test_deidentify_qc_segments.py::test_a_sheet_shows_the_face_that_a_segment_traces

  • tests/dicom/test_deidentify_qc_series_risks.py::test_the_series_evidence_is_assessed_as_the_instances_are

  • tests/dicom/test_deidentify_qc_series_risks.py::test_the_run_pack_assesses_each_series_of_released_and_held_instances

  • tests/dicom/test_deidentify_qc_series_risks.py::test_a_run_over_the_synthetic_corpus_lists_its_volumes_and_rt_image

  • tests/dicom/test_deidentify_qc_surfaces.py::test_a_volume_gets_a_surface_render_after_its_projection

  • tests/dicom/test_deidentify_qc_surfaces.py::test_the_frontal_view_meets_the_nose_first

  • tests/dicom/test_deidentify_qc_surfaces.py::test_each_crossing_of_a_surface_is_found_in_order_from_the_viewer

  • tests/dicom/test_deidentify_qc_surfaces.py::test_the_frontal_view_shows_the_patients_left_on_the_viewers_right

  • tests/dicom/test_deidentify_qc_surfaces.py::test_an_rt_dose_grid_is_rendered_at_each_of_its_dose_levels

  • tests/dicom/test_deidentify_qc_surfaces.py::test_the_view_from_behind_shows_the_patients_left_on_the_viewers_left

  • tests/dicom/test_deidentify_qc_surfaces.py::test_a_signed_grid_shows_its_doses_below_0_after_those_above

  • tests/dicom/test_deidentify_qc_surfaces.py::test_a_face_outlined_where_a_signed_dose_changes_sign_is_shown

  • tests/dicom/test_deidentify_readiness.py::test_a_run_is_ready_where_all_three_gates_pass

  • tests/dicom/test_deidentify_readiness.py::test_every_released_instance_that_can_show_a_face_needs_a_verdict_of_absent

  • tests/dicom/test_deidentify_readiness.py::test_an_instance_without_pixel_data_or_contours_needs_no_verdict

  • tests/dicom/test_deidentify_readiness.py::test_a_verdict_covers_only_the_pixel_data_that_the_reviewed_run_showed

  • tests/dicom/test_deidentify_readiness.py::test_a_verdict_covers_only_the_contours_that_the_reviewed_run_showed

  • tests/dicom/test_deidentify_readiness.py::test_a_verdict_covers_a_segmentation_only_where_its_segments_sheet_showed_it

  • tests/dicom/test_deidentify_readiness.py::test_a_verdict_covers_only_the_grids_that_the_reviewed_run_showed

  • tests/dicom/test_deidentify_readiness.py::test_an_instance_with_grids_alone_needs_a_verdict

  • tests/dicom/test_deidentify_readiness.py::test_an_assessment_of_facial_reconstruction_stands_in_for_the_verdicts

  • tests/dicom/test_deidentify_readiness.py::test_a_second_run_of_a_reviewed_and_assessed_collection_is_ready

  • tests/dicom/test_deidentify_release_report_pixel_risks.py::test_the_report_counts_what_the_qc_pack_lists_of_released_and_held_instances

  • tests/dicom/test_deidentify_release_report_pixel_risks.py::test_a_run_over_the_synthetic_corpus_reports_what_its_qc_pack_lists

  • tests/dicom/test_deidentify_risk_assessment_command.py::test_the_summary_says_whether_faces_were_assessed

  • tests/dicom/test_deidentify_risk_assessment_command.py::test_a_collection_reviewed_and_assessed_with_the_commands_is_ready

Note: Recognizable Visual Features of YES, an RT Structure Set’s contours of an ROI whose interpreted type or name is EXTERNAL, BODY, or SKIN, the patient’s outline, or BOLUS or FIXATION, which lie on it, every CT, MR, PET, and RT Dose volume, single-frame or enhanced, whatever its slice spacing, every CT, MR, and PET localizer, every RT Image, Enhanced RT Image, and Enhanced Continuous RT Image, whatever its gantry angle, which can show the face in profile, every Nuclear Medicine image, a planar view, a tomographic acquisition’s projections, or a volume reconstructed from them, whatever its Image Type, every Segmentation, whose skin or body segment traces the face, and every Parametric Map, whose map of the head shows its outline, are reported as indicators of a reconstructable face, and a volume, localizer, RT Image, Enhanced RT Image, Enhanced Continuous RT Image, Nuclear Medicine image, Segmentation, or Parametric Map that names a region of the head or neck, a Segmentation also by a segment’s anatomy or property type, as showing it. Each run lists each instance’s indicators and each series’ indicators, assessed over its released and held instances, in its QC pack for review. The QC pack renders the skin surface of each volume that it projects, whose slices may be the frames of multi-frame CT, MR, and PET instances placed by their functional groups, a volume for each stack and time point, and for each phase of slices that repeat their positions, parted by the time point or acquisition that each names,whose slices may lie off the patient’s axes, as with a gantry tilt, a shear, or an oblique orientation, sampled to voxels along the patient’s axes with every slice drawn, and the 5% isodose surface of each RT Dose grid of two or more frames, from the front, both obliques, both profiles, and behind, on voxels of 2 mm or its own spacing however long it is, so that the reviewer sees the face that a recognition system would; a volume more than 2,000 mm long or 1,000 mm across is not rendered, and is held beyond its cine strip. Every preview draws padding as one black, or as no patient, so an instance with a frame whose padding holds more than one stored value is held. A dose render that shows no face is inconclusive, since lower doses can keep an outline that the 5% isodose misses. It renders each RT Structure Set’s contours as written from the same directions, each frame’s ROIs united and each ROI alone, drawn with the surface of a volume in their frame, so that the reviewer sees a face that an outline traces, and each BINARY or LABELMAP Segmentation’s segments as written likewise, each frame placed by its functional groups; a FRACTIONAL Segmentation, or one that a sheet cannot place or show, is held (D-017). A public-release run is ready for release only where every instance that it releases that can show a face, one with pixel data or contours, is in a series that the reviewer marked as showing no face, on the pixel data and contours that the run writes, or where its statistical gate passes with an external assessment that covered faces reconstructed from the images, which D-017 accepts as the risk assessment reference that unblocks facial information; otherwise its pixel and face review gate fails (D-017, D-053). The public-release preset selects the Clean Recognizable Visual Features Option, so its run removes the face block from each CT series in which it finds one, the remedy that the recommendation names, and holds every instance that can show a face unless a reviewer marked its series as showing none, on the pixel data and contours that the run writes; under the preset, an assessment of facial risk releases no face (D-017, D-046, D-050). A reviewer’s verdict that the renders show no face stands in for a quantified facial risk, as D-017 decides; the engine computes no facial re-identification probability itself. Remaining: a collection-specific quantified analysis of the facial re-identification risk wherever a series can show a face, which a reviewer’s verdict does not supply (M4).

MIDI-BP-16#

Source: MIDI report §1.6. Status: out-of-scope.

Redact faces in photographic images so as to defeat recognition by people and software, with the residual risk quantified; above the threshold, do not share the images publicly.

Decisions: D-010.

Note: Photographic images are outside the supported objects and are sequestered (D-010).

MIDI-BP-17#

Source: MIDI report §1.6. Status: implemented.

Use human quality control to confirm that de-identification both removed what it should and preserved utility. Guide the proportion and type of records inspected by a documented risk assessment that sets the threshold of residual risk before and after quality control, taking account of what is removed or replaced and how reliably. Cover structured and unstructured text, pixel data, overlays, and metadata embedded in compressed bitstreams.

Decisions: D-016, D-017, D-026, D-027.

Implementation:

  • _dicom/deidentify/attestation_command.py

  • _dicom/deidentify/descriptor_cleaning.py

  • _dicom/deidentify/instance_transform.py

  • _dicom/deidentify/qc_attestation.py

  • _dicom/deidentify/qc_contours.py

  • _dicom/deidentify/qc_pack.py

  • _dicom/deidentify/qc_previews.py

  • _dicom/deidentify/qc_retained.py

  • _dicom/deidentify/qc_store.py

  • _dicom/deidentify/qc_surfaces.py

  • _dicom/deidentify/release_report.py

  • _dicom/deidentify/release_report_markdown.py

  • _dicom/deidentify/run.py

  • _dicom/deidentify/run_qc.py

  • _dicom/deidentify/run_report.py

  • _dicom/deidentify/run_results.py

  • _dicom/deidentify/stack_sampling.py

Tests:

  • tests/dicom/test_deidentify_attestation_command.py::test_an_attestation_is_recorded_beside_the_pack

  • tests/dicom/test_deidentify_attestation_command.py::test_the_releasers_confirmations_reach_the_record

  • tests/dicom/test_deidentify_attestation_command.py::test_attested_without_every_review_records_nothing

  • tests/dicom/test_deidentify_attestation_command.py::test_a_path_that_is_not_a_qc_pack_is_refused

  • tests/dicom/test_deidentify_attestation_command.py::test_the_release_report_record_comes_from_the_pack

  • tests/dicom/test_deidentify_conformance_values.py::test_the_releaser_accepts_the_residual_risk_of_these_values

  • tests/dicom/test_deidentify_conformance_values.py::test_the_release_report_summarises_the_structural_checks

  • tests/dicom/test_deidentify_conformance_values.py::test_the_release_report_records_the_releasers_confirmations

  • tests/dicom/test_deidentify_descriptor_cleaning.py::test_a_run_holds_the_structure_set_and_releases_the_rest

  • tests/dicom/test_deidentify_instance_transform.py::test_a_sibling_that_gives_no_evidence_withholds_its_subject

  • tests/dicom/test_deidentify_instance_transform.py::test_a_walker_sequestered_sibling_withholds_as_uncollected

  • tests/dicom/test_deidentify_instance_transform.py::test_values_left_out_of_the_search_are_dropped_with_their_reasons

  • tests/dicom/test_deidentify_instance_transform.py::test_an_instances_own_omissions_are_its_material_not_the_gates

  • tests/dicom/test_deidentify_instance_transform.py::test_each_value_not_searched_is_listed_once_at_its_own_instance

  • tests/dicom/test_deidentify_instance_transform.py::test_a_sequestered_instance_keeps_its_written_constant_drops

  • tests/dicom/test_deidentify_instance_transform.py::test_a_run_writes_the_qc_pack_of_the_transform_and_gate

  • tests/dicom/test_deidentify_qc_attestation.py::test_the_record_holds_only_the_reference_and_outcome

  • tests/dicom/test_deidentify_qc_attestation.py::test_attested_needs_every_review_that_d_017_requires

  • tests/dicom/test_deidentify_qc_attestation.py::test_a_pack_changed_after_attestation_is_detected

  • tests/dicom/test_deidentify_qc_attestation.py::test_the_releasers_confirmations_are_bound_to_the_pack

  • tests/dicom/test_deidentify_qc_face_verdicts.py::test_a_run_binds_each_series_to_the_digests_of_its_source_files

  • tests/dicom/test_deidentify_qc_face_verdicts.py::test_verdicts_are_written_by_series_in_order

  • tests/dicom/test_deidentify_qc_face_verdicts.py::test_the_command_records_each_verdict

  • tests/dicom/test_deidentify_qc_pack.py::test_retained_strings_are_grouped_by_value

  • tests/dicom/test_deidentify_qc_pack.py::test_an_excerpt_shows_the_bytes_around_a_residual

  • tests/dicom/test_deidentify_qc_pack.py::test_a_search_gives_entries_with_excerpts_and_omissions

  • tests/dicom/test_deidentify_qc_pack.py::test_the_document_holds_every_section

  • tests/dicom/test_deidentify_qc_pack.py::test_the_destination_is_neither_in_nor_around_releases

  • tests/dicom/test_deidentify_qc_pack.py::test_the_destination_is_outside_the_source_directory

  • tests/dicom/test_deidentify_qc_pack.py::test_a_written_pack_is_readable_only_by_its_owner

  • tests/dicom/test_deidentify_qc_pack.py::test_qc_material_is_recognised_wherever_it_is

  • tests/dicom/test_deidentify_qc_preview_pack.py::test_a_preview_changed_after_attestation_is_detected

  • tests/dicom/test_deidentify_qc_preview_pack.py::test_a_run_previews_its_images_in_the_qc_pack

  • tests/dicom/test_deidentify_qc_previews.py::test_a_series_gets_a_cine_strip_in_slice_order

  • tests/dicom/test_deidentify_qc_previews.py::test_a_volume_gets_a_frontal_projection_head_up_and_right_on_the_left

  • tests/dicom/test_deidentify_qc_previews.py::test_a_high_risk_instance_is_previewed_at_full_resolution

  • tests/dicom/test_deidentify_qc_previews.py::test_an_instance_that_cannot_be_previewed_is_listed_with_why

  • tests/dicom/test_deidentify_qc_retained.py::test_the_walker_plan_retains_its_kept_text_in_file_order

  • tests/dicom/test_deidentify_qc_retained.py::test_a_retained_value_that_was_not_read_is_refused

  • tests/dicom/test_deidentify_qc_retained.py::test_each_string_the_plan_keeps_is_given_to_the_qc_pack

  • tests/dicom/test_deidentify_qc_retained.py::test_kept_values_that_can_name_an_institution_are_given_to_the_qc_pack

  • tests/dicom/test_deidentify_qc_retained.py::test_a_kept_string_that_cannot_be_decoded_sequesters_the_instance

  • tests/dicom/test_deidentify_qc_series_risks.py::test_a_run_over_the_synthetic_corpus_lists_its_volumes_and_rt_image

  • tests/dicom/test_deidentify_qc_surfaces.py::test_a_volume_gets_a_surface_render_after_its_projection

  • tests/dicom/test_deidentify_readiness.py::test_a_pack_not_attested_fails_both_review_gates_but_not_the_statistical

  • tests/dicom/test_deidentify_readiness.py::test_the_attestation_gate_needs_the_residual_risk_accepted

  • tests/dicom/test_deidentify_release_report.py::test_the_qc_review_records_the_releasers_confirmations

  • tests/dicom/test_deidentify_release_report_checks.py::test_a_run_without_instances_shows_each_check_with_none

  • tests/dicom/test_deidentify_release_report_checks.py::test_each_check_is_given_by_the_reasons_it_sequesters_with

  • tests/dicom/test_deidentify_release_report_checks.py::test_each_check_gives_what_it_sequestered_and_reported

  • tests/dicom/test_deidentify_release_report_checks.py::test_a_check_cannot_have_sequestered_more_than_the_report_lists

  • tests/dicom/test_deidentify_release_report_checks.py::test_a_summary_that_could_hold_a_value_is_refused

  • tests/dicom/test_deidentify_release_report_checks.py::test_the_summary_is_a_table_of_each_check

  • tests/dicom/test_deidentify_release_report_markdown.py::test_the_releasers_confirmations_are_shown_or_said_to_be_absent

  • tests/dicom/test_deidentify_retained_private.py::test_each_kept_private_string_is_given_to_the_qc_pack

  • tests/dicom/test_deidentify_retained_private.py::test_each_value_of_a_kept_private_string_is_reviewed

  • tests/dicom/test_deidentify_retained_private.py::test_each_creator_written_as_un_is_kept_and_reviewed_as_lo

  • tests/dicom/test_deidentify_retained_private.py::test_a_kept_string_is_read_alike_with_or_without_its_vr

  • tests/dicom/test_deidentify_retained_private.py::test_read_values_gives_each_path_asked_or_none_where_undecodable

  • tests/dicom/test_deidentify_risk_assessment_command.py::test_a_collection_reviewed_and_assessed_with_the_commands_is_ready

  • tests/dicom/test_deidentify_run_qc.py::test_the_qc_pack_lists_every_input_with_labels_for_the_sequestered

  • tests/dicom/test_deidentify_run_qc.py::test_the_qc_pack_holds_the_transforms_and_gates_material_by_position

  • tests/dicom/test_deidentify_run_qc.py::test_a_qc_destination_in_the_source_release_or_staging_is_refused_first

  • tests/dicom/test_deidentify_run_qc.py::test_a_pack_that_cannot_be_written_publishes_nothing

  • tests/dicom/test_deidentify_run_qc.py::test_a_pack_is_withdrawn_when_its_release_is_not_published

  • tests/dicom/test_deidentify_run_report.py::test_an_identical_copy_of_a_sequestered_input_is_counted_once

  • tests/dicom/test_deidentify_run_report.py::test_the_structural_checks_count_each_reported_instance_once

  • tests/dicom/test_deidentify_run_written_references.py::test_the_report_counts_what_the_check_withheld_and_reported

  • tests/dicom/test_deidentify_several_patient_studies.py::test_the_qc_pack_lists_each_study_by_patient_in_order

  • tests/dicom/test_deidentify_several_patient_studies.py::test_the_pack_writes_each_study_by_patient

  • tests/dicom/test_deidentify_several_patient_studies.py::test_a_study_entry_of_another_form_is_refused

  • tests/dicom/test_deidentify_several_patient_studies.py::test_a_pack_whose_studies_do_not_fit_its_instances_is_refused

Note: D-016 provides the confidential QC pack, to which the release gate sends the residual search’s text findings inside the data set, and files whose collection for the search was incomplete for other text or for private values or values to which the pinned dictionary gives no VR (D-027), and D-017 sets the review coverage (every distinct retained string, every series, and every instance in high-risk categories) and the attestation. For the first release, the design assigns to whoever releases the data the confirmation that utility is preserved and that the residual risk is acceptable (decided 6 October 2026). The QC pack is built and written: each instance by run position and source path, with its source file’s SHA-256 and its output path or opaque label (D-026); each residual with a contextual excerpt, and each drop and form not searched, by instance and place (D-027); each distinct retained string with its places (D-017); what descriptor cleaning wrote for each ROI Name, or why it held it for review (D-009); and each study whose instances name several patients that the run sequestered, by its instances’ run positions grouped by patient, for a curator to correct (D-026). It is written only to a designated, owner-only location outside the source, release, and staging directories, and is marked so that no release includes it (D-016). A reviewer attests to a pack once, stating whether they reviewed every distinct retained string, every series, and every instance in high-risk categories, and an outcome of attested needs all three; the attestation is bound to the pack by its digest, and gives the release report only the pack’s opaque reference and the outcome (D-016, D-017). The pack lists each series of released or held instances, and the attestation may give any of them, by its first run position, a face verdict, absent or present, from its previews; the pack’s source digests, and the digests of the pixel data written of them, bind the verdict to the files the reviewer saw and to what the run wrote of them, and the release report does not record it (D-016, D-017, D-046). Each run writes its pack before it publishes the release, to a QC destination that the caller must name and that is checked before anything is created; a pack that cannot be written publishes nothing, and the pack of a release that cannot be published is removed, or named if it cannot be. The run’s instance transform and release gate adapter give it this material. The pack lists every input from its outcome, each sequestered one by the random label that the run gives it, and the transform’s and gate’s material by run position: each residual search with its excerpts, each source value they left out of the search with its reason, what descriptor cleaning wrote for each ROI Name, and each retained string with its places. The retained strings of an instance are the values that its plan keeps as they are, in elements with a text VR other than UI, outside any removed sequence, and other than Specific Character Set, and a value that was not read for review, or could not be decoded, is refused rather than left out, and the run’s transform sequesters an instance with such a value. The pack previews each series released or held for review, by a cine strip and, for a volume, a frontal maximum intensity projection and renders of its skin surface from six directions; renders the skin surface of each RT Dose grid of two or more frames; previews each instance whose source shows an indicator of risk in its pixel data at full resolution, with those indicators; renders each RT Structure Set’s contours as written on a contour sheet, each frame’s ROIs united and drawn on a volume of that frame where the run wrote one, and each ROI alone, listing a set that a sheet cannot show; and lists each series whose released and held instances are a CT, MR, PET, or RT Dose volume, are localizers, or name the head or neck, with the instances that show each; it decodes compressed pixel data by the plugin pinned to its transfer syntax; it lists each instance it could not preview, with why, and records each preview’s SHA-256, so that an attestation covers the previews too (D-017). A reviewer records an attestation of a pack with a command, which refuses a directory that holds no QC pack and quotes neither the reviewer nor a path; the release report published with the run keeps the record it was written with. The person releasing the data may confirm in the attestation, yes or no, that the output was checked for its intended use and that its residual risk was accepted under the institution’s governance, and the record that a release report takes carries those outcomes alone; the report’s QC review gives each as true, false, or not stated, and refuses one without an attestation. Each run’s release report summarises the engine’s structural checks of its output, the first pass’s references, the IOD requirements, and the written references: for each, how many instances it sequestered and how many it reported without acting on them, naming none, so that a recipient sees the output is consistent, though not that it suits a use (decided 6 October 2026).

MIDI-BP-18#

Source: MIDI report §1.6. Status: implemented.

Document the de-identification process in detail, including the release of PS3.15 and the Options used, and publish the documentation, or a reference to it, with the collection.

Decisions: D-009, D-011, D-012, D-016, D-024, D-026, D-027, D-029.

Implementation:

  • _dicom/deidentify/conformance.py

  • _dicom/deidentify/conformance_values.py

  • _dicom/deidentify/markers.py

  • _dicom/deidentify/method_digest.py

  • _dicom/deidentify/release_report.py

  • _dicom/deidentify/release_report_markdown.py

  • _dicom/deidentify/run_report.py

  • _dicom/deidentify/runtime.py

Tests:

  • tests/dicom/test_deidentify_api.py::test_the_basic_preset_releases_a_collection_with_its_report

  • tests/dicom/test_deidentify_command.py::test_a_run_given_a_reporter_publishes_its_release_report

  • tests/dicom/test_deidentify_conformance.py::test_the_statement_names_the_edition_preset_and_options

  • tests/dicom/test_deidentify_conformance.py::test_the_method_digest_is_the_policys_with_the_same_vocabulary

  • tests/dicom/test_deidentify_conformance.py::test_the_statement_is_the_same_every_time_it_is_generated

  • tests/dicom/test_deidentify_conformance.py::test_the_inserted_codes_are_listed_with_their_meanings

  • tests/dicom/test_deidentify_conformance.py::test_the_runtime_versions_are_described_not_quoted

  • tests/dicom/test_deidentify_conformance_values.py::test_the_release_report_names_sequestered_instances_by_label

  • tests/dicom/test_deidentify_conformance_values.py::test_the_release_report_counts_held_instances_by_stage

  • tests/dicom/test_deidentify_conformance_values.py::test_the_release_report_counts_roi_names_by_outcome

  • tests/dicom/test_deidentify_conformance_values.py::test_the_release_report_counts_reported_reference_findings_by_kind

  • tests/dicom/test_deidentify_conformance_values.py::test_the_release_report_summarises_the_structural_checks

  • tests/dicom/test_deidentify_conformance_values.py::test_the_release_report_records_the_releasers_confirmations

  • tests/dicom/test_deidentify_conformance_values.py::test_the_release_report_counts_pixel_risks_by_risk_and_indicator

  • tests/dicom/test_deidentify_conformance_values.py::test_every_reason_that_the_release_report_counts_is_described

  • tests/dicom/test_deidentify_descriptor_cleaning.py::test_the_runs_release_report_counts_its_roi_names_naming_none

  • tests/dicom/test_deidentify_descriptor_cleaning.py::test_an_identical_copy_of_a_structure_set_is_counted_once

  • tests/dicom/test_deidentify_descriptor_cleaning.py::test_a_structure_set_refused_by_descriptor_cleaning_counts_no_name

  • tests/dicom/test_deidentify_icc_reports.py::test_the_report_counts_cleaned_and_replaced_profiles_by_reason

  • tests/dicom/test_deidentify_markers.py::test_each_preset_adds_exactly_its_markers

  • tests/dicom/test_deidentify_markers.py::test_tps_import_claims_no_ps3_15_conformance_and_adds_no_codes

  • tests/dicom/test_deidentify_markers.py::test_the_first_method_value_is_exactly_the_method_digest

  • tests/dicom/test_deidentify_markers.py::test_the_markers_are_defined_by_the_iod_and_the_equipment_item_is_complete

  • tests/dicom/test_deidentify_markers.py::test_software_versions_give_pymedphys_then_python_pydicom_and_tomlkit

  • tests/dicom/test_deidentify_markers.py::test_software_versions_give_the_running_python_pydicom_and_tomlkit

  • tests/dicom/test_deidentify_markers.py::test_software_versions_are_the_runtime_environment_of_the_release_report

  • tests/dicom/test_deidentify_markers.py::test_each_source_of_software_versions_changes_its_own_value

  • tests/dicom/test_deidentify_markers.py::test_python_and_library_versions_change_software_versions_not_the_digest

  • tests/dicom/test_deidentify_method_digest.py::test_the_components_of_a_small_synthetic_input_are_digests_of_its_members

  • tests/dicom/test_deidentify_method_digest.py::test_the_components_show_which_input_changed_the_digest

  • tests/dicom/test_deidentify_patient_characteristics.py::test_the_run_reports_an_instance_held_for_its_free_text_naming_no_value

  • tests/dicom/test_deidentify_release_report.py::test_the_policy_section_names_the_edition_preset_and_options

  • tests/dicom/test_deidentify_release_report.py::test_the_document_has_the_sections_and_fields_the_design_lists

  • tests/dicom/test_deidentify_release_report.py::test_the_method_section_holds_the_digest_and_its_components

  • tests/dicom/test_deidentify_release_report.py::test_the_runtime_section_holds_the_values_of_software_versions

  • tests/dicom/test_deidentify_release_report.py::test_the_report_holds_no_path_of_the_installed_engine

  • tests/dicom/test_deidentify_release_report.py::test_the_report_holds_the_vocabulary_digest_not_its_entries

  • tests/dicom/test_deidentify_release_report.py::test_the_report_holds_the_reviewed_names_digest_it_was_given

  • tests/dicom/test_deidentify_release_report.py::test_a_report_with_a_field_that_could_hold_a_value_or_path_is_refused

  • tests/dicom/test_deidentify_release_report.py::test_sequestered_instances_are_listed_by_label_with_their_reasons

  • tests/dicom/test_deidentify_release_report.py::test_labels_are_random_and_carry_nothing_from_the_run

  • tests/dicom/test_deidentify_release_report.py::test_values_not_searched_are_counted_by_attribute_and_reason

  • tests/dicom/test_deidentify_release_report.py::test_exempted_findings_are_counted_by_exemption_attribute_and_place

  • tests/dicom/test_deidentify_release_report.py::test_a_run_section_with_a_field_that_could_hold_a_value_is_refused

  • tests/dicom/test_deidentify_release_report.py::test_released_instances_are_listed_by_output_name_alone

  • tests/dicom/test_deidentify_release_report.py::test_the_qc_review_is_recorded_by_the_packs_reference_and_outcome

  • tests/dicom/test_deidentify_release_report.py::test_a_run_without_a_qc_pack_records_no_qc_review

  • tests/dicom/test_deidentify_release_report.py::test_a_release_or_review_field_that_could_hold_a_value_is_refused

  • tests/dicom/test_deidentify_release_report_checks.py::test_a_run_without_instances_shows_each_check_with_none

  • tests/dicom/test_deidentify_release_report_checks.py::test_each_check_gives_what_it_sequestered_and_reported

  • tests/dicom/test_deidentify_release_report_checks.py::test_a_summary_that_could_hold_a_value_is_refused

  • tests/dicom/test_deidentify_release_report_checks.py::test_the_summary_is_a_table_of_each_check

  • tests/dicom/test_deidentify_release_report_checks.py::test_a_summary_not_in_the_reports_form_is_refused

  • tests/dicom/test_deidentify_release_report_markdown.py::test_every_value_of_the_report_is_shown_and_nothing_else

  • tests/dicom/test_deidentify_release_report_markdown.py::test_the_text_around_the_values_does_not_depend_on_them

  • tests/dicom/test_deidentify_release_report_markdown.py::test_the_form_cites_no_decision_number

  • tests/dicom/test_deidentify_release_report_markdown.py::test_the_form_is_the_same_every_time_it_is_generated

  • tests/dicom/test_deidentify_release_report_markdown.py::test_the_roi_names_are_counted_by_outcome_and_held_reason

  • tests/dicom/test_deidentify_release_report_markdown.py::test_the_icc_profiles_are_counted_as_cleaned_or_replaced_by_reason

  • tests/dicom/test_deidentify_release_report_markdown.py::test_the_reference_findings_are_counted_by_kind

  • tests/dicom/test_deidentify_release_report_markdown.py::test_the_pixel_risks_are_counted_by_disposition_risk_and_indicator

  • tests/dicom/test_deidentify_release_report_markdown.py::test_a_document_not_in_the_reports_form_is_refused

  • tests/dicom/test_deidentify_release_report_markdown.py::test_each_exempted_finding_is_a_row_with_its_place_and_count

  • tests/dicom/test_deidentify_release_report_pixel_risks.py::test_the_section_splits_the_risks_from_their_indicators

  • tests/dicom/test_deidentify_release_report_pixel_risks.py::test_a_section_not_of_the_engines_codes_is_refused_naming_no_value

  • tests/dicom/test_deidentify_release_report_pixel_risks.py::test_a_run_over_the_synthetic_corpus_reports_what_its_qc_pack_lists

  • tests/dicom/test_deidentify_release_report_reasons.py::test_each_stage_that_sequesters_gives_its_reason_code

  • tests/dicom/test_deidentify_release_report_reasons.py::test_an_unsupported_iod_is_named_with_its_reason

  • tests/dicom/test_deidentify_release_report_reasons.py::test_the_codes_of_each_stage_are_distinct

  • tests/dicom/test_deidentify_release_report_reasons.py::test_a_release_gate_reason_names_its_attribute_at_most

  • tests/dicom/test_deidentify_release_report_reasons.py::test_held_instances_are_counted_by_stage_and_reason_each_once

  • tests/dicom/test_deidentify_release_report_reasons.py::test_a_held_section_that_could_hold_a_value_is_refused

  • tests/dicom/test_deidentify_release_report_reasons.py::test_roi_names_are_counted_by_outcome_and_held_names_by_reason

  • tests/dicom/test_deidentify_release_report_reasons.py::test_a_run_without_roi_names_counts_none

  • tests/dicom/test_deidentify_release_report_reasons.py::test_roi_name_counts_that_could_hold_a_value_are_refused

  • tests/dicom/test_deidentify_release_report_reasons.py::test_reference_findings_count_each_instance_once_for_each_kind

  • tests/dicom/test_deidentify_release_report_reasons.py::test_a_run_without_reference_findings_counts_none

  • tests/dicom/test_deidentify_release_report_reasons.py::test_reference_finding_counts_that_could_hold_a_value_are_refused

  • tests/dicom/test_deidentify_retained_creators.py::test_the_report_counts_findings_that_a_kept_creator_explains

  • tests/dicom/test_deidentify_run_report.py::test_a_run_publishes_its_release_report_naming_no_value

  • tests/dicom/test_deidentify_run_report.py::test_a_run_publishes_the_reports_human_readable_form_beside_it

  • tests/dicom/test_deidentify_run_report.py::test_the_report_lists_released_inputs_and_its_qc_pack_not_yet_attested

  • tests/dicom/test_deidentify_run_report.py::test_the_report_names_each_sequestered_input_by_its_label

  • tests/dicom/test_deidentify_run_report.py::test_the_report_names_the_iod_of_an_input_the_release_does_not_support

  • tests/dicom/test_deidentify_run_report.py::test_a_report_that_cannot_be_written_publishes_nothing

  • tests/dicom/test_deidentify_run_report.py::test_a_report_whose_form_cannot_be_generated_publishes_nothing

  • tests/dicom/test_deidentify_run_report.py::test_an_input_whose_reasons_cannot_be_reported_is_sequestered_alone

  • tests/dicom/test_deidentify_run_report.py::test_an_input_sequestered_for_its_reasons_keeps_whose_copy_it_is

  • tests/dicom/test_deidentify_run_report.py::test_a_run_publishes_the_conformance_statement_of_its_policy

  • tests/dicom/test_deidentify_run_report.py::test_a_statement_that_cannot_be_written_publishes_nothing

  • tests/dicom/test_deidentify_run_report.py::test_the_report_counts_released_instances_with_exempted_findings

  • tests/dicom/test_deidentify_run_report.py::test_roi_names_are_counted_from_each_structure_sets_qc_material

  • tests/dicom/test_deidentify_run_report.py::test_the_report_counts_the_runs_roi_names_without_naming_them

  • tests/dicom/test_deidentify_run_report.py::test_a_dangling_reference_is_counted_in_the_report_and_listed_in_the_pack

  • tests/dicom/test_deidentify_run_report.py::test_a_dangling_reference_at_an_identical_copy_counts_once

  • tests/dicom/test_deidentify_run_report.py::test_the_report_counts_each_instance_once_for_each_kind_of_finding

  • tests/dicom/test_deidentify_run_report.py::test_the_report_counts_held_inputs_by_reason_without_naming_them

  • tests/dicom/test_deidentify_run_report.py::test_no_output_name_is_the_release_report

  • tests/dicom/test_deidentify_runtime.py::test_each_value_is_read_from_its_source_at_each_call

  • tests/dicom/test_deidentify_several_patient_studies.py::test_the_report_counts_the_studies_and_gives_their_instances_reasons

  • tests/dicom/test_deidentify_several_patient_studies.py::test_a_run_that_sequesters_none_reports_its_choice

  • tests/dicom/test_deidentify_several_patient_studies.py::test_the_section_gives_the_choice_and_the_count

  • tests/dicom/test_deidentify_several_patient_studies.py::test_a_section_of_another_form_is_refused_naming_no_value

  • tests/dicom/test_deidentify_several_patient_studies.py::test_a_section_that_is_not_the_runs_choice_is_refused

  • tests/dicom/test_deidentify_several_patient_studies.py::test_a_studys_reason_is_a_reference_reason

  • tests/dicom/test_deidentify_several_patient_studies.py::test_the_markdown_gives_the_choice

  • tests/dicom/test_deidentify_several_patient_studies.py::test_the_markdown_refuses_a_section_of_another_form

Note: Each instance’s markers record the process in the instance itself: De-identification Method names PyMedPhys, its version, the PS3.15 edition, and the preset, and gives the method digest; the CID 7050 codes name the profile and each Option the instance satisfies; and a Contributing Equipment Sequence item names PyMedPhys as the de-identifying equipment, and gives in Software Versions its version and the runtime environment that ran, which the method digest does not cover (D-012, D-024). The validated policy records the edition, preset, and options. The conformance statement generated from a policy names the PyMedPhys version, the PS3.15 edition, the preset, its Options with their CID 7050 codes, and its method digest with the vocabulary it covers, without the technique vocabulary, and without a reviewed-names or reviewed descriptors list, and the markers that each instance gains, and the same policy and engine always give the same text. The release report’s record of the policy (its edition, preset, and options), of the method digest with the components it is computed from (D-024), and of the runtime environment that ran, with the values that Software Versions records (D-029), is built and written as JSON, and refuses any field that could hold a source value or a path outside the engine’s package (D-016). Its sections on a run give the QC pack’s opaque reference with its attestation’s outcome (D-016), list each released instance by its output name, name each sequestered instance by a random label for the run, with each reason’s stage and code, and the attribute, action, and VR where the walker sequestered it, or the attribute where the release gate’s reason names one (D-026); count the instances held for review by stage and reason, naming none (D-009); count the ROI Names that descriptor cleaning cleaned by outcome, and the distinct names sent for review by reason, naming none (D-009); count the instances with each kind of reference finding that the run reports without acting on it, naming none (D-026); count the released and held instances that show each risk in their pixel data, and each indicator of it, naming none (D-015); count the source values that the residual search did not search, in full or in part, by attribute and reason (D-027); summarise the engine’s structural checks, each with how many instances it sequestered and how many it reported, naming none; and give the run’s choice for a study whose instances name several patients, to stop or to sequester it, with how many such studies it sequestered (D-026); the conformance statement describes them, generated from the report’s format, labels, and reason codes. A run given a reporter writes the report as release-report.json at the root of the release before it publishes it, with its QC pack by reference, not yet attested, its released instances by output name, its sequestered instances by label, its held instances by reason, an identical copy counting as the instance it copies, and the values that the residual searches did not search, each counted once at the instance that holds it, and a report that cannot be built publishes nothing and leaves no QC pack; an input whose reasons the report cannot give is sequestered for an invalid reason instead, which the report gives alone and the QC pack lists before its own reasons, so that it never stops the release. Beside the report, the run writes its human-readable form as release-report.md: CommonMark, with GitHub Flavored Markdown tables, generated from the JSON text alone, which shows every value of the document, and each file name that keys a digest, as a code span as often as the document holds it, and draws nothing else from the report; the same text always gives the same form, and a report whose form cannot be generated publishes nothing. Every reason that sequesters or holds an instance in a run, from the run’s own checks, the transform, the writer, the preservation check, and the release gate, has a stage and code in the report, and the codes within each stage are distinct. Each run given a release reporter publishes the conformance statement of its policy with its release, beside the release report. Both supported entry points, pymedphys._dicom.deidentify.api.deidentify and the command’s main, always give the run a reporter, unless a test supplies the command’s transform, so every run they start publishes the release report and the conformance statement; run.run and command.deidentify_directory, which are private, write them only when given one.