Skip to content

Breast IHC dataset release and publication

This maintainer-only procedure covers preparation, deterministic packaging, draft upload, and independently authorized publication of a breast-IHC dataset release. It is deliberately separate from the user-facing marker-quantification tutorial.

Every new release requires its own privacy, ethics, governance, rights, visible-pixel, storage-quota, metadata, and publication reviews. Never place the private source manifest, alias secret, linkage, upload token, or authorization file in the repository or public staging tree.

1. Prepare an offline release draft

bin/prepare_breast_ihc_patch_release.py prepares a new, local sanitized draft from an explicitly selected set of authorized TIFF patches. It has no network, deposit, upload, or publication capability. The source manifest, alias secret, and private linkage are private materials and must remain outside both the repository and the public staging directory.

The private source-selection CSV has these required columns:

Column Required content
case_id Private source case identifier
marker Canonical English marker: H&E, ER, PR, HER2, or Ki-67
field_id Private source field or patch identifier
source_path Authorized .tif or .tiff source path, absolute or relative to the manifest
include Exactly true or false
microns_per_pixel Verified physical scale for this individual image
mpp_provenance Allowlisted English provenance for the audited scale

For example, the following is a schema illustration, not a usable manifest. Replace every placeholder and keep the completed CSV private:

case_id,marker,field_id,source_path,include,microns_per_pixel,mpp_provenance
PRIVATE_CASE_ID,H&E,PRIVATE_FIELD_ID,/path/to/authorized/source-patch.tif,true,VERIFIED_PER_IMAGE_MPP,measured_scale_bar_calibration

mpp_provenance is not free text. Use one of the safe English provenance categories measured_scale_bar_calibration, documented_magnification_extrapolation, or externally_verified_calibration. Known cohort-specific objective/binning forms are normalized to corresponding canonical English values. The canonical value is published in patch_manifest.csv and summarized in validation_report.json; private instrument, vendor, date, or operator notes must not be placed in this column.

This provenance describes how the external audit established microns_per_pixel; it does not claim that the source TIFF's resolution tags were present or correct.

Source TIFFs may omit the TIFF Orientation tag or use its default top-left value. The sanitizer rejects a non-default orientation rather than silently changing pixel coordinates or visual orientation during re-encoding.

Create the alias secret in a private location. The utility requires a regular, non-symlink file owned by the current user, with exactly mode 0600, at least 32 random bytes, and no additional hard links. Retain a protected backup when stable aliases must be reproduced.

# Create private release material outside the repository and public staging.
install -d -m 700 /path/outside/repository/private-release-material
umask 077
dd if=/dev/urandom \
  of=/path/outside/repository/private-release-material/alias-secret.bin \
  bs=32 count=1 status=none
chmod 600 /path/outside/repository/private-release-material/alias-secret.bin

A dry run is mandatory before local draft creation. Choose new public-output and private-linkage targets that do not already exist, replace the two count placeholders with the exact included-case and included-file integers, and run:

# Validate the complete private selection and plan without writing either output.
python3 bin/prepare_breast_ihc_patch_release.py \
  --source-manifest /path/outside/repository/private-source-selection.csv \
  --alias-secret-file /path/outside/repository/private-release-material/alias-secret.bin \
  --public-output /path/outside/repository/breast-ihc-public-draft \
  --private-linkage /path/outside/repository/private-release-material/private-linkage.csv \
  --expected-cases REPLACE_WITH_INCLUDED_CASE_COUNT \
  --expected-files REPLACE_WITH_INCLUDED_TIFF_COUNT \
  --dry-run

The dry run validates the selection and prints a safe summary without private IDs or paths. It writes neither the public draft nor the private linkage. Only after it passes, rerun the same inputs without --dry-run:

# Create the local sanitized draft after the mandatory dry run passes.
python3 bin/prepare_breast_ihc_patch_release.py \
  --source-manifest /path/outside/repository/private-source-selection.csv \
  --alias-secret-file /path/outside/repository/private-release-material/alias-secret.bin \
  --public-output /path/outside/repository/breast-ihc-public-draft \
  --private-linkage /path/outside/repository/private-release-material/private-linkage.csv \
  --expected-cases REPLACE_WITH_INCLUDED_CASE_COUNT \
  --expected-files REPLACE_WITH_INCLUDED_TIFF_COUNT

