Device Catalog

The device catalog is a Python module that unifies image filtering and pixel scrubbing into a single, structured representation of known imaging devices. Each entry identifies a specific hardware device by a set of DICOM attribute patterns (manufacturer, model, modality, software version, image dimensions) that match the images that device produces. The engine uses these patterns to recognize those images and blank the pixel regions where the device burns in text.

Note

The bundled catalog comes from studies on a single PACS at one medical research center. Its device rules and pixel scrub regions reflect the scanner fleet seen there. They are unlikely to be complete or correct for another site. Treat the included catalog as a starting point that requires local validation. See Limitations and portability.

Goals

The device catalog is built on the following design choices:

  1. Model known devices, not arbitrary expressions. Each entry matches a specific imaging device by manufacturer, model, modality, software version, and image dimensions. The rules read as data, not code.

  2. Couple resolution to scrub regions. Each device entry contains resolution-specific variants that bind image dimensions directly to the pixel regions to blank. Each device defines its filter decision and its scrub regions together.

  3. Return a structured decision with a reason. Every evaluation returns a CatalogDecision containing the action (allow, deny, scrub), a human-readable reason string, and the list of scrub regions. The reason propagates through the pipeline into the FILTERED result so callers can distinguish why a file was rejected.

  4. Validate at import time. The catalog is a Python module. The interpreter catches syntax errors when it loads the module. It runs no runtime script parsing.

Modules

Module

Description

dicom_dre.catalog

Core matching engine, dataclasses, DeviceCatalog

dicom_dre.default_catalog

Device and exclusion rule data

dicom_dre.pixel_blanker

Python pixel blanking (JPEG DCT + pydicom/numpy)

String matching

Pattern strings on device fields use a prefix to select the match operator. All comparisons are case-insensitive except /regex/ patterns, which are case-sensitive unless the pattern includes an inline flag such as (?i).

Prefix

Operator

Example

(none)

case-insensitive substring

"KONICA"

=

case-insensitive exact

"=GE MEDICAL SYSTEMS"

^

case-insensitive starts-with

"^GE MEDICAL"

/regex/

re.search (case-sensitive by default)

"/[1-9]\\d{3,}/"

= (alone)

tag absent or blank

"="

A list of values on any field uses OR semantics: at least one must match. All fields on a single device rule are AND’d together.

The = exact match compares the full value case-insensitively. A /regex/ pattern is evaluated with re.search and is case-sensitive unless the pattern includes an inline flag such as (?i). To match an exact value case-insensitively with a regex (for example when you also need anchoring, alternation, or a negative lookahead), anchor the pattern and add (?i), as in "/(?i)^SIGNA PET\\/MR$/". This is why many regex rules in the default catalog use the /(?i)^...$/ form.

Device rules

Each device entry is created with the device() factory function:

device(
    "KONICA 0402 CR",
    "allow",
    manufacturer="KONICA",
    modality="^CR",
    manufacturer_model_name="0402",
    variants=[
        variant(software_versions="^6.", rows=2446, cols=2446, scrub=[(0, 2308, 2446, 137)]),
        variant(software_versions="^2.", rows=2010, cols=2446, scrub=[(0, 0, 2446, 115)]),
    ],
)

The first positional argument is the device name, which becomes the reason field of the returned CatalogDecision. The second positional argument is the action: "allow", "deny", or "scrub".

Device rule fields

All keyword fields on device() are optional and default to None (unconstrained). A field constrains the match only when you specify it. Every specified field is AND’d together: a device matches only when all of its fields match. When you give a field a list, the list uses OR semantics.

Field

DICOM tag

Purpose

manufacturer

Manufacturer (0008,0070)

Vendor name

modality

Modality (0008,0060)

CT, CR, DX, MR, US, etc.

manufacturer_model_name

ManufacturerModelName (0008,1090)

Device model

software_versions

SoftwareVersions (0018,1020)

Firmware/software revision

image_type

ImageType (0008,0008)

All listed components must appear

image_type_any

ImageType (0008,0008)

At least one component must appear

image_type_exclude

ImageType (0008,0008)

None of the components may appear

burned_in_annotation

BurnedInAnnotation (0028,0301)

YES/NO flag

sop_class_uid

SOPClassUID (0008,0016)

SOP class UID pattern

secondary_capture_device_manufacturer_model_name

SecondaryCaptureDeviceManufacturerModelName (0018,1018)

SC device model (used by GE workstations)

series_description

SeriesDescription (0008,103E)

Free-text series label

code_meaning

CodeMeaning (0008,0104)

Coded concept meaning

comments_on_radiation_dose

CommentsOnRadiationDose (0040,0310)

Dose report text

