Repository navigation
Isocenter 1.0.0
Highlights
1.0.0 is the first stable release of Isocenter. These highlights are for a reader who upgrades from 0.9.x. They give the main changes of 1.0.0rc1 to 1.0.0rc22 and of this release. Each item names its issue. The full entry is in CHANGELOG.md, in the section of the candidate that made the change. The entries of this release follow the highlights.
What 1.0 promises
The API has three tiers. The API stability page names each one (#26, #787).
- Frozen (tier 1). These names keep their spelling and behaviour for every 1.x release. They are
Session, its methods and their parameters, the shapes they return, and the other callables in the frozen tables. A change to them is a 2.0. - Documented but internal (tier 2). These names are safe to call. A 1.x release can change them, with a changelog entry that names the old spelling and the new one.
- Private (tier 3). Names with a leading underscore, and every module that the page does not list, can change without notice.
- The output. For the same input, configuration and project secret, a 1.x release exports what the previous release exported, or its changelog says what changed. Each release's export of a fixed cohort is fingerprinted, and a release stops on a difference that no changelog entry names (#717).
- The configuration. Every 1.x release loads a file that 1.0 loads. A 1.x release that applies an unchanged file differently raises the schema's minor version (#762, #782). A privacy profile name is pinned to its PS3.15 edition:
basicmeansbasic@2026cin every 1.x release (#714). - Plugins. The exporter registry is provisional until 1.1. In 1.0, an export through a third-party exporter grades
REVIEW_REQUIRED(#527).
Before you upgrade from 0.9.x
Isocenter migrates nothing from 0.9.x (owner ruling on #790).
- Identity tokens. Releases before 1.0 wrote the identity token into the wrong element of the Encrypted Attributes item. 1.x refuses a token in that layout by name and does not decrypt it. To recover such an identity, use Isocenter 0.9.x and the key that locked it (#790).
- Stores. A 0.9.x store opens in 1.0. On open, 1.0 adds the columns it records into, empty, and converts no value that the store holds.
- Its PHI statuses have no recorded policy. An export of them grades
REVIEW_REQUIREDuntil you runaudit()andanonymize()under your configuration, thensave()(#555). - Its instances keep their source UIDs until the next
audit()andanonymize()replace them (#544). Files exported before 1.0 keep their source UIDs, and 1.0 exports of the same data do not link to them by UID. - 0.9.x grouped files with no Patient ID under one patient,
''orUnknownPatient. Such a store keeps that grouping, because a split would give dates already shifted a second offset. While a patient's ID in the store is still''orUnknownPatient, every open writes oneWARNINGrow that gives only a count. Every report over the store then gradesREVIEW_REQUIRED.audit(),anonymize()andsave()do not remove a row already written. A group thatanonymize()gave a pseudonym, in 0.9.x or in 1.0, is not counted. So a store that was anonymized before 1.0 can hold such a group with no row. To separate the subjects, ingest their source files into a new store (#584). - 0.9.x does not write the new columns. A status that 0.9.x saves into a store that 1.0 opened then sits beside the policy of 1.0's last scan, and 1.0 cannot detect this. Before you open a 0.9.x store in 1.0, keep a copy of it. Do not save into the 1.0 store from 0.9.x. If you go back to 0.9.x, use the copy.
- Its PHI statuses have no recorded policy. An export of them grades
- Configuration files. A file that 0.9.x's
create_config()or auto-save wrote loads unchanged, with one exception. Before 0.9.8,set_phi_tag(replacement=)saved aphi_tagsrule with areplacementkey. The loader refuses that key. Rename it tovalue(#538).- The loader refuses an unknown key, a value of the wrong type, and a
versionthat this library does not read (#711, #712, #713). - 0.9.x cannot load
privacy_profile: basic@2026c, which 1.0 writes. Writebasicin a file that both must load (#714). - A change to the configuration no longer writes the file. Call
configuration.save(), or setconfiguration.auto_save(#715).
- The loader refuses an unknown key, a value of the wrong type, and a
- Renamed and removed names.
start_patient(id=)is nowpatient_id=.add_rule(model=, zones=)is nowmodel_name=andredaction_zones=.set_phi_tag(replacement=)is nowvalue=. The old keywords raiseTypeError(#26).write_project_secret()andload_project_secret()are removed. A project secret stays in the store that made it, and a copy of the store is the same project (#716).ConfigLoader.load_redaction_rules(),ConfigLoader.load_phi_config()and theconfig_pathparameter ofPhiInspectorare removed (#729).export_dataframe()requires its output path (#812).
- Selections. A bare
straspatient_idsraisesTypeError. Writepatient_ids=["X"](#696). An ID or UID that names nothing is counted, and an export with one gradesREVIEW_REQUIRED(#686, #725). - A stricter grade. A run that graded
PASSin 0.9.x can gradeREVIEW_REQUIREDin 1.0. The analytics page lists every condition. These are the main new ones: - Changed output under
basic@2026cand the floor.- Each UID that Table E.1-1 codes
Uis replaced by a UID derived from the value and the store's project secret. One UID gets one replacement everywhere in the store, so references between exported files still resolve (#544). - An element that Table E.1-1 codes
Dgets a dummy value of its VR (#557). - Retired curve groups, whole overlay groups, Content Sequence, Graphic Annotation Sequence and Referenced File ID are removed (#556, #840, #848, #1133).
- Every exported DICOM file that is not JPEG 2000 is Explicit VR Little Endian, so each element states its VR (#1107).
- Each UID that Table E.1-1 codes
- Other changed behaviour.
- At ingest, files with no Patient ID are no longer merged into one patient. Each study that they belong to is its own patient. A store that 0.9.x wrote keeps its grouping (see Stores, above) (#584).
- A machine rule with
serial_number: "*"now also covers series with no Device Serial Number (#1108). - A private-creator key matches its creator in any case and spacing. Two keys for one creator with different rules are refused (#1147).
New in 1.0
- De-identification markers. An exported DICOM file that the audit found clean, or that
anonymize()de-identified, says how (#554, #978, #1011).- Patient Identity Removed
(0012,0062)isYES, unless the file says Burned In Annotation(0028,0301)YES. - De-identification Method
(0012,0063)names the version and the policy. - Longitudinal Temporal Information Modified
(0028,0303)isMODIFIEDwhen this store shifted a date in the file. It isREMOVEDwhen every date in the file is absent, empty or its VR's dummy. Otherwise the file keeps the source's value. But a source'sUNMODIFIEDis not kept when this store took a top-level date out of the file. - A rule of any action on one of these three tags stops that stamp.
- Patient Identity Removed
- Burned-in text. A
burned_in_textpolicy makesredact()read the images, then redact, report or withhold the text that it finds (#1108). The policy has three more settings (#1121):scope: which images to read.target: which kinds of text to act on (names, dates, IDs).detector: how to find text. Use tesseract, or a text-detection model that ships with Isocenter and runs under OpenCV (thetextdetectextra).
SCRUB. ASCRUBrule removes the identifiers inside a text element and keeps the rest of the text (#1130, #1146). It removes the kinds that you name: names, dates, IDs, organizations, phone numbers, addresses, sites and initials. WithSCRUB, you can keep a structured report's content and scrub its text values (#1131).- More precise rules. A private-element rule can name its creator, as in
'0009,"GEMS_IDEN_01",27'(#1132). A machine rule can select the series with no Device Serial Number (serial_number: ""), or select by device (match:) (#1108). - Waveforms. A WFDB export names coded ECG leads
I,II,aVR,V1and so on (#828, #832). - Documentation. The site has one folder for each minor version (#866), and it is built with Sphinx (#1155). Its tutorials run as part of the test suite (#27). A new page gives Isocenter's scores on TCIA's MIDI-B de-identification benchmark (
docs/midi-b.md).
Security fixes since 0.9.x
- Export path (GHSA-2rc2-r9r5-x7hm). Every release from 0.7.0 to 1.0.0rc15 could write a file outside the export folder when a SOP Instance UID held a path separator. Fixed in 1.0.0rc16. Its entry in
CHANGELOG.mdsays which earlier exports to check. - Free text in structured reports and presentation states. Under
basicand the floor, this text was exported as written. Fixed in 1.0.0rc4 and 1.0.0rc5 (#840, #848). - Two redaction rules on one image. Burned-in text inside a redaction zone could be exported unredacted when two rules covered one image. Fixed in 1.0.0rc10. Its entry in
CHANGELOG.mdsays to runredact()again and export again (#908).
Added
- docs: a MIDI-B benchmark page.
docs/midi-b.md(/1.0/midi-b/) gives Isocenter 1.0.0rc22's scores on TCIA's MIDI-B de-identification benchmark, scored with the benchmark's validation script at commitccc0939: 99.22% of the Validation set's checks and 99.23% of the Test set's, against 72.51% and 68.45% for the default configuration. It sets out the configuration part by part with what each part is worth, what is left and why, the choices a site may want to change, how to reproduce the run, and the whole configuration. The configuration was tuned on both sets, so neither figure is a held-out estimate. Example values are synthetic: MIDI-B's own where an issue already quotes them, invented values of the same shape otherwise. It takes the place "Upgrading from 0.9.x" held in the site's navigation and the README.
Changed
-
docs: the overview pages and the MIDI-B page follow Simplified Technical English (ASD-STE100).
docs/index.md,docs/for-institutions.md,docs/architecture.md,docs/performance.mdanddocs/midi-b.mdnow split long sentences and replace semicolons with full stops, in STE-flavored form. Their step lists and the MIDI-B "Reproducing these results" steps use strict form. No heading, link target, code block, figure or example value changed, and every hedge keeps its strength, so each published anchor and each number on these pages is as before.docs/about/index.mdanddocs/guides/index.mdalready complied and are unchanged. -
docs: the procedure pages and the README in Simplified Technical English (ASD-STE100), and a glossary.
docs/installation.md,docs/quickstart.md,docs/tutorials/index.md, the six tutorials,docs/ctp-import.mdandREADME.mdare rewritten in shorter sentences with no semicolons, one action per step, and one term for each concept. No heading, code block or link target changes, so every heading id is the same. No fact, condition or hedge changes, except two sentences that were no longer true and are corrected: the installation page said no CI job installs the spaCy model, which CI installs on every entry that is not free-threaded, and the redaction tutorial saidredact()reads every image that holds pixels under aburned_in_textpolicy, where since #1121 it reads only the images in the policy'sscope. A new page,docs/glossary.md(/1.0/glossary/, under About), names the one term the documentation uses for each concept and the words it does not use for it. The README's tagline and the home page's hero are now two sentences, and they stay identical: "De-identify a DICOM cohort without touching the source files. Hand compliance a report that names anything the run could not do." The README reaches PyPI only at the next upload. -
docs: the configuration guide follows Simplified Technical English (ASD-STE100).
docs/configuration.mdnow splits long sentences and replaces semicolons with full stops, in STE-flavored form, and its template, method and auto-discovery steps use strict form. No heading, link target, code block, number or quoted standard or runtime text changed, and every hedge keeps its strength. Four sentences that were no longer true are corrected: the burned-innamesparagraph said no CI job runs the model, which CI runs on every entry that is not free-threaded;basic@2026creplaces some dates with the dummy rather than removing or emptying all of them; aREPLACEwith avalue:on Study or Series Instance UID is refused when it loads (#877); and a machine rule can reach a series with no Device Serial Number (serial_number: ""ormatch). -
docs: the reference pages for the report, export output, codecs, OCR, waveforms, environment variables and development follow Simplified Technical English (ASD-STE100).
docs/analytics.md,docs/export-output.md,docs/codecs.md,docs/ocr.md,docs/waveforms.md,docs/environment.mdanddocs/developer_guide.mdnow split long sentences, replace semicolons with full stops, and put long series into lists, in STE-flavored form. The OCR prerequisites and the developer guide's command steps use strict form. No heading, link target, code block, quoted standard text, quoted runtime text or example value changed, so every published anchor is as before, and every hedge keeps its strength. Four sentences that were no longer true are corrected: the environment page said one test fails rather than skips underISOCENTER_REQUIRE_TEXT_DETECTOR, where several do; the developer guide called the API-page check the second command of its fence, where it is the third; the export page saidDicomExporter.write_tree()applies "the same CT Type 1 check", where since #879 the check covers CT, MR and PET; and the OCR page said the spaCy model runs in no CI job, where CI runs thenamesarm's name finder with the real model on every entry that is not free-threaded (theredact()andexport(check_burned_in=True)paths through it are tested with a stand-in model, asdocs/configuration.mdsays). -
docs: the API pages follow Simplified Technical English (ASD-STE100).
docs/api/stability.md,docs/api/session.md,docs/api/entities.md,docs/api/exporters.md,docs/api/results.md,docs/api/configuration.md,docs/api/persistence.mdanddocs/api/index.mdnow split long sentences and replace semicolons with full stops, in STE-flavored form, and the exporter-author rules use strict form. No heading, table row or code block changed, one link target changed (the transfer-syntax correction below), and every hedge keeps its strength. The 1.0 promise on the stability page means what it meant: six sentences thattests/test_frozen_surface.pypins word for word are reworded (semicolons made full stops, a passive with a known actor made active), and the test's literals change with them, in wording only. Three sentences that were not true are corrected: the exporter registry page said the Quick Start lists the transfer syntaxes ingest reads, which it never did and Codec support does, and that the two built-in formats register whenisocenteris imported, where they register whenisocenter.exportersis imported, whichsession.export()does; and the entities page said the export writes anyDecimalas afloat, where a non-finite one, or one past a float's range, stays aDecimal. -
docs: the docstrings that the API pages render follow Simplified Technical English (ASD-STE100). The rendered docstrings and
#:comments ofisocenter/session.py,entities.py,io_handlers.py(IngestSummary,ExportSummary,ExportError),persistence.py,configuration.py,privacy.py,verification.py,automation.pyandexporters/__init__.pynow split long sentences, replace semicolons with full stops, and put long series into lists, in STE-flavored form. TheArgs:,Returns:,Raises:andAttributes:items keep their shape, and no code, code comment or runtime message changed. Every hedge keeps its strength, and no tier-1 promise changes in meaning. The docstrings ofservices.py,discovery.pyandpixel_analysis.pyare not in this change. Twenty-two sentences that were not true are corrected:RedactionVerifier.verify_instancesaid a rule is selected by serial number alone, where it takes every rule that covers the equipment (services.rules_matching), so with no equipment the""and"*"rules still apply;set_phi_tagsaid a tag is stored lower-case, where a private creator's name keeps its case and has its surrounding spaces stripped;Equipment.from_partssaid every machine rule matches on the serial, where amatch:rule does not;scan_pixel_contentsaid a series with no Device Serial Number is never scanned, where a"","*"ormatch:rule covers it asredact()reads it;redactnamed only the serial as the way a rule selects a series, wherematch:selects one too;auto_remediate_configsaid it returns the number of rules updated, where it returns the number of changes applied, one per zone added or grown;reconcile_private_tagsandredact(force=)each pointed to the "Upgrading from 0.9.x" page, which no longer exists; four sentences inredact(),export()and_export_dicom()saidOcrUnavailableErroris raised when theocrextra or the tesseract binary is unavailable, where it is raised when a reader that theburned_in_textpolicy needs cannot run, tesseract is not needed underdetector: dbwithtarget: any, and its subclassesTextDetectorUnavailableErrorandNlpUnavailableErrorare raised for the text detector and the names model (#1164); five sentences in the same three said the policy's read is by OCR, where underdetector: dbthe text detector reads;redact()said itsREDACTIONrow names the tesseract version, where underdetector: dbit names the detector model and the OpenCV version;redact()andredact_by_machine()called the drain that runs a failed background save again the entry drain, where it comes afterredact()reads itsburned_in_textpolicy (as the stability page says since #1172);set_phi_tag'svaluesaid only an empty value is no value, where any value that is false in Python is (#1172); andlock_identitiesdid not say that, for Patient's Name and Patient ID, the lock reads the patient's value when an instance holds none (#1172).
Removed
- docs: the "Upgrading from 0.9.x" page (
docs/migration.md). Nothing before 1.0 is accommodated (owner ruling on #790), and each change it listed is recorded in this changelog's 0.9.x and 1.0.0rc sections. Its URLs,/1.0/migration/and/latest/migration/, and their anchors (#configuration-files,#hollow-waveform-multiplex-itemsand the rest) no longer resolve once the site is next deployed. The links to it from the index, analytics, configuration, for-institutions and waveforms pages, and from the README, are removed, not repointed.
Fixed
- docs: sentences on the API pages that the code did not bear out (#1172). Found during the ASD-STE100 rewrite (#1170). They were already on
mainbefore it. No behaviour changes.docs/api/stability.md:redact()and the two lock methods do not run a failed background save again "on entry, before anything else".redact()first reads itsburned_in_textpolicy, and returns 0 with no rules and no policy. The locks first check that reversible anonymization is enabled, and the key. The single form also checks the Patient ID. The batch form also checks its selection and the refusals of every patient. The page now lists these checks, and the Behaviours bullet says the same forredact().- The
RuntimeErrorfor no key file while the session holds a token of ours is the lock's, notenable_reversible_anonymization()'s. The lock methods' "not enabled"RuntimeErroris now listed. recover_patient_identity()also raisesRuntimeErrorwhen reversible anonymization is not enabled,ValueErrorfor a malformed key file, andRuntimeErrorfor a token from a later release. Nothing on the patient is restored after any of the errors that the page lists.set_attr()stores atuplethat holds a numpy value as a newlist, so the value held does not equal thetupleset (alsodocs/api/entities.md). #1173 is to keep thetuple.- For the
tags_to_lockrefusal, the lock reads Patient's Name and Patient ID from the patient when an instance holds none. So under the default tags this refusal does not occur for a patient whose Patient ID is astr. An emptytags_to_lockis also refused. set_phi_tag()stores novaluekey forNone,""or another value that is false in Python, such as0orFalse. #1174 is to refuse0andFalse.docs/api/entities.md: under a standardATelement,export()writes atupleof exactly two integers or twostras one tag (#1009). A pair thatset_attr()stored as alist(atuplethat holds a numpy number), or that a reopened store holds as alist(#1016), is written as two tags.set_attr()does not raise for a value of another type, butInstance.set_attr()can raiseValueErrorfor an edit to a pixel descriptor.docs/api/exporters.md: after a third-party export, the report shows the "generated before any export" note only when the store holds noEXPORTrow from an earlier export.
- docs: three API-page entries, and eight comments and docstrings, made true to the code (#1178). Found in the review of #1177. No behaviour changes.
docs/api/stability.md'srecover_patient_identity()entry did not list theRuntimeErrorthatrestore=Trueraises, before anything is written, when patients that would share a Patient ID after the restore were de-identified under different date-offset schemes. The shared-scheme entry on the same page already named the call. Itsredact()burned_in_textentry did not name theValueErrorthatredact()raises, before its drain, for a policy thatload_configwould refuse. The same entry saidredact()andexport(check_burned_in=True)raiseRuntimeErrorwhenever theocrextra or thetesseractbinary is unavailable. Underdetector: dbwithtarget: any, the policy does not need tesseract, so neither call raises when tesseract is missing. The entry now names the readers that eachdetectorandtargetneeds.docs/api/exporters.mdsaidexport()only resolves the format and dispatches. Before it dispatches, it also clears the last export's counts, and for a third-party exporter it writes theWARNINGrow and logs the same text. Four code comments inisocenter/session.pyand three test docstrings saidredact()drains on entry, where it drains after it reads itsburned_in_textpolicy and before it writes. TheDicomItem.set_attrdocstring said alistortupleis converted when it holds a numpy number, where any numpy value in it,np.str_included, makes it a newlist.