The local public draft contains only the following release-side structure:

breast-ihc-public-draft/
├── patches/<case_alias>/<patch_alias>_<MARKER>.tif
├── patch_manifest.csv
├── case_marker_counts.csv
├── validation_report.json
├── SHA256SUMS
└── MD5SUMS

For each selected image, the utility fully decodes the source and sanitized TIFF, verifies identical decoded RGB pixels, strips descriptions, dates, software, vendor, OME, shaped, and other non-allowlisted metadata, and computes checksums. It carries that row's verified microns_per_pixel value into the sanitized TIFF resolution tags, then reads and verifies the embedded scale. Different rows may therefore retain different physical scales. Metadata stripping does not detect identifiers burned into pixels, so independent visible-pixel, privacy, ethics, rights, and governance review remains required.

The public patch_manifest.csv records each image's audited MPP, canonical mpp_provenance, and domain-separated decoded-RGB SHA-256. The private linkage retains both the original and canonical provenance values.

The HMAC-derived aliases appear in the public draft. Original identifiers, source paths, and the alias mapping appear only in the separately created mode-0600 private linkage. validation_report.json records that the result is draft-only and that no network, upload, or publication action occurred. The case-linkage and privacy reference explains how that protected release mapping is later used without publishing direct identifiers.

The sanitized tree is an input to local packaging, not an upload payload. Do not upload or publish it; the sanitizer cannot perform either action.

2. Package the sanitized draft locally

bin/package_breast_ihc_patch_release.py converts one completed sanitized draft into deterministic local upload files. It retains and never modifies the source tree. The package output must be a new directory outside the repository and separate from the sanitized source tree.

A packager dry run is mandatory. Replace both count placeholders with the exact counts recorded by the completed sanitized draft:

# Validate the exact draft roster and report disk/upload plans without writing.
python3 bin/package_breast_ihc_patch_release.py \
  --source-draft /path/outside/repository/breast-ihc-public-draft \
  --package-output /path/outside/repository/breast-ihc-upload-package \
  --expected-cases REPLACE_WITH_SANITIZED_CASE_COUNT \
  --expected-files REPLACE_WITH_SANITIZED_TIFF_COUNT \
  --dry-run

The dry run validates the entire source roster and reports the upload-file count and conservative additional-disk estimate. It writes no package output. Only after that exact plan passes, repeat it without --dry-run:

# Create the deterministic local package after the mandatory dry run passes.
python3 bin/package_breast_ihc_patch_release.py \
  --source-draft /path/outside/repository/breast-ihc-public-draft \
  --package-output /path/outside/repository/breast-ihc-upload-package \
  --expected-cases REPLACE_WITH_SANITIZED_CASE_COUNT \
  --expected-files REPLACE_WITH_SANITIZED_TIFF_COUNT

For this cohort, the validated planning values are 51 cases and 1,901 sanitized TIFFs. Use --expected-cases 51 --expected-files 1901 only for that exact roster. Its package has 55 top-level upload files:

breast-ihc-upload-package/
├── TQA_BC_<case-alias>.zip                 # 51 ZIP64 case archives
├── TQA_BreastIHC_manifest_bundle.zip       # 1 manifest bundle
├── packaging_report.json                   # 1 packaging report
├── SHA256SUMS                              # 1 upload checksum file
└── MD5SUMS                                 # 1 upload checksum file

The manifest bundle contains patch_manifest.csv, case_marker_counts.csv, validation_report.json, the sanitized draft's SHA256SUMS and MD5SUMS, and the generated archive_manifest.csv. The outer checksum files cover all 51 case archives, the manifest bundle, and the packaging report.

Case archives use forced ZIP64 and ZIP_STORED; no TIFF recompression occurs. For identical validated inputs and the same supported packager tool/runtime, fixed member order, timestamps, modes, and ZIP settings produce exact archive bytes independently of source filesystem metadata. This is not a guarantee of byte identity across arbitrary Python versions or ZIP implementations. ZIP_STORED also duplicates the retained TIFF payload on disk. Confirm the dry-run value of estimated_additional_disk_bytes before packaging.

