External IIIF Source Inventories
timeatlas.SourceInventory normalizes CSV/JSON metadata for images already
hosted by an external IIIF server. It uses the same camelCase source configuration
as the ingest assistant. It performs no network requests: the supplied metadata
and service capabilities are validated locally, not verified against a server.
It also exposes normalize_source_manifests for already downloaded IIIF
Presentation 2/3 manifests, using the same inventory and generation pipeline.
plan_source_image_urls and normalize_source_image_urls support image URL
lists enriched from host-downloaded Image API information responses.
Configuration
An inventory has one row per canvas, a stable image identifier, a complete image
request URL, positive width/height, and the MIME type of the delivered image.
Optional columns provide the image service root and label. Do not supply a
info.json URL as the service root. For example:
config = {
"enabled": True,
"inputType": "inventory",
"columns": {
"imageKey": "filename", "imageUrl": "url",
"width": "width", "height": "height", "mediaType": "media_type",
},
"imageService": {"version": "3", "profile": "level2"},
"grouping": {"mode": "single", "label": "Volume I"},
"ordering": {"mode": "natural", "field": "filename"},
"recordLink": {
"recordKeyField": "record_id", "recordImageField": "page_number",
"imageJoinField": "filename", "normalization": "page-number",
"missing": "error",
},
}
grouping.mode also accepts field with field naming a column, or
per-image. grouping.labelField maps a document label column; conflicting
labels inside one manifest are rejected. ordering.mode also accepts
inventory (input order) or numeric (a numeric ordering column).
recordLink.normalization accepts exact or page-number. The latter
matches numeric page values such as 174.0 to image filenames such as /174.jpg
and explicit page labels such as pg. 174. Image request and service URLs
can also be matched by a numeric image-identifier basename; URL decoding applies
only to the join key and never changes the stored external URLs.
Page numbers repeated in different volumes are ambiguous: use a compound key
and an exact join instead. missing must explicitly be error or
allow-unlinked. Invalid or ambiguous links always fail.
Generation
Pass actual generated Historical Record UUIDs, not independent UUID guesses:
from timeatlas import (
UUIDManager, normalize_source_inventory, link_source_records,
build_source_collection, write_source_collection, write_source_inventory,
)
inventory = normalize_source_inventory("images.csv", config)
# HRs have already been produced using the source record IDs.
hr_ids_by_record_key = {row["record_id"]: hr.id for row, hr in produced_records}
links, report = link_source_records(
inventory, record_rows, config, hr_ids_by_record_key,
)
uuid_manager = UUIDManager("https://example.org/my-dataset")
collection, counts = build_source_collection(
inventory, uuid_manager=uuid_manager,
collection_key="my-dataset", collection_label="My source collection",
links=links,
)
write_source_collection(collection, uuid_manager=uuid_manager, output_dir=dataset_dir)
write_source_inventory(inventory, "/job/source-inventory.csv")
dataset.sources = [collection.id]
Catch SourceInventoryError and save its report when joins fail. Store
the inventory and link report outside import output. IIIF files are written
under the dataset’s iiif/collections and iiif/manifests directories,
named using their UUIDs.
The normalized schema contains manifest_key, manifest_label, canvas_key, canvas_label, canvas_position, image_url, image_service_url, image_service_type, image_service_profile, width, height, media_type, image_join_key and source_row. Manifest and canvas keys produce deterministic IDs independently of CSV order. Multiple HR links create annotations without duplicating canvases. Blank pages without annotations are retained.
A JSON array in a record’s image-reference field links that record to multiple canvases. CSV cell strings are not implicitly split. Different source record keys may map to the same HR UUID when the production model deliberately merges records, but each source record key itself must remain unique.
Downloaded manifest input
The application downloads configured manifestUrls on the host, saves source
snapshots and checksums, then passes local JSON paths or already loaded manifest
objects to the offline parser:
from timeatlas.SourceInventory import normalize_source_manifests
config = {
"enabled": True,
"inputType": "manifest-url",
"manifestUrls": ["https://example.org/iiif/book/manifest"],
"recordLink": {
"recordKeyField": "record_id", "recordImageField": "page_number",
"imageJoinField": "canvas_label", "normalization": "page-number",
"missing": "error",
},
}
inventory = normalize_source_manifests(
["/job/source-manifests/manifest-001.json"], config,
)
Use the same link_source_records and build_source_collection calls as
for CSV input. Manifest grouping, image-service metadata and canvas order come
from each supplied manifest; inventory-mode settings do not override them.
Join fields are canvas_id, canvas_label, image_url and
image_service_url. A missing source canvas label receives a display fallback
but never a fabricated join key.
The normalized rows additionally contain original manifest_id/canvas_id,
manifest_position, complete manifest_labels/canvas_labels language
maps, and optional source_manifest_metadata rights/requiredStatement fields.
The builder preserves these label maps and rights statements. Existing source
Historical Record annotations are excluded; only explicitly joined current
dataset HRs are annotated. New UUIDs depend on stable original manifest/canvas
identifiers and the destination dataset namespace.
Supported input is Presentation 3 embedded canvases/AnnotationPages or
Presentation 2 with one embedded sequence, one complete image painting per
canvas, matching image/canvas dimensions, required image width/height/MIME
metadata and one advertised ImageService2/3. Version 2 string/language labels
and attribution are translated to Presentation 3 language maps and
requiredStatement. Service profiles are normalized to their advertised
compliance level; the downloaded source snapshot retains any extra service
capability details. For a complete Image API request, a recognized output format
in the painting URL takes precedence over the declared image MIME type: a TIFF
service identifier requesting default.jpg produces image/jpeg. Other
URLs retain their declared MIME type. Source snapshots are unchanged.
For matching image-request/service pairs with a file-style identifier below
/iiif/2/ or /iiif/3/, duplicate identifier separators (literal /,
encoded %2F or a mixture) collapse to one encoded separator. For example,
venice%2F%2Fcatastici%2F001.tif becomes
venice%2Fcatastici%2F001.tif consistently in the image and service URL.
This compatibility rule treats those identifiers as filesystem-style paths;
opaque identifiers and arbitrary URL prefixes are never rewritten. Valid single
separators, other escapes, host/scheme spelling, queries and fragments remain
unchanged. Original canvas objects remain available in the source snapshots for
authored record joins; normalized inventory URL fields contain the canonical
values. This rule does not alter the CSV-inventory or image-URL-list adapters.
Optional structures (navigation ranges/tables of contents) are accepted but
not copied. They can repeat, omit or reorder canvas references independently of
the page sequence. All pages follow the manifest’s top-level items order
(or its single Presentation 2 sequence), once each, including pages absent from
the ranges. Each supplied manifest stays a separate volume; the collection
follows the supplied manifest order. Rebuilding the original navigation hierarchy
is outside this adapter’s scope.
Unsupported input fails explicitly: Collections, multiple sequences, referenced AnnotationPages, Choice/multiple painting bodies, region or composite layouts, differing image/canvas dimensions and missing required metadata. No remote image enrichment or source annotation copying occurs in this adapter.
Image URL lists and information snapshots
Image URL input requires only a UTF-8 text file containing one complete IIIF image request URL per nonempty line. The planner performs no network access and preserves the original URL spelling, escaping and order:
from timeatlas.SourceInventory import (
plan_source_image_urls, normalize_source_image_urls,
)
plan = plan_source_image_urls("images.txt")
# Host code downloads each unique plan row's info_url. Each snapshot is:
snapshots = [{
"serviceUrl": plan[0]["image_service_url"],
"infoUrl": plan[0]["info_url"],
"finalUrl": plan[0]["info_url"], # actual final URL after redirects
"path": "/job/source-image-info/info-0001.json",
"sha256": "...SHA-256 of the downloaded file bytes...",
}]
config = {
"enabled": True, "inputType": "image-urls",
"grouping": {"mode": "single"},
"ordering": {"mode": "natural", "field": "image_filename"},
"recordLink": {
"recordKeyField": "record_id", "recordImageField": "page_number",
"imageJoinField": "image_filename", "normalization": "page-number",
"missing": "error",
},
}
inventory = normalize_source_image_urls("images.txt", snapshots, config)
The snapshot list contains one entry per unique requested info URL. Each plan
row contains image_url, image_service_url, info_url, source_position and
request_format. An already loaded info object can replace path/sha256 for
offline use. The normalizer verifies snapshot checksums when supplied and
requires the declared service identity to match the requested service or the
final downloaded metadata URL. Percent-encoding equivalence is accepted during
identity comparison; duplicate slashes and unrelated identifiers are not fixed.
The original image URL remains unchanged in generated painting bodies.
Dimensions and ImageService2/3 compliance levels come from the metadata; MIME
type comes from the actual requested format, not the source file extension or a
list of available formats. Requested formats must be supported by the advertised
compliance level or additional format declarations. Requests are limited to full
region, default quality, rotation 0 and Image API 2 full/max or Image API 3 max
size. If maxWidth/maxHeight/maxArea reduce the available full size, normalization
fails instead of guessing scaled dimensions. Metadata and image pixel retrieval
are separate: the helpers never download or copy image pixels.
Grouping, ordering, labels and HR joins use these derived columns:
image_url: exact supplied request URL, also the stable canvas key.image_service_url: validated service ID advertised by info.json.image_identifier: decoded last service-URI component.image_filename: basename of the decoded service path.image_directory: parent of the decoded service path, including its prefix.source_position: original 1-based input position.
Natural ordering defaults to image_filename; numeric ordering defaults to
source_position. columns.label optionally names one derived field; otherwise
labels use page numbers. The default HR join field is image_filename. Existing
inventory-mode image column mappings and manual service settings are ignored.
Text input is limited to 10,000 unique URLs and 2 MiB. Duplicate URLs, missing
snapshots, unsupported requests and unavailable metadata produce explicit errors.
External URL handling
Page supports explicit image_url, image_service_url,
image_service_type, image_service_profile and optional thumbnail_url.
Explicit URLs are preserved without URL-decoding or re-encoding. Without an
explicit thumbnail, the original image request serves as the thumbnail; the
library does not invent a resize request that a remote server may not support.
Existing object_ref paths and a shared url_prefix keep their behavior.
Image API 2 services serialize using @id/@type and a version 2 profile
URI. Image API 3 uses id/type and its short profile name, as described in
the IIIF Presentation API service specification.
API
Normalize external IIIF image inventories and join them to Historical Records.
The configuration is the ingest assistant’s ModelingSourceConfig (camelCase).
This module is deliberately offline: it neither downloads nor publishes images.
- exception timeatlas.SourceInventory.SourceInventoryError[source]
Invalid source metadata or links;
reportcontains actionable details.
- timeatlas.SourceInventory.read_source_rows(rows)[source]
Read UTF-8 CSV, a JSON array (or
{"rows": [...]}), or row mappings.
- timeatlas.SourceInventory.image_service_from_request(image_url)[source]
Remove a full IIIF request’s region/size/rotation/quality.format suffix.
Encoded identifiers and query strings are retained. This function requires an Image API request URL; a bare JPEG URL cannot identify an IIIF service.
- timeatlas.SourceInventory.normalize_source_inventory(rows, config)[source]
Validate mapped CSV/JSON data and return one canonical row per canvas.
Required column mappings: imageKey, imageUrl, width, height, mediaType. Grouping is single, field, or per-image. Ordering is inventory, natural, or numeric. Image API version/profile must be explicitly configured. IDs are derived later from manifest_key/canvas_key, never row positions.
- timeatlas.SourceInventory.link_source_records(inventory, records, config, hr_ids_by_record_key)[source]
Join source records to actual generated HR UUIDs, without guessing links.
hr_ids_by_record_keymaps stable source record keys to the exact UUIDs used by the production script. Values may be one UUID or a list of UUIDs. A JSON array in the record image field links one record to several canvases; CSV strings are never split implicitly. Missing source references are fatal unless missing=’allow-unlinked’. Unannotated source images are allowed: blank book pages are legitimate.
- timeatlas.SourceInventory.build_source_collection(inventory, *, uuid_manager, collection_key, collection_label, links=(), allow_unlinked=False)[source]
Build stable Collection/Document/Page/Annotation objects from normalized rows.
- Parameters:
- Return type:
- timeatlas.SourceInventory.write_source_collection(collection, *, uuid_manager, output_dir)[source]
Write IIIF under
output_dir/iiif/{collections,manifests}/<uuid>.json.output_diris the dataset directory, e.g. /output/rde/datasets/<slug>. This does not create metadata sidecars inside the import output directory.- Parameters:
collection (Collection)
uuid_manager (UUIDManager)
- Return type:
- timeatlas.SourceInventory.write_source_inventory(inventory, path)[source]
Save the canonical inventory CSV, normally under /job for review.
- timeatlas.SourceInventory.normalize_source_manifests(manifests, config)[source]
Normalize local Presentation 2/3 manifests; download URLs on the host first.
See
timeatlas.SourceManifestsfor the supported manifest profile.