study_description

StudyDescription (0008,1030)

Free-text study label

conversion_type

ConversionType (0008,0064)

Secondary-capture conversion type

pixel_spacing

PixelSpacing (0028,0030)

Physical pixel spacing

body_part_examined

BodyPartExamined (0018,0015)

Anatomic region

requires_ultrasound_regions

SequenceOfUltrasoundRegions (0018,6011)

When True, a non-empty regions sequence must be present

variants

n/a

List of resolution/version variants (see below)

rows / cols / scrub

Rows / Columns / n/a

Shorthand for a single-variant device

Every match attribute has a dedicated field; there is no free-form keyword dict. The catalog evaluates each field with the same string matching rules. It matches only the attributes that DicomTags.from_dataset() reads.

The three image_type* fields match differently depending on whether the pattern is a single string or a list. ImageType is a multi-valued tag stored as backslash-joined components (for example ORIGINAL\PRIMARY\AXIAL). A list value such as image_type=["DERIVED", "PRIMARY"] matches each element against the individual components. The catalog matches a single string containing backslashes such as image_type="DERIVED\\PRIMARY\\DIXON\\WATER" against the full joined string, which lets you require a specific component order.

The rows, cols, and scrub shorthand parameters are mutually exclusive with variants. Supplying both raises ValueError at import time. Use the shorthand for a device with a single resolution; use variants for a device with multiple resolutions or software versions.

Variants

A variant binds a resolution (rows, cols) or software version to a set of scrub regions. All fields in a variant are optional. A device with no variants has no dimension constraint.

variant(
    rows=2446,           # exact Rows match
    cols=2446,           # exact Columns match
    software_versions="^6.",  # further narrows the device-level version
    image_type=["DERIVED"],   # ImageType components, all must appear
    scrub=[(x, y, w, h)],     # pixel regions to blank
)

A variant with only scrub (no rows/cols) applies those regions to all resolutions. A variant with only rows/cols (no scrub) allows files at that resolution without blanking.

When a device has variants, the evaluation finds the first variant whose constraints match the DICOM file. If no variant matches, the engine skips the device.

Skipping abandons the whole device rule, including its filter decision. Evaluation continues to later device rules and then the exclusion list, which commonly denies the file. As a result, every resolution you want allowed needs its own variant, even one with no scrub regions (variant(rows=780, cols=800)). The engine does not implicitly allow a resolution with no matching variant.

Scrub region coordinates

Scrub regions are (x, y, width, height) tuples in pixel units. The origin (0, 0) is the top-left corner of the image. x increases to the right, y increases downward. A region is the rectangle spanning columns x to x + width and rows y to y + height.

Regions may extend past the image bounds; the pixel blanker clips to the actual dimensions. The default catalog uses a wide sentinel width such as 10000 to blank a full-width banner regardless of the exact column count (for example, scrub=[(0, 0, 10000, 70)] blanks the top 70 rows). The blanker blanks multiple regions on one variant independently: scrub=[(0, 0, 500, 80), (256, 0, 256, 22)].

Scrub-only devices

A device with action="scrub" accumulates scrub regions but does not make a filter decision. Evaluation continues to subsequent devices and exclusion rules to determine whether the file is ultimately allowed or denied.

Scrub accumulation is forward-only. An earlier scrub device attaches its regions to a later allow (or exclusion deny) decision, but not the reverse. An allow or deny match returns immediately, so evaluation never reaches a scrub device declared after it. Because default_devices appends _scrub_only_devices last, its rules contribute only when no earlier device returned allow. To apply a scrub-only overlay to a file that a specific device already allows, add the scrub region to that device’s variant instead of relying on a later scrub-only rule.

device(
    "Siemens CT Dose overlay",
    "scrub",
    manufacturer="SIEMENS",
    modality="CT",
    image_type_any=["DOSE"],
    scrub=[(0, 0, 512, 30)],
)

Exclusion rules

Exclusion rules form the deny-list. The engine checks them after all device rules.

deny_modalities

deny_modalities(
    exact=["SR", "RTPLAN", "RTDOSE"],  # exact modality match
    substring=["XA", "MG", "US"],      # substring modality match
)

deny_when

deny_when("Burned-in annotation", burned_in_annotation="=YES")
deny_when("Encapsulated PDF", sop_class="=1.2.840.10008.5.1.4.1.1.104.1")
deny_when("INFINITT PACS", manufacturer="INFINITT", series_number="/[1-9]\\d{3,}/")

All keyword arguments to deny_when are AND’d together. Available parameters are: sop_class, sop_class_not, burned_in_annotation, image_type_empty, image_type_any, image_type_exclude, manufacturer, manufacturer_model_name, manufacturer_model_name_fallback, modality, modality_not, series_number, conversion_type_present, graphic_annotation_absent, referenced_instance_absent.