Before writing, the packager verifies the exact allowlisted source roster, manifest and case-marker counts, sanitizer report, source checksums, aliases, TIFF headers, metadata allowlist, per-image MPP, and canonical MPP provenance. It fully decodes every sanitized TIFF and recomputes its domain-separated RGB SHA-256 instead of trusting the manifest value alone. After writing, it reopens every archive and verifies its exact member roster, fixed member metadata, ZIP64 requirement, stored sizes, CRC32, SHA-256, and MD5. It then verifies the exact 55-file output roster and both upload checksum files and confirms that the sanitized source roster remains unchanged.

The packager has no network, deposit, upload, or publication capability. Zenodo's default planning limits remain 50 GB and 100 files per record. The published cohort package contains exactly 55 files and 74,958,557,152 bytes, so it required additional record storage while remaining within the file-count limit. A new release still requires its own measured-size check, quota where needed, visible-pixel/privacy review, governance approval, and publication authorization.

3. Create or resume the open Zenodo draft

bin/zenodo_breast_ihc_deposit.py is the draft-only uploader for this exact release. It accepts only the 51 verified case ZIPs and the four verified auxiliary files described above. It revalidates the complete package locally, including both checksum rosters, the manifest bundle, every case archive and member, and the packaging report before it reads a credential or contacts Zenodo.

Prepare a separate JSON metadata file with a title, description, license, at least one named creator, "upload_type": "dataset", and "access_right": "open". Keep the state and token outside the package. The token file must have mode 0600 and needs only the Zenodo deposit:write scope. Start from this deliberately incomplete template; replace the creator and license placeholders with reviewed, authorized values:

{
  "title": "TumorQuantAI breast IHC raw TIFF patch dataset",
  "description": "Sanitized raw breast-IHC TIFF patches and verification manifests for research workflows.",
  "upload_type": "dataset",
  "access_right": "open",
  "license": "REPLACE_WITH_AUTHORIZED_CANONICAL_LICENSE_ID",
  "creators": [
    {"name": "REPLACE_WITH_REAL_FAMILY_NAME, REAL_GIVEN_NAME"}
  ],
  "keywords": ["digital pathology", "immunohistochemistry"]
}

The uploader rejects unresolved placeholders. It canonicalizes known legacy license aliases before fingerprinting. If optional language is present, use the canonical three-letter code. Related identifiers require an explicit supported scheme and canonical relation; omit optional empty arrays instead of writing [].

First run the network-free plan:

python3 bin/zenodo_breast_ihc_deposit.py \
  --package-dir /path/outside/repository/breast-ihc-upload-package \
  --metadata /path/outside/repository/zenodo-metadata.json \
  --state /path/outside/repository/private-release-material/zenodo-state.json \
  --plan

If the package is larger than the record's available quota, create and verify the open-access draft without uploading files, then request quota for that deposition:

python3 bin/zenodo_breast_ihc_deposit.py \
  --package-dir /path/outside/repository/breast-ihc-upload-package \
  --metadata /path/outside/repository/zenodo-metadata.json \
  --state /path/outside/repository/private-release-material/zenodo-state.json \
  --token-file /path/outside/repository/private-release-material/zenodo-token \
  --create-only

An approved account allocation is not sufficient by itself. Open that exact draft in Zenodo, choose Manage storage, allocate the additional quota to that specific draft, and select Apply. Confirm that the draft's resulting storage allocation covers the complete measured package. Only then run the same command without --create-only and record the confirmed total quota in bytes when it exceeds 50 GB:

python3 bin/zenodo_breast_ihc_deposit.py \
  --package-dir /path/outside/repository/breast-ihc-upload-package \
  --metadata /path/outside/repository/zenodo-metadata.json \
  --state /path/outside/repository/private-release-material/zenodo-state.json \
  --token-file /path/outside/repository/private-release-material/zenodo-token \
  --confirmed-quota-bytes REPLACE_WITH_CONFIRMED_TOTAL_QUOTA_BYTES

