Session API
Session is the entry point: from isocenter import Session. It is the
class isocenter.session.DicomSession, rendered below. Its constructor
and every method on this page are frozen at 1.0
(API stability), and so are the attributes in its table
except store_backend, which is tier 2. The methods are listed in pipeline
order, ingest → examine → config → audit → anonymize → redact → verify →
export → report, with compact, release_memory and close last. What
the methods return and raise is on Results and errors.
Session(persistence_file=None)
The session: an indexed store of DICOM files and the de-identification pipeline over it.
Import it as from isocenter import Session. Use it in a with block,
or call close(): an open session holds a worker pool and two threads.
Attributes:
| Name | Type | Description |
|---|---|---|
store |
DicomStore
|
The object graph. |
configuration |
IsocenterConfiguration
|
The configuration
|
store_backend |
SqliteStore
|
The session's SQLite store. Its
|
persistence_file |
str
|
The store's path, or |
Open the session store at persistence_file, creating it if needed.
The store is the SQLite file and a pixel file beside it
(by default isocenter.db and isocenter_pixels.bin). It holds the
original identifiers and pixels of everything ingested; keep it where
you keep the source data. Until export(), the session writes only the
store, isocenter.log and files you ask for (a configuration, a key).
When a file named isocenter.key exists in the current working
directory, the session calls enable_reversible_anonymization()
with it, resolved to an absolute path now. With no such file,
reversible anonymization stays off and no key is created.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
persistence_file
|
str
|
Path to the SQLite database file for session persistence.
Defaults to |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
|
ingest(directory)
Ingest every DICOM file under a directory into the session store.
Walks directory recursively, reads each DICOM file into the
Patient -> Study -> Series -> Instance hierarchy, and saves the
session when it finishes.
A file that cannot be ingested does not raise. It is counted in the
returned summary and gets an ERROR audit row naming the path and
the reason, which bars a PASS grade. Check the return value: a
run that rejected files completes normally.
A file that ends the worker process reading it (the
out-of-memory killer, a decoder crash, SIGKILL) is read again
alone on a fresh worker, and rejected only if it ends that worker
too. Files already read are kept, and a death that does not recur
costs no file and logs one WARNING line. If fresh workers cannot
run at all, every file left is rejected as "Not read", with a
reason naming the usual causes (a script without the
if __name__ == "__main__": guard among them), and the call
returns. Any other failure of the worker pool raises.
Duplicate SOP Instance UIDs. A file whose SOP Instance UID an
instance in this session already holds (ingested earlier in this
call, by an earlier call, or loaded from the store) is declined:
the instance is kept, the file is not read into the store, it is
counted in IngestSummary.declined, and a WARNING audit row names
the UID, the file, and the file the instance was ingested from. An
instance the session already holds is always kept over a new file.
Among files new to this call, the one whose path sorts first is
kept, whatever order the filesystem lists them in; the sort is on
the path string as walked (os.path.join of the directory and the
name), the one the WARNING row prints. A declined file is not
recorded as imported, so ingesting the same folder again declines
it again.
Byte order. A big-endian source's values in words wider than a
byte (OW, OL, OF, OD, OV and the waveform samples) are
stored little-endian, as its pixels are. What cannot be converted
whole (a UN value, a length that is not a whole number of words,
samples with no usable Waveform Bits Allocated) is kept as read and
gets one WARNING audit row per element.
Environment. ISOCENTER_FORCE_THREADS and
ISOCENTER_MAX_TASKS_PER_CHILD have no effect: ingest runs on the
session's own process pool, which has no threads mode and never
recycles a worker. Each call that has files to read logs one
WARNING when either is set. ISOCENTER_MAX_WORKERS is read on
every call, and the pool is rebuilt when the width has changed; an
unchanged width rebuilds nothing. While another ingest() in this
session is running on the pool, a changed width is reported in one
WARNING and this call runs at the pool's current width.
Concurrency. compact() on any thread of this session raises
while an ingest runs. While a compact() is saving or rewriting,
this call waits (bounded, see Raises) and then proceeds. A file
whose pixels cannot be written in that time is rejected like any
other failed file, with an ERROR audit row naming the path and
the reason.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
directory
|
str
|
The path to the directory containing DICOM files. |
required |
Returns:
| Type | Description |
|---|---|
IngestSummary
|
How many files reached the store, and
|
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If the ingest cannot start within 180 s because a
|
save(sync=False)
Persist the current session state to the store.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sync
|
bool
|
If True, block until the save is complete. A
synchronous save first drains the persistence manager, as
|
False
|
examine()
Prints a summary of the session contents and equipment.
create_config(output_path)
Generates a unified configuration file (scaffold) in YAML format.
Reads the session inventory, pre-fills redaction rules for any machine the shipped knowledge bases recognise, adds default PHI tag policy, and writes the result as commented YAML.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
output_path
|
str
|
Where to write the generated YAML. A
|
required |
load_config(config_file)
Load a configuration file as the session's configuration.
Loading changes no data. preview_config() shows which instances
its redaction rules match; audit(), anonymize() and redact()
apply it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config_file
|
str
|
Path to the YAML configuration file. |
required |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
If |
ValueError
|
If the file fails validation: not |
preview_config()
Performs a dry-run of the currently loaded configuration.
Checks the active redaction rules against the current session inventory and prints a summary of which instances would be affected (matched) by the rules. Does not modify any data.
audit(config_path=None)
Scan every patient in the session for PHI under a tag policy.
The policy is the file at config_path when one is given, and
otherwise session.configuration.phi_tags. The scan runs in
parallel worker processes.
Before the scan, two Patient objects holding one Patient ID are
merged into the one that was in the session first, so
store.patients can get shorter, as after anonymize().
Every status the scan records is recorded with the policy it ran
under: the configuration's, or, with config_path, that file's
rules under this session's remove_private_tags. An entity whose
status is unchanged but whose policy differs is re-recorded, so the
next save() writes it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config_path
|
str
|
Path to a configuration file whose PHI rules to scan with. |
None
|
Returns:
| Type | Description |
|---|---|
PhiReport
|
The findings: iterable, indexable, and convertible
with |
Raises:
| Type | Description |
|---|---|
ValueError
|
When the file at |
RuntimeError
|
When patients sharing a Patient ID were de-identified under different date-offset schemes, so they cannot be merged; raised after the policy is validated and before anything is scanned or a project secret is created. Also on a store holding dates shifted under a project secret it no longer has. |
auto_remediate_config(report)
Analyzes the provided OCR report and automatically updates the session's configuration to fix detected leaks (by expanding zones or adding new ones).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
report
|
PhiReport
|
The findings from .scan_pixel_content() |
required |
Returns:
| Type | Description |
|---|---|
int
|
The number of rules updated. |
anonymize(findings=None)
Apply the remediation each PHI finding proposes (tag anonymization).
With findings, only those findings are remediated. An empty list,
tuple, PhiReport or iterator applies nothing and returns 0: a
filtered report that matched nothing is not a request to remediate
everything. Only findings=None, or no argument, runs a full
audit() under the current configuration and remediates every
finding it raises. An empty call still reads the store's project
secret, so a store that has lost it refuses rather than returning 0.
Nothing outside session.store is written. A finding whose
entity is itself in the graph is acted on as it is, except a
REMOVE_TAG, whose "already gone" is read on the object at the
finding's address, and which declines where the two differ. Any
other finding is resolved against the live graph at its
entity_uid and entity_path (an instance's UID from before
redact(), and a patient's original Patient ID after the pseudonym
this store minted for it, included) and acts on the object found
there, so a report kept across close() and a reopen cleans the
graph export() writes. A finding whose address names no single
object declines; one inside a sequence a pass already removed or
emptied is satisfied. A Patient ID is written, and a date shifted,
only with a value that belongs to the live patient holding it (this
store's pseudonym, and an offset seeded on that patient under its
own scheme), and declines otherwise. The findings passed are not
modified.
Two patients left holding one Patient ID (a study ingested under a
patient's original ID after that patient was anonymized) are merged
into whichever was in the session first, and the other is removed
from store.patients.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
findings
|
List[PhiFinding]
|
Specific findings to clean. |
None
|
Returns:
| Type | Description |
|---|---|
int
|
How many remediations were applied. Failures are logged and excluded, so a caller can tell a clean run from a partial one. |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
When two patients left holding one Patient ID were de-identified under different date-offset schemes. Raised at the merge, after the remediations are applied. Unreachable on a graph the library built, and reachable on one built in user code. |
enable_reversible_anonymization(key_path='isocenter.key')
Turn on reversible anonymization with the key file at key_path.
Loads the key when a file is there. This call never creates the
key file, and neither does recovery: the first lock_identities()
creates it when none exists. So a mistyped path before
recover_patient_identity() fails there with FileNotFoundError
rather than minting a key the data was never locked under.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key_path
|
str
|
Path to the key file. |
'isocenter.key'
|
Raises:
| Type | Description |
|---|---|
ValueError
|
The file at |
lock_identities(patient_id, persist=False, *, verbose=True, tags_to_lock=None)
Encrypt each instance's original identifiers into an identity token carried in the instance, for reversible anonymization.
The token is written into the Encrypted Attributes Sequence
(0400,0500) under the key enable_reversible_anonymization()
names. Values are captured from each instance, and one token is
written per distinct set of them: under the default tags a patient
with several studies carries about one token per study, and each
instance's token holds that instance's own values.
Call it before anonymize() if recovery is required:
afterwards the identifying tags no longer hold their original
values, and the lock refuses to capture what a remediation wrote.
Anything but a str (an iterable of Patient IDs, a PhiReport or
a list of findings) is handed to lock_identities_batch(), with
the same persist, verbose and tags_to_lock.
auto_persist_chunk_size is that method's own argument.
Refusals. The lock raises RuntimeError and writes no token
when the patient cannot be locked as asked:
- a value the lock would capture was written by a remediation
(
ANONYMIZED,ANON_..., a rule'svalue:, a shifted date); - a tag it names was emptied or removed by
anonymize(); the message names thetags_to_lockthat works without it; - a re-lock would lose a value the existing token holds;
- the patient carries an identity token this library wrote that the
key at the path given to
enable_reversible_anonymization()does not decrypt, or that opens to no identity record, or that is in the layout releases before 1.0 wrote, which 1.x does not read; - a re-lock over a token this store did not write (one that arrived
inside a file, or one a release before 0.9.8 wrote) would change
a value it holds, naming the tag, which need not be one
tags_to_locknames;recover_patient_identity(..., restore=True)followed by a lock is the way through; - a value it would capture is one no token can hold (
bytes), naming the tag; - Patient's Name is blank under a rule of EMPTY or REMOVE on it;
- the patient has instances and any of them holds no value in any
tag
tags_to_locknames (or it names none), counted. A tag held blank is a value, and a patient with no instances locks as 0 instances.
Each of these is judged on every instance's own values: a value a pass wrote on any study refuses the lock. An existing token is judged against the first instance that carries it. Before any patient is planned, the lock also refuses when no key file exists at the path and an instance in the session carries a token this library wrote; no key is created. Given a list or a report, every patient is checked first and, if any is refused, one error lists each and no patient is locked.
No message carries a Patient ID: a message says "this patient", its
advice spells the ID <its Patient ID>, and a replaced Patient ID
is described, not quoted. When the lock creates the key file (the
first lock under a path with none), the file is created already
written, with mode 0600.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
patient_id
|
str
|
The ID of the patient to lock; anything that
is not a |
required |
persist
|
bool
|
If True, writes each instance's token into the
row the store holds for it, immediately; an instance the
store holds no row for raises (see |
False
|
verbose
|
bool
|
If True, logs debug information. |
True
|
tags_to_lock
|
List[str]
|
The tags whose original values are embedded. When omitted: PatientName, PatientID, PatientBirthDate, PatientSex and AccessionNumber. |
None
|
Returns:
| Type | Description |
|---|---|
Union[List[Instance], LockingResult]
|
A list of modified instances. |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
When reversible anonymization is not enabled, or
for a refusal above, before any token is written. Also, with
|
TypeError
|
When |
ValueError
|
The key file at the path is empty (the message names the path) or is not a Fernet key. Neither is cached: a later call reads the file again. |
sqlite3.Error
|
With |
lock_identities_batch(patient_ids, auto_persist_chunk_size=0, tags_to_lock=None, *, persist=False, verbose=True)
Lock the identities of several patients; see lock_identities().
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
patient_ids
|
Union[Iterable[str], PhiReport]
|
The patients to
lock: an iterable of Patient IDs (read once, so an iterator
works), a |
required |
auto_persist_chunk_size
|
int
|
If > 0, persists changes and releases memory every N instances, and the call returns an empty list. |
0
|
tags_to_lock
|
List[str]
|
Passed to every patient's
lock; |
None
|
persist
|
bool
|
Passed to every patient's lock: each patient's
instances are written as they are locked. With
|
False
|
verbose
|
bool
|
Passed to every patient's lock: one debug line per patient. |
True
|
Returns:
| Type | Description |
|---|---|
Union[List[Instance], LockingResult]
|
All modified instances, or
an empty list when |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
When reversible anonymization is not enabled, or
when any patient found cannot be locked as asked (the
refusals |
TypeError
|
When |
ValueError
|
The key file is empty or is not a Fernet key, as
for |
sqlite3.Error
|
A store write failed while tokens were being
persisted ( |
recover_patient_identity(patient_id, restore=True)
Decrypt a patient's identity tokens and return the identity they
hold; with restore=True, also write it back onto the patient.
Which token speaks for the patient. The first token of ours in study, series and instance order: a study without a token, or with an Encrypted Attributes Sequence this library did not write, is walked past. For a patient with a Patient ID, a first token holding a blank Patient ID is passed over for the first token holding a non-blank one, when there is one. A subject with no Patient ID is spoken for by its first token always, and keeps its key.
Every distinct token is opened before anything is written, with
restore=False too: a token of ours on any study that this key
cannot open, or that holds no record, raises and writes nothing. So
restore=False also checks that the patient is recoverable under
this key. Every failure raises; nothing is printed, and no message
names a Patient ID.
What restore=True writes. Every instance of the patient
in memory takes the locked identity tags from the token it
carries. The restore is recorded, so a later save() stores it, and
a patient already holding the restored Patient ID is merged into
whichever of the two was in the session first.
- Only the locked tags are put back. Every other date stays shifted
by the patient's offset, so intervals are intact and a later
audit()does not shift it again. A date among the locked tags is put back like any locked tag, and a lateraudit()raises it again. - A restored Study Date is also put back on each
Study, which is whereexport()reads it, from that study's own token, when the restored value reads as a date. A blank or unreadable one leaves theStudyas it is, with one WARNING per such study that carries no date. - An instance carrying no token takes only the patient-level identifiers (group 0010, which include Patient's Age, Size and Weight, and so may be another study's) of the token that speaks for the patient, and keeps its other locked identifiers as the pass left them; one WARNING gives the count. For a patient with no Patient ID, such an instance keeps a non-blank Patient ID of its own rather than take the token's blank one, and a second WARNING counts those.
- Where tokens disagree on Patient's Name or Patient ID, each
instance keeps its own and the
Patienttakes the speaking token's, with a WARNING. A token whose Patient ID is blank does not disagree on it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
patient_id
|
str
|
The Patient ID the patient holds now (normally its pseudonym). |
required |
restore
|
bool
|
If True, write the recovered identity back, as above. If False, write nothing. |
True
|
Returns:
| Type | Description |
|---|---|
Dict[str, Dict[str, Any]]
|
The identity recovered. Each key is
the SOP Instance UID of an instance carrying an identity
token of ours, as it was when the call began; each value is
a deep copy of the values that instance's token holds, keyed
|
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
No key file at the path given to
|
ValueError
|
No patient in this session holds |
RuntimeError
|
When reversibility is not enabled; the patient
has no instances, or no instance carrying an identity token
(an Encrypted Attributes Sequence that did not come from
this library counts as no token, not as the wrong key); the
key does not decrypt the token, or the key opens it but it
holds no identity record this library writes, or was written
by a later release of this library; an instance's token is in
the layout releases before 1.0 wrote, the token in
|
redact(show_progress=True, force=False)
Apply the configured pixel redaction zones to every matching image.
Uses session.configuration.rules: every image of a series whose
Device Serial Number a rule matches has each of the rule's zones
set to zero. This changes pixel data in memory (and the sidecar,
for persistence); call save() afterwards to persist it. A
redacted instance takes a new SOP Instance UID, derived from its
source SOP Instance UID and the rule's zones.
Concurrency. compact() on any thread of this session raises
while a pass runs. While a compact() is saving or rewriting, this
call waits (bounded, see Raises) and then proceeds. A worker whose
pixels cannot be written in time comes back as a failed redaction,
with an ERROR audit row.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
show_progress
|
bool
|
If True, displays a progress bar. |
True
|
force
|
bool
|
Redact again the instances already redacted under
this configuration, instead of skipping them. Every
instance the rules match is redacted again, and its SOP
Instance UID is derived again from its source SOP Instance
UID and the rule's zones: unchanged zones give the UID and
exported filename it already has, and other zones give
another. |
False
|
Returns:
| Type | Description |
|---|---|
int
|
How many instances had at least one configured zone
applied to their pixels. An instance a rule matched but
whose every zone fell outside the image is not counted;
a zone with no area fails its instance and the pass raises
|
Raises:
| Type | Description |
|---|---|
RedactionError
|
If any instance's zone could not be applied.
Raised at the end of the pass, not at the first failure:
the instances that could be redacted are redacted, the
failures are already |
Exception
|
Whatever the redaction backend raised, after logging it. |
RuntimeError
|
If the pass cannot start within 180 s because a
|
RuntimeError
|
On a |
redact_by_machine(serial_number, roi)
Redact one zone on one machine's images, without editing the configuration.
Replaces the configuration's rules with one rule for
serial_number holding roi, runs redact(), and restores the
original rules afterwards, also when redact() raises.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
serial_number
|
str
|
The device serial number to target. |
required |
roi
|
List[int]
|
The Region of Interest as [y1, y2, x1, x2]. |
required |
Raises:
| Type | Description |
|---|---|
RedactionError
|
Propagated from |
RuntimeError
|
Propagated from |
scan_pixel_content(serial_number=None)
Scan instances for burned-in text with OCR, and report the text no configured redaction zone covers.
Only instances of machines (by Device Serial Number) the current configuration has a rule for are scanned; other machines are skipped.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
serial_number
|
str
|
Scan only the machine with this serial number. |
None
|
Returns:
| Type | Description |
|---|---|
PhiReport
|
Findings for burned-in text no zone covers. Each
finding's |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
|
discover_redaction_zones(serial_number, sample_size=50, min_confidence=80.0)
OCR a random sample of one machine's instances and collect where burned-in text appears.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
serial_number
|
str
|
The Device Serial Number of the machine. |
required |
sample_size
|
int
|
The most instances to read; a machine with more is sampled at random. |
50
|
min_confidence
|
float
|
The lowest OCR confidence, 0 to 100, a text candidate needs to be kept. |
80.0
|
Returns:
| Type | Description |
|---|---|
DiscoveryResult
|
Every text candidate found. Call |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
|
reconcile_private_tags()
Delete stored private-tag rows that the store's core attributes do not hold.
A repair for a store de-identified before 0.9.1, whose stripped private tags come back when it is opened; see Upgrading from 0.9.x.
It deletes every instance_attributes row whose tag is absent from
its instance's stored core attributes, removes the same tags from
the in-memory graph, and writes one RECONCILE_PRIVATE audit row
per affected instance. The graph edit advances no revision: store
and graph agree afterwards, nothing reads as unsaved, and stored
PHI statuses are kept.
Call it only for a store you know was de-identified before
0.9.1. In a store that keeps its private tags
(remove_private_tags: false, saved by 0.9.1 or later), those rows
are the private data, and this deletes all of them. If unsure, run
anonymize() and save() instead: a save removes the rows of tags
the graph no longer holds. Nothing makes this choice automatically.
Returns:
| Type | Description |
|---|---|
int
|
|
export(folder, format='dicom', **options)
Export the session to a directory in the requested format.
Either format writes one WARNING audit row, and changes nothing it
writes, when the instances it writes carry PHI statuses recorded
under a policy that is neither the one in force nor one this
session scanned under, or with no recorded policy (a store written
before 1.0); the report then grades REVIEW_REQUIRED.
check_burned_in=True re-audits first, so it never does.
Either format also writes one WARNING audit row, and logs one
WARNING line, when patient_ids names an ID no patient in the
session holds: counted by position, never named, and the report
then grades REVIEW_REQUIRED. The patients that match are exported
as asked; nothing raises. The dicom format does the same for a
subset value that names nothing in the session at any level.
A format served by any exporter other than the two built-in classes
(one registered through exporters.register, a subclass of a
built-in, or another class registered as dicom) writes one
WARNING audit row before it runs, saying its output is not
attested by Isocenter, so the report grades REVIEW_REQUIRED. None
of the export gates runs for it, and it writes no EXPORT row. The
registry is provisional until 1.1; see
Exporter registry.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
folder
|
str
|
Output directory. |
required |
format
|
str
|
Registered format name. "dicom" (default) writes cleaned DICOM files; "wfdb" writes PhysioNet WFDB records. |
'dicom'
|
**options
|
dict
|
Passed through to the selected exporter. The DICOM format's options are listed under "DICOM export options" on the Session API page. |
{}
|
Returns:
| Type | Description |
|---|---|
Any
|
The selected format's own result object. The DICOM
exporter returns an
ExportSummary, whose
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
TypeError
|
For an option name the selected exporter does not
recognise; nothing is written. The two formats do not accept
the same options, so a caller forwarding one dict to both
must split it. Also, on both formats and before anything is
written, for a |
ExportError
|
From either exporter, when zero of N
attempted instances reached disk and at least one failed.
An empty plan (zero of zero) does not raise: a subset that
matched nothing is a fact about the run, and the |
export_dataframe(output_path='export_metadata.csv', expand_metadata=False, patient_ids=None)
Write the cohort report to CSV or Parquet, and return it.
The format is chosen from the extension: .parquet writes
Parquet, anything else writes CSV.
It reports the session's in-memory graph and does not save()
first: pending edits are not committed as a side effect.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
output_path
|
str
|
The output file path (ends with .csv or .parquet). |
'export_metadata.csv'
|
expand_metadata
|
bool
|
If True, includes all DICOM attributes as columns. |
False
|
patient_ids
|
Iterable[str]
|
Restrict the export to
these Patient IDs, read by |
None
|
Returns:
| Type | Description |
|---|---|
pd.DataFrame
|
The frame that was written. |
Raises:
| Type | Description |
|---|---|
ImportError
|
If pandas (or, for Parquet, a Parquet engine) is not installed. |
TypeError
|
For a |
Exception
|
Whatever the Parquet write raised, after logging it. |
get_cohort_report(expand_metadata=False, patient_ids=None)
Return a pandas DataFrame of the cohort, one row per instance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
expand_metadata
|
bool
|
If True, add a column for every DICOM attribute. |
False
|
patient_ids
|
Iterable[str]
|
Restrict the report to
these Patient IDs, read exactly as |
None
|
Returns:
| Type | Description |
|---|---|
pd.DataFrame
|
One row per instance. |
Raises:
| Type | Description |
|---|---|
TypeError
|
If |
phi_status_summary()
What the session currently knows about the PHI in each entity.
Counts PhiStatus per level, as each entity stands now rather than
as the last scan left it: an entity edited since it was scanned
counts as UNSCANNED.
Series are not counted: the scan records a status on patients, studies and instances only.
redact() is the one edit that keeps an instance's status: an
instance REMEDIATED or CLEARED before the pass reads the same after
it, provided nothing but redaction's own writes changed it. See
PhiStatus.
A status is counted whatever policy it was recorded under;
phi_status_policy names that policy, and an export() of
statuses recorded under a policy other than the one in force says
so.
Returns:
| Type | Description |
|---|---|
Dict[str, Counter]
|
Keyed "patients", "studies", "instances"; each a Counter of PhiStatus to how many carry it. |
generate_report(output_path, format='markdown')
Write the compliance report for the session's store.
The report holds the grade (PASS or REVIEW_REQUIRED), decided
from the audit trail the store holds and the graph's PHI statuses
("How the grade is decided" in Analytics & Reporting), with the session's counts,
the audit actions, data loss, exceptions, and the policy in force.
Generate it after export(): an export writes rows of its own, and
a report generated before any export carries a note saying so.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
output_path
|
str
|
The file path where the report should be saved. |
required |
format
|
str
|
|
'markdown'
|
Raises:
| Type | Description |
|---|---|
ValueError
|
For any other |
generate_manifest(output_path, format='html')
Write an HTML or JSON manifest of every instance in the session.
One entry per SOP Instance in the session, with its source file path and key metadata (Modality, Manufacturer, and so on).
Each JSON item's anonymized is True when the last tag-policy PHI
scan left no identifier unremediated on that instance's patient,
study or instance, and none of the three has been edited since. It
does not mean "anonymize() ran" (a clean input reads True after
audit() alone), and it says nothing about burned-in pixel text.
After anonymize() the UIDs listed are the replacements, beside
each instance's source file path, so the manifest maps source files
to exported UIDs: keep it with the store, not with an export.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
output_path
|
str
|
The file path where the manifest should be saved. |
required |
format
|
str
|
|
'html'
|
Raises:
| Type | Description |
|---|---|
ValueError
|
For any other |
save_analysis(report)
Persists the results of a PHI analysis to the database.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
report
|
Union[PhiReport, List[PhiFinding]]
|
The PHI report object or list of findings to save. |
required |
compact()
Rewrite the pixel sidecar (_pixels.bin) to reclaim the space of
frames no instance references, and point every loader at the new
offsets.
An expensive I/O operation. It starts with save(sync=True), so it
waits for a background save that is running.
Two behaviours are contract, observable from any thread of this session:
- It raises
RuntimeErrorwhile aredact()oringest()pass is open on the same store. The check comes first, before the leading save, so a refused call has done nothing. - A
redact()oringest()that starts while it is saving or rewriting waits, bounded by 180 s, and then proceeds.
Every frame writer, and this method for the whole rewrite, holds a
cross-process lock beside the sidecar, so a frame written while
compaction runs lands in the compacted file. A writer that cannot
take the lock within 180 s raises RuntimeError; a background save
that expires this way is logged as Background save failed and its
instances stay unsaved for the next save.
The rewrite holds the lock for its whole length, about 0.2 s per GB
on local SSD. A close() whose persistence worker is queued behind
a compaction longer than 30 s reports that worker as wedged.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
While a |
release_memory()
Release cached pixel and waveform data from every instance.
Each instance goes through Instance.unload_pixel_data(), with its
precondition: an array replaced through set_pixel_data() and not
since written is kept. An array mutated in place is not
tracked, so it is dropped here, and the next get_pixel_data()
returns the frame from before the mutation. Only an array a save
has already written can be mutated in place: a frame read from a
file or from the store is read-only.
Cached waveform samples are int16 of shape (num_samples, num_channels): about 80 KB for a 10-second 12-lead, about 104 MB for a 24-hour 3-channel Holter.
Its progress bar follows ISOCENTER_SHOW_PROGRESS.
close()
Shut the session down: the persistence-manager thread, the audit thread that owns the sqlite connection, and the process pool. A session that is never closed leaks its worker subprocesses for the life of the process.
All three steps run even if an earlier one raises. If more than one fails, the first failure is raised and the later ones are logged. A second call is safe; it repeats the shutdown messages and the unsaved-instances WARNING.
DICOM export options
export(folder, format="dicom", **options) takes the options below for
the dicom format. Their names and defaults are frozen with export()
(API stability). An option name the format
does not take raises TypeError, and nothing is written. The table is
read from the format's own method, which is private: pass the options to
export(), never call that method directly.
Export the session as DICOM, one folder per patient, study and series.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
folder
|
str
|
The output directory path. |
required |
use_compression
|
bool
|
If True, compresses output images using JPEG 2000 (lossless). A 16-bit image with more than one sample is written this way too, and pydicom with only its Pillow plugin cannot decode it; the export names each such instance at INFO. |
True
|
check_burned_in
|
bool
|
If True, scans for PHI before exporting and
withholds every instance that still carries an identifier,
at any level of its hierarchy. Each withheld instance
writes one |
False
|
check_reversibility
|
bool
|
If True (the default), warn when the
files this export wrote still carry the encrypted originals
that |
True
|
patient_ids
|
Iterable[str]
|
Limit export to
specific Patient IDs. Only |
None
|
show_progress
|
bool
|
If True, shows progress bar. |
True
|
subset
|
Union[str, pd.DataFrame, Iterable[str]]
|
Filter the
export: a pandas query string run against
|
None
|
verify_readback
|
bool
|
If True, each worker re-reads the file
it just wrote before it is published under its real name,
and holds it against what it meant to write: Rows,
Columns, SamplesPerPixel, NumberOfFrames and
BitsAllocated against the dataset it serialized; then the
file's Passing True can cost a file the default export
delivers. By default a Photometric Interpretation the
written syntax does not admit ( An unreadable or undecodable file, or any mismatch, fails
that instance's export: it is counted out of "Instances
Written", gets an |
False
|
Returns:
| Type | Description |
|---|---|
ExportSummary
|
What reached disk and what did not; empty when nothing was attempted. |
Raises:
| Type | Description |
|---|---|
TypeError
|
|
ValueError
|
|
ExportError
|
Every planned instance failed (nothing written and
at least one failure), raised last, after the audit rows
and the |