sop_class_not matches when the SOP Class UID matches none of its patterns (an allowlist condition). graphic_annotation_absent=True matches when the Graphic Annotation Sequence (0070,0001) is absent or empty. referenced_instance_absent=True matches when the Current Requested Procedure Evidence Sequence (0040,A375) nests no Referenced SOP Instance UID (0008,1155).

Presentation state admission

Modality PR instances are admitted on the presence of a graphic annotation. Two ordered deny_when rules implement this:

deny_when(
    "unsupported presentation state",
    modality="=PR",
    sop_class_not=[
        "=1.2.840.10008.5.1.4.1.1.11.1",   # Grayscale (GSPS)
        "=1.2.840.10008.5.1.4.1.1.11.2",   # Color
        "=1.2.840.10008.5.1.4.1.1.11.3",   # Pseudo-Color
        "=1.2.840.10008.5.1.4.1.1.11.4",   # Blending
        "=1.2.840.10008.5.1.4.1.1.11.5",   # XA/XRF Grayscale
        "=1.2.840.10008.5.1.4.1.1.11.12",  # Variable Modality LUT
    ],
),
deny_when(
    "no annotation data",
    modality="=PR",
    graphic_annotation_absent=True,
),

A PR instance whose SOP Class UID is outside the six admitted 2D softcopy presentation state classes is denied with reason unsupported presentation state. An admitted-class PR instance carrying no Graphic Annotation Sequence is denied with reason no annotation data. An admitted-class PR instance with a non-empty Graphic Annotation Sequence reaches the default allow action. The Empty ImageType exclusion is scoped with modality_not=["=PR"] so it does not reject presentation states, which carry no Image Type.

Key Object Selection admission

Key Object Selection (KO) documents, SOP Class 1.2.840.10008.5.1.4.1.1.88.59, are admitted on the presence of a referenced instance. Two ordered deny_when rules replace the former single SR/KO deny:

deny_when(
    "unsupported structured report",
    sop_class="^1.2.840.10008.5.1.4.1.1.8",
    sop_class_not=["=1.2.840.10008.5.1.4.1.1.88.59"],
),
deny_when(
    "no referenced instances",
    sop_class="=1.2.840.10008.5.1.4.1.1.88.59",
    referenced_instance_absent=True,
),

The first rule denies the SR family and the three non-SR classes caught by the ^1.2.840.10008.5.1.4.1.1.8 prefix (Ophthalmic Visual Field, Ophthalmic Thickness Map, Corneal Topography Map), while exempting KO. The second denies a KO that references no instance with reason no referenced instances. A KO that references at least one instance reaches the default allow action. The Empty ImageType exclusion is scoped with modality_not=["=PR", "=KO"] so it does not reject KO, which carries no Image Type.

Evaluation order

  1. Iterate device rules top-to-bottom. For each rule, check device-level fields, then check variants.

  2. On first match:

    • "allow": return allow decision with accumulated scrub regions, stop.

    • "deny": return deny decision, stop.

    • "scrub": accumulate scrub regions, continue.

  3. After all devices, iterate exclusion rules top-to-bottom.

  4. On first exclusion match, return deny decision.

  5. If nothing matched, return the default action ("allow" for the default catalog).

CatalogDecision

Every call to DeviceCatalog.evaluate() returns a frozen CatalogDecision:

@dataclass(frozen=True, slots=True)
class CatalogDecision:
    action: str                              # "allow", "deny", or "scrub"
    reason: str                              # matched rule name or exclusion reason
    scrub_regions: list[ScrubRegion]         # (x, y, w, h) regions to blank
    matched_variant: Variant | None          # the variant that matched, if any

The reason field receives the device name argument for device matches, or a constructed string for exclusion matches (for example, "Denied modality: XA").

Default catalog

dicom_dre.default_catalog contains the device and exclusion rules. It groups device rules by modality:

  • CR/DX (computed radiography and digital radiography)

  • CT, CT/PET, PET/MR

  • NM (nuclear medicine)

  • Mammography

  • Breast MRI

  • Ultrasound

  • Scrub-only devices (dose overlays and workstation-generated images)

The exclusion list covers denied modalities, SOP classes, burned-in annotations, empty ImageType, specific vendors (INFINITT, Vidar, iCAD, biopsy equipment), and image type combinations (DERIVED, SECONDARY, MRSC).

get_default_catalog() assembles all device lists and the exclusion list into a DeviceCatalog with default_action="allow".

For a step-by-step walkthrough of adding support for a new device, see Extending the catalog.