The state is mode 0600 and is bound to the exact metadata and 55 local file hashes. Repeating the command resumes by remote size and MD5. Each bucket upload attempt is exactly one PUT, with no blind transport retry. After a failed or ambiguous PUT, the uploader rereads the exact draft and reconciles the complete remote roster by filename, size, and MD5. It retries the target only when that reconciliation confirms the file is absent; an exact committed file is accepted. A pending or mismatched file, an unexpected file, or the loss of a previously verified file stops the run rather than triggering another PUT, deletion, or replacement. Before an upload attempt, --replace-mismatched permits an explicitly reviewed replacement only after every local file is rehashed. Production and sandbox are the only accepted origins; use --api-url https://sandbox.zenodo.org/api for a sandbox exercise. The command cannot publish. Inspect the resulting draft in Zenodo and retain the separate governance and publication approvals.

4. Publish the independently authorized draft

bin/zenodo_breast_ihc_publish.py is a separate publish-only command. It cannot create a deposition, change metadata, upload, replace, or delete files. Use it only after the draft uploader has verified all 55 remote files and the data steward has approved the irreversible public release.

First obtain the exact release fingerprint and canonical license without a credential or network request:

python3 bin/zenodo_breast_ihc_publish.py \
  --package-dir /path/outside/repository/breast-ihc-upload-package \
  --metadata /path/outside/repository/zenodo-metadata.json \
  --state /path/outside/repository/private-release-material/zenodo-state.json \
  --deposition-id REPLACE_WITH_EXACT_DEPOSITION_ID \
  --plan

Create a new JSON authorization file containing exactly the following keys. Copy the license and fingerprint verbatim from the plan, identify the actual authorizer, and use a timezone-aware ISO-8601 time:

{
  "deidentification_review_complete": true,
  "pixel_content_privacy_review_complete": true,
  "public_redistribution_authorized": true,
  "dataset_rights_confirmed": true,
  "license_confirmed": true,
  "metadata_review_complete": true,
  "publish_irreversibility_acknowledged": true,
  "authorized_by": "REPLACE_WITH_DATA_STEWARD_NAME",
  "authorized_at": "2026-08-04T12:00:00-04:00",
  "license": "REPLACE_WITH_EXACT_CANONICAL_LICENSE_FROM_PLAN",
  "release_fingerprint_sha256": "REPLACE_WITH_EXACT_FINGERPRINT_FROM_PLAN"
}

Keep the authorization, token, and state as three distinct files outside the package, each owned by the current user with exact mode 0600. The token needs both Zenodo deposit:write and deposit:actions. Publication requires the explicit --publish action:

python3 bin/zenodo_breast_ihc_publish.py \
  --package-dir /path/outside/repository/breast-ihc-upload-package \
  --metadata /path/outside/repository/zenodo-metadata.json \
  --state /path/outside/repository/private-release-material/zenodo-state.json \
  --deposition-id REPLACE_WITH_EXACT_DEPOSITION_ID \
  --authorization /path/outside/repository/private-release-material/publication-authorization.json \
  --token-file /path/outside/repository/private-release-material/zenodo-actions-token \
  --publish

Immediately before the one permitted publish request, the command atomically records publish-intent in the state. It does not retry that POST. If the outcome is ambiguous, rerunning the same command performs read-only reconciliation and will not issue a second publish request. The state becomes published only after the published deposition and anonymous public record both match the exact metadata and 55-file size/MD5 roster, and the public DOI and URLs have been verified.

Dataset publication status

The raw-only example is published under CC BY 4.0 at Zenodo record 21797920, dataset DOI 10.5281/zenodo.21797920. The exact deposit contains 55 files: 51 case archives holding 1,901 TIFF patches, one manifest bundle, one packaging report, and two checksum rosters, totaling 74,958,557,152 bytes. It does not contain the locally generated paper or QC figures. Cite this DOI only for the breast-IHC dataset and cite the TumorQuantAI software separately.

The repository support folder is examples/breast-ihc-patches/.