aind-ibl-ephys-alignment-preprocessing 0.5.2__tar.gz → 0.7.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. aind_ibl_ephys_alignment_preprocessing-0.7.0/AGENTS.md +7 -0
  2. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/CHANGELOG.md +28 -0
  3. aind_ibl_ephys_alignment_preprocessing-0.7.0/CLAUDE.md +3 -0
  4. aind_ibl_ephys_alignment_preprocessing-0.5.2/CLAUDE.md → aind_ibl_ephys_alignment_preprocessing-0.7.0/CONTRIBUTING.md +10 -3
  5. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/PKG-INFO +192 -32
  6. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/README.md +190 -31
  7. aind_ibl_ephys_alignment_preprocessing-0.7.0/docs/external_asset_references.md +201 -0
  8. aind_ibl_ephys_alignment_preprocessing-0.7.0/docs/source/data_contracts.md +10 -0
  9. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/docs/source/index.md +1 -0
  10. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/pyproject.toml +6 -1
  11. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/src/aind_ibl_ephys_alignment_preprocessing/_async/histology.py +3 -2
  12. aind_ibl_ephys_alignment_preprocessing-0.7.0/src/aind_ibl_ephys_alignment_preprocessing/_constants.py +34 -0
  13. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/src/aind_ibl_ephys_alignment_preprocessing/datapackage.py +40 -11
  14. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/src/aind_ibl_ephys_alignment_preprocessing/discovery.py +83 -8
  15. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/src/aind_ibl_ephys_alignment_preprocessing/histology.py +3 -2
  16. aind_ibl_ephys_alignment_preprocessing-0.7.0/src/aind_ibl_ephys_alignment_preprocessing/manifest_builder.py +298 -0
  17. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/src/aind_ibl_ephys_alignment_preprocessing/registration_frame.py +113 -5
  18. aind_ibl_ephys_alignment_preprocessing-0.7.0/src/aind_ibl_ephys_alignment_preprocessing/scripts/build_manifest.py +42 -0
  19. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/src/aind_ibl_ephys_alignment_preprocessing/stages.py +13 -2
  20. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/src/aind_ibl_ephys_alignment_preprocessing/types.py +81 -12
  21. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/src/aind_ibl_ephys_alignment_preprocessing/validation.py +38 -0
  22. aind_ibl_ephys_alignment_preprocessing-0.7.0/tests/sidecar_fixtures.py +43 -0
  23. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/tests/test_datapackage.py +2 -1
  24. aind_ibl_ephys_alignment_preprocessing-0.7.0/tests/test_manifest_builder.py +160 -0
  25. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/tests/test_registration_override.py +12 -21
  26. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/tests/test_stages.py +23 -0
  27. aind_ibl_ephys_alignment_preprocessing-0.7.0/tests/test_template_to_ccf_override.py +254 -0
  28. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/uv.lock +4 -2
  29. aind_ibl_ephys_alignment_preprocessing-0.5.2/docs/external_asset_references.md +0 -417
  30. aind_ibl_ephys_alignment_preprocessing-0.5.2/src/aind_ibl_ephys_alignment_preprocessing/_constants.py +0 -18
  31. aind_ibl_ephys_alignment_preprocessing-0.5.2/src/aind_ibl_ephys_alignment_preprocessing/co_asset_resolver.py +0 -596
  32. aind_ibl_ephys_alignment_preprocessing-0.5.2/tests/test_co_asset_resolver.py +0 -454
  33. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/.copier-answers.yml +0 -0
  34. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  35. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  36. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/.github/ISSUE_TEMPLATE/user-story.md +0 -0
  37. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/.github/dependabot.yml +0 -0
  38. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/.github/workflows/bump-call.yml +0 -0
  39. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/.github/workflows/ci-call.yml +0 -0
  40. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/.github/workflows/docs-ci.yml +0 -0
  41. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/.github/workflows/publish-call.yml +0 -0
  42. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/.gitignore +0 -0
  43. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/.python-version +0 -0
  44. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/.readthedocs.yaml +0 -0
  45. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/LICENSE +0 -0
  46. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/docs/source/_static/dark-logo.svg +0 -0
  47. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/docs/source/_static/favicon.ico +0 -0
  48. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/docs/source/_static/light-logo.svg +0 -0
  49. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/docs/source/api.rst +0 -0
  50. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/docs/source/conf.py +0 -0
  51. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/scripts/run_linters_and_checks.sh +0 -0
  52. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/src/aind_ibl_ephys_alignment_preprocessing/__init__.py +0 -0
  53. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/src/aind_ibl_ephys_alignment_preprocessing/_async/__init__.py +0 -0
  54. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/src/aind_ibl_ephys_alignment_preprocessing/_async/concurrency.py +0 -0
  55. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/src/aind_ibl_ephys_alignment_preprocessing/_async/ephys.py +0 -0
  56. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/src/aind_ibl_ephys_alignment_preprocessing/_async/pipeline.py +0 -0
  57. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/src/aind_ibl_ephys_alignment_preprocessing/_async/probes.py +0 -0
  58. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/src/aind_ibl_ephys_alignment_preprocessing/_timing.py +0 -0
  59. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/src/aind_ibl_ephys_alignment_preprocessing/coverage.py +0 -0
  60. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/src/aind_ibl_ephys_alignment_preprocessing/datapackage_schema.py +0 -0
  61. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/src/aind_ibl_ephys_alignment_preprocessing/ephys.py +0 -0
  62. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/src/aind_ibl_ephys_alignment_preprocessing/pipeline.py +0 -0
  63. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/src/aind_ibl_ephys_alignment_preprocessing/probes.py +0 -0
  64. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/src/aind_ibl_ephys_alignment_preprocessing/py.typed +0 -0
  65. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/src/aind_ibl_ephys_alignment_preprocessing/schemas/aind-ibl-ephys-alignment-datapackage/3.1.0/datapackage.schema.json +0 -0
  66. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/src/aind_ibl_ephys_alignment_preprocessing/schemas/aind-ibl-ephys-alignment-datapackage/3.2.0/datapackage.schema.json +0 -0
  67. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/src/aind_ibl_ephys_alignment_preprocessing/schemas/aind-ibl-ephys-alignment-datapackage/4.0.0/datapackage.schema.json +0 -0
  68. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/src/aind_ibl_ephys_alignment_preprocessing/schemas/aind-ibl-ephys-alignment-datapackage/4.1.0/datapackage.schema.json +0 -0
  69. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/src/aind_ibl_ephys_alignment_preprocessing/scripts/__init__.py +0 -0
  70. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/src/aind_ibl_ephys_alignment_preprocessing/scripts/export_datapackage_schema.py +0 -0
  71. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/src/aind_ibl_ephys_alignment_preprocessing/scripts/run.py +0 -0
  72. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/tests/__init__.py +0 -0
  73. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/tests/test_ants_domain.py +0 -0
  74. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/tests/test_coverage.py +0 -0
  75. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/tests/test_datapackage_schema.py +0 -0
  76. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/tests/test_example.py +0 -0
  77. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/tests/test_geometry_sidecar.py +0 -0
  78. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/tests/test_registration_frame.py +0 -0
  79. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/tests/test_resample.py +0 -0
  80. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/tests/test_sorting_detection.py +0 -0
  81. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/tests/test_sync_async_parity.py +0 -0
  82. {aind_ibl_ephys_alignment_preprocessing-0.5.2 → aind_ibl_ephys_alignment_preprocessing-0.7.0}/tests/test_timing.py +0 -0
@@ -0,0 +1,7 @@
1
+ # Agent Instructions
2
+
3
+ Read [CONTRIBUTING.md](CONTRIBUTING.md) before making changes. It is the
4
+ shared guide for development commands, architecture, and validation.
5
+
6
+ Keep shared development guidance in CONTRIBUTING.md and agent-specific
7
+ instructions here. Keep CLAUDE.md as a pointer to this file.
@@ -5,6 +5,34 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## v0.7.0 (2026-10-23)
9
+
10
+ ### BREAKING CHANGE
11
+
12
+ - registration_asset directories must carry
13
+ ls_to_template_transform_information.json or transform_information.json, and
14
+ template_to_ccf_asset requires a registration_asset.
15
+
16
+ ### Feat
17
+
18
+ - write template_to_ccf_asset from the manifest mapping
19
+
20
+ ### Fix
21
+
22
+ - refuse override transforms whose frame or template is unverified
23
+ - keep per-brain override columns on the rows discover keeps
24
+
25
+ ## v0.6.0 (2026-10-22)
26
+
27
+ ### Feat
28
+
29
+ - add per-brain template_to_ccf_asset manifest column
30
+ - add manifest builder
31
+
32
+ ### Fix
33
+
34
+ - match override transforms through the S3 cache prefix
35
+
8
36
  ## v0.5.2 (2026-09-30)
9
37
 
10
38
  ### Fix
@@ -0,0 +1,3 @@
1
+ # Claude Instructions
2
+
3
+ Read and follow [AGENTS.md](AGENTS.md).
@@ -1,6 +1,7 @@
1
- # CLAUDE.md
1
+ # Contributing
2
2
 
3
- This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
3
+ This is the main development guide for human contributors and coding agents.
4
+ Agent-specific instructions are in [AGENTS.md](AGENTS.md).
4
5
 
5
6
  ## Commands
6
7
 
@@ -42,6 +43,12 @@ Always use `uv run` to execute commands, `uv add` to add dependencies, and `uv s
42
43
 
43
44
  ## Architecture
44
45
 
46
+ Before changing datapackage fields, asset references, output layout, or
47
+ channel/shank joins, read
48
+ [the datapackage and external asset contract](docs/external_asset_references.md).
49
+ Keep machine-specific locations in resolver configuration and preserve the
50
+ convention-loaded ephys collection contents as well as explicit references.
51
+
45
52
  This is a Python package using a `src/` layout. Source code lives in `src/aind_ibl_ephys_alignment_preprocessing/`, tests in `tests/`.
46
53
 
47
54
  - Build system: hatchling
@@ -72,7 +79,7 @@ src/aind_ibl_ephys_alignment_preprocessing/
72
79
  probes.py # Per-probe annotation loading and coordinate conversion
73
80
  ephys.py # Sync ephys extraction wrapper (delegates to aind-ephys-ibl-gui-conversion)
74
81
  pipeline.py # Sync orchestrator: run_pipeline()
75
- manifest.py # Output datapackage.json generation (DataPackage model, schema v1.0.0)
82
+ datapackage.py # Output datapackage.json generation, asset references, schema 4.1.0
76
83
  scripts/run.py # CLI entry point: aind-ibl-preprocess
77
84
  _async/
78
85
  concurrency.py # IO lane throttling (per-path semaphores), EphysCoordinator, thread helpers
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: aind-ibl-ephys-alignment-preprocessing
3
- Version: 0.5.2
3
+ Version: 0.7.0
4
4
  Summary: Library to prepare histology and ephys for the IBL ephys alignment GUI
5
5
  Project-URL: Repository, https://github.com/AllenNeuralDynamics/aind-ibl-ephys-alignment-preprocessing/
6
6
  Project-URL: Issues, https://github.com/AllenNeuralDynamics/aind-ibl-ephys-alignment-preprocessing/issues
@@ -19,6 +19,7 @@ Requires-Dist: aind-registration-utils>=0.4
19
19
  Requires-Dist: aind-s3-cache>=0.1
20
20
  Requires-Dist: aind-zarr-utils>=0.11.2
21
21
  Requires-Dist: antspyx>=0.3.0
22
+ Requires-Dist: boto3
22
23
  Requires-Dist: filelock
23
24
  Requires-Dist: iblatlas>=0.4.0
24
25
  Requires-Dist: numpy>=1.21.0
@@ -256,7 +257,8 @@ before a column existed stays valid.
256
257
  | `probe_name` | *legacy* | Alias for `ephys_collection`. |
257
258
  | `probe_shank` | *legacy* | Alias for both `histology_shank` and `ephys_shank`. |
258
259
  | `surface_finding` | *null* | Path (relative to `data_root`) to a surface-finding file. |
259
- | `registration_asset` | *null* | Path (relative to `data_root`) to a registration directory holding the `ls_to_template_SyN_*` transforms to use **instead of** the stitched asset's own -- e.g. `SmartSPIM_750108_reg/ccf_Ex_639_Em_667`. Per-*brain*: replicated across rows like `mouseid`, and rows must agree. The registration channel is taken from the directory name (a leading `ccf_` is stripped), so the histology volume is built from the channel the transforms were computed from rather than the one `processing.json` names. |
260
+ | `registration_asset` | *null* | Path relative to `data_root` to a replacement registration directory, e.g. `SmartSPIM_750108_reg/ccf_Ex_639_Em_667`. One value per brain; all nonblank rows must agree. See [Separate registration asset](#separate-registration-asset) for its contents and coordinate requirements. |
261
+ | `template_to_ccf_asset` | *null* | Path relative to `data_root` to the template-to-CCF registration of the template `registration_asset` was registered to, when that is not the SmartSPIM LCA template. One value per brain, like `registration_asset`. See [Separate template-to-CCF registration](#separate-template-to-ccf-registration). |
260
262
 
261
263
  **Constraints:**
262
264
  - All rows must have the same `mouseid`.
@@ -281,6 +283,52 @@ mouse001,2024-06-01_rec_sorted,track_annotations_probeB_shank1,B0001,probeB,Prob
281
283
  mouse001,2024-06-01_rec_sorted,track_annotations_quad_shank3,Q0001,quad0,ProbeD,3,0
282
284
  ```
283
285
 
286
+ #### Building a manifest from a mapping
287
+
288
+ Most of a manifest repeats or follows from two choices: which sorted recording each session uses,
289
+ and which stream each annotated track was recorded on. `aind-ibl-build-manifest` takes only those,
290
+ in a TOML file, and derives the rest. Track names are never parsed, because annotators follow no
291
+ naming convention.
292
+
293
+ ```toml
294
+ mouseid = "837772"
295
+ annotation = "837772-probe-annotations-CK-2026-09-29.json" # relative to this file
296
+ # registration_asset = "..." # optional, per brain
297
+ # template_to_ccf_asset = "..." # optional, per brain
298
+
299
+ [[session]]
300
+ sorted_recording = "ecephys_837772_2026-06-27_14-08-21_sorted_2026-08-27_18-59-49"
301
+ # surface_finding = "..." # optional
302
+
303
+ [session.tracks]
304
+ "732_MD_1" = "46101-1" # one shank of a split quad-base probe
305
+ "733_BLA" = "46810" # a single-stream probe
306
+ "740_PL" = { stream = "45883", shank = 1 } # one shank of a single-stream multi-shank probe; the first shank is 1
307
+ ```
308
+
309
+ ```bash
310
+ aind-ibl-build-manifest 837772.toml -o manifest_837772.csv [--data-root /data]
311
+ ```
312
+
313
+ A stream is the collection parsed from the sort's `postprocessed/` analyzer names. They are listed
314
+ from a copy mounted under `--data-root`, or else unsigned from that sort's prefix in
315
+ `aind-open-data`. How the columns are derived:
316
+
317
+ - `probe_file` is the annotation file's name without its extension.
318
+ - **Split quad-base probe:** a stream `<probe>-<N>`, when the sort also holds another
319
+ `<probe>-<M>`, gets `logical_probe = <probe>`, `histology_shank = N - 1` and `ephys_shank = 0`.
320
+ - **Any other stream** is its own `logical_probe`. Its shanks stay blank, or both take the given
321
+ shank's index.
322
+
323
+ The build writes nothing if any of these fail, and it reports every failure:
324
+ - every stream in each sort has a track, and every assigned stream is in its sort;
325
+ - every track is a layer of the annotation file with at least two points;
326
+ - no track is assigned twice;
327
+ - a split stream has exactly one track, and the tracks sharing any other stream have distinct
328
+ shanks.
329
+
330
+ Annotated layers left out of the mapping are printed as notes, so a missed track gets noticed.
331
+
284
332
  ### Neuroglancer JSON
285
333
 
286
334
  A Neuroglancer state JSON file that contains image source URIs pointing to the
@@ -291,7 +339,136 @@ and any additional channels.
291
339
  The asset directory must contain:
292
340
  - `image_tile_fusing/OMEZarr/<channel>.zarr/` -- fused OME-Zarr volumes
293
341
  - `image_atlas_alignment/<registration_channel_stem>/` -- ANTs registration
294
- outputs: the light-sheet to template affine and inverse warp
342
+ outputs: the light-sheet to template affine and inverse warp (or supply a
343
+ separate registration asset as described below)
344
+
345
+ ### Separate registration asset
346
+
347
+ Set the manifest's optional `registration_asset` column to the transform
348
+ directory relative to `data_root`:
349
+
350
+ ```csv
351
+ mouseid,sorted_recording,probe_file,histology_track_id,ephys_collection,registration_asset
352
+ 750108,recording_sorted,probeA,A0001,ProbeA,SmartSPIM_750108_reg/ccf_Ex_639_Em_667
353
+ ```
354
+
355
+ Use one path per brain; conflicting nonblank values fail. An absent or blank
356
+ column uses the stitched asset's registration.
357
+
358
+ The directory must contain `ls_to_template_SyN_0GenericAffine.mat`,
359
+ `ls_to_template_SyN_1InverseWarp.nii.gz` and a geometry sidecar (below): ANTs
360
+ SyN outputs registering this brain's selected channel to the SmartSPIM LCA
361
+ template, or to another template whose CCF registration
362
+ `template_to_ccf_asset` (below) supplies.
363
+
364
+ The final directory name selects the channel after stripping a leading
365
+ `ccf_`. For this example, the stitched asset must contain
366
+ `image_tile_fusing/OMEZarr/Ex_639_Em_667.zarr/`. Images and metadata come
367
+ from the stitched asset; the override needs only transforms and their sidecar.
368
+
369
+ Every separate registration needs a geometry sidecar beside the transforms;
370
+ a directory without one fails. Preprocessing checks
371
+ `ls_to_template_transform_information.json`, then `transform_information.json`.
372
+ Its `moving_domain` must describe the actual registration input in LPS
373
+ millimeters and match the selected volume's native or pipeline-anchored frame.
374
+ Its `fixed_domain` must match the template the template-to-CCF chain starts
375
+ from: the SmartSPIM LCA template unless `template_to_ccf_asset` is set. Missing
376
+ or incompatible domains fail, since a wrong frame or template still puts the
377
+ probes somewhere plausible.
378
+
379
+ **QC:** Additional-channel CCF volumes (`emit_qc=True`) also need
380
+ `ls_to_template_SyN_1Warp.nii.gz` in the override directory; without it the
381
+ QC warp fails rather than falling back to the stitched asset's registration.
382
+
383
+ #### Generate the sidecar from ANTs outputs
384
+
385
+ Use the actual fixed template and moving histology images passed to ANTs,
386
+ including any preprocessing of their geometry. Transform files alone cannot
387
+ recover these domains. Headers must be 3D, axis-aligned, and in millimeters.
388
+
389
+ Run `uv sync` in the preprocessing library checkout, then save this as
390
+ `generate_registration_sidecar.py`:
391
+
392
+ ```python
393
+ import sys
394
+ from pathlib import Path
395
+
396
+ import SimpleITK as sitk
397
+ from aind_ants_transform_sidecar import dump_package, load_package
398
+ from aind_registration_utils.domains import ImageHeader, make_syn_sidecar
399
+
400
+
401
+ def read_header(path):
402
+ reader = sitk.ImageFileReader()
403
+ reader.SetFileName(str(path))
404
+ reader.ReadImageInformation() # Read geometry without loading voxel data.
405
+ if reader.GetDimension() != 3:
406
+ raise ValueError("Registration inputs must be 3D")
407
+ return ImageHeader(
408
+ origin=reader.GetOrigin(),
409
+ spacing=reader.GetSpacing(),
410
+ direction=reader.GetDirection(),
411
+ size=reader.GetSize(),
412
+ )
413
+
414
+
415
+ registration_dir, fixed_image, moving_image = map(Path, sys.argv[1:])
416
+ sidecar = make_syn_sidecar(
417
+ fixed_image_header=read_header(fixed_image),
418
+ moving_image_header=read_header(moving_image),
419
+ affine="ls_to_template_SyN_0GenericAffine.mat",
420
+ warp="ls_to_template_SyN_1Warp.nii.gz",
421
+ inverse_warp="ls_to_template_SyN_1InverseWarp.nii.gz",
422
+ )
423
+ text = dump_package(sidecar)
424
+ load_package(text) # Validate the serialized package.
425
+ destination = registration_dir / "ls_to_template_transform_information.json"
426
+ destination.write_text(text + "\n")
427
+ print(destination)
428
+ ```
429
+
430
+ Pass the transform directory, fixed image, and moving image in that order:
431
+
432
+ ```bash
433
+ uv run python generate_registration_sidecar.py \
434
+ /path/to/SmartSPIM_750108_reg/ccf_Ex_639_Em_667 \
435
+ /path/to/fixed_template_used_by_ants.nii.gz \
436
+ /path/to/moving_histology_used_by_ants.nii.gz
437
+ ```
438
+
439
+ The script reads only image headers. Transform filenames are relative to the
440
+ sidecar; rename raw ANTs outputs to the expected names. The schema requires a
441
+ forward-warp filename, but normal preprocessing needs only the affine and
442
+ inverse warp. The helper does not verify transform files.
443
+
444
+ Generate the JSON beside the transforms before uploading the asset;
445
+ Code Ocean's `/data` mounts are read-only.
446
+
447
+ ### Separate template-to-CCF registration
448
+
449
+ A brain registered to a template other than the SmartSPIM LCA template (an
450
+ iDISCO template, say) needs that template's CCF registration. Set the optional
451
+ `template_to_ccf_asset` column to its directory relative to `data_root`, one
452
+ path per brain like `registration_asset`:
453
+
454
+ ```csv
455
+ mouseid,sorted_recording,probe_file,histology_track_id,ephys_collection,registration_asset,template_to_ccf_asset
456
+ 750107,recording_sorted,probeA,A0001,ProbeA,idisco_reg/750107,idisco_template_2026-10/template_to_ccf
457
+ ```
458
+
459
+ The directory must contain `syn_0GenericAffine.mat` and
460
+ `syn_1InverseWarp.nii.gz`, ANTs SyN outputs with CCF fixed and the template
461
+ moving; `emit_qc=True` also needs `syn_1Warp.nii.gz`. They replace the
462
+ pipeline's `spim_template_to_ccf_syn_*_25` transforms in the probe and QC
463
+ chains, and the datapackage refers to them under the `template_to_ccf` asset,
464
+ named after the directory's first component under `data_root`.
465
+
466
+ The column requires a `registration_asset` with a sidecar, whose `fixed_domain`
467
+ is checked against this template. If the directory ships its own sidecar
468
+ (`template_to_ccf_transform_information.json` or `transform_information.json`),
469
+ the registration's `fixed_domain` must match its `moving_domain`. Without one,
470
+ the registration's `fixed_domain` must *not* match the SmartSPIM LCA template;
471
+ to replace the LCA template's own CCF registration, ship the sidecar.
295
472
 
296
473
  ### Reference volumes
297
474
 
@@ -368,9 +545,13 @@ results_root/
368
545
  +-- band_corr/ # Full matrices + row_channels.json
369
546
  ```
370
547
 
371
- The `datapackage.json` file contains a structured manifest of all outputs,
372
- including transform chain paths, histology volume paths, and per-probe
373
- metadata. It can be loaded back with:
548
+ The `datapackage.json` file describes the mouse, recordings, ephys collections,
549
+ transform chain, histology, channel tables, and track mappings. It references
550
+ each ephys directory; individual spike/QC files and `band_corr/` contents are
551
+ loaded by convention rather than enumerated in the JSON. See
552
+ [the datapackage contract](docs/external_asset_references.md) for asset
553
+ descriptions versus runtime location resolution, Code Ocean/desktop examples,
554
+ shank joins, and validation limits. The model can be loaded back with:
374
555
 
375
556
  ```python
376
557
  from aind_ibl_ephys_alignment_preprocessing.datapackage import load_datapackage
@@ -395,7 +576,9 @@ The pipeline performs the following steps:
395
576
  `xyz_picks.json` files for the alignment GUI.
396
577
  5. **Ephys extraction** (optional) -- Convert spike-sorted data into IBL ALF
397
578
  format.
398
- 6. **Manifest generation** -- Write `datapackage.json` summarizing all outputs.
579
+ 6. **Manifest generation** -- Write `datapackage.json` describing explicit
580
+ references and ephys collection directories; preserve their conventional
581
+ contents, including QC files not individually listed in the JSON.
399
582
 
400
583
  ## Adapting to your own data
401
584
 
@@ -423,31 +606,8 @@ origin at bregma, in meters.
423
606
 
424
607
  ## Development
425
608
 
426
- To develop the code, run:
427
- ```bash
428
- uv sync
429
- ```
430
-
431
- Please test your changes using the full linting and testing suite:
432
-
433
- ```bash
434
- ./scripts/run_linters_and_checks.sh -c
435
- ```
436
-
437
- Or run individual commands:
438
- ```bash
439
- uv run --frozen ruff format # Code formatting
440
- uv run --frozen ruff check # Linting
441
- uv run --frozen mypy # Type checking
442
- uv run --frozen interrogate -v src # Documentation coverage
443
- uv run --frozen codespell --check-filenames # Spell checking
444
- uv run --frozen pytest --cov aind_ibl_ephys_alignment_preprocessing # Tests with coverage
445
- ```
446
-
447
- ### Documentation
448
- ```bash
449
- sphinx-build -b html docs/source/ docs/build/html
450
- ```
609
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for developer setup, checks, and
610
+ repository conventions. Coding agents should also read [AGENTS.md](AGENTS.md).
451
611
 
452
612
  ## License
453
613
 
@@ -227,7 +227,8 @@ before a column existed stays valid.
227
227
  | `probe_name` | *legacy* | Alias for `ephys_collection`. |
228
228
  | `probe_shank` | *legacy* | Alias for both `histology_shank` and `ephys_shank`. |
229
229
  | `surface_finding` | *null* | Path (relative to `data_root`) to a surface-finding file. |
230
- | `registration_asset` | *null* | Path (relative to `data_root`) to a registration directory holding the `ls_to_template_SyN_*` transforms to use **instead of** the stitched asset's own -- e.g. `SmartSPIM_750108_reg/ccf_Ex_639_Em_667`. Per-*brain*: replicated across rows like `mouseid`, and rows must agree. The registration channel is taken from the directory name (a leading `ccf_` is stripped), so the histology volume is built from the channel the transforms were computed from rather than the one `processing.json` names. |
230
+ | `registration_asset` | *null* | Path relative to `data_root` to a replacement registration directory, e.g. `SmartSPIM_750108_reg/ccf_Ex_639_Em_667`. One value per brain; all nonblank rows must agree. See [Separate registration asset](#separate-registration-asset) for its contents and coordinate requirements. |
231
+ | `template_to_ccf_asset` | *null* | Path relative to `data_root` to the template-to-CCF registration of the template `registration_asset` was registered to, when that is not the SmartSPIM LCA template. One value per brain, like `registration_asset`. See [Separate template-to-CCF registration](#separate-template-to-ccf-registration). |
231
232
 
232
233
  **Constraints:**
233
234
  - All rows must have the same `mouseid`.
@@ -252,6 +253,52 @@ mouse001,2024-06-01_rec_sorted,track_annotations_probeB_shank1,B0001,probeB,Prob
252
253
  mouse001,2024-06-01_rec_sorted,track_annotations_quad_shank3,Q0001,quad0,ProbeD,3,0
253
254
  ```
254
255
 
256
+ #### Building a manifest from a mapping
257
+
258
+ Most of a manifest repeats or follows from two choices: which sorted recording each session uses,
259
+ and which stream each annotated track was recorded on. `aind-ibl-build-manifest` takes only those,
260
+ in a TOML file, and derives the rest. Track names are never parsed, because annotators follow no
261
+ naming convention.
262
+
263
+ ```toml
264
+ mouseid = "837772"
265
+ annotation = "837772-probe-annotations-CK-2026-09-29.json" # relative to this file
266
+ # registration_asset = "..." # optional, per brain
267
+ # template_to_ccf_asset = "..." # optional, per brain
268
+
269
+ [[session]]
270
+ sorted_recording = "ecephys_837772_2026-06-27_14-08-21_sorted_2026-08-27_18-59-49"
271
+ # surface_finding = "..." # optional
272
+
273
+ [session.tracks]
274
+ "732_MD_1" = "46101-1" # one shank of a split quad-base probe
275
+ "733_BLA" = "46810" # a single-stream probe
276
+ "740_PL" = { stream = "45883", shank = 1 } # one shank of a single-stream multi-shank probe; the first shank is 1
277
+ ```
278
+
279
+ ```bash
280
+ aind-ibl-build-manifest 837772.toml -o manifest_837772.csv [--data-root /data]
281
+ ```
282
+
283
+ A stream is the collection parsed from the sort's `postprocessed/` analyzer names. They are listed
284
+ from a copy mounted under `--data-root`, or else unsigned from that sort's prefix in
285
+ `aind-open-data`. How the columns are derived:
286
+
287
+ - `probe_file` is the annotation file's name without its extension.
288
+ - **Split quad-base probe:** a stream `<probe>-<N>`, when the sort also holds another
289
+ `<probe>-<M>`, gets `logical_probe = <probe>`, `histology_shank = N - 1` and `ephys_shank = 0`.
290
+ - **Any other stream** is its own `logical_probe`. Its shanks stay blank, or both take the given
291
+ shank's index.
292
+
293
+ The build writes nothing if any of these fail, and it reports every failure:
294
+ - every stream in each sort has a track, and every assigned stream is in its sort;
295
+ - every track is a layer of the annotation file with at least two points;
296
+ - no track is assigned twice;
297
+ - a split stream has exactly one track, and the tracks sharing any other stream have distinct
298
+ shanks.
299
+
300
+ Annotated layers left out of the mapping are printed as notes, so a missed track gets noticed.
301
+
255
302
  ### Neuroglancer JSON
256
303
 
257
304
  A Neuroglancer state JSON file that contains image source URIs pointing to the
@@ -262,7 +309,136 @@ and any additional channels.
262
309
  The asset directory must contain:
263
310
  - `image_tile_fusing/OMEZarr/<channel>.zarr/` -- fused OME-Zarr volumes
264
311
  - `image_atlas_alignment/<registration_channel_stem>/` -- ANTs registration
265
- outputs: the light-sheet to template affine and inverse warp
312
+ outputs: the light-sheet to template affine and inverse warp (or supply a
313
+ separate registration asset as described below)
314
+
315
+ ### Separate registration asset
316
+
317
+ Set the manifest's optional `registration_asset` column to the transform
318
+ directory relative to `data_root`:
319
+
320
+ ```csv
321
+ mouseid,sorted_recording,probe_file,histology_track_id,ephys_collection,registration_asset
322
+ 750108,recording_sorted,probeA,A0001,ProbeA,SmartSPIM_750108_reg/ccf_Ex_639_Em_667
323
+ ```
324
+
325
+ Use one path per brain; conflicting nonblank values fail. An absent or blank
326
+ column uses the stitched asset's registration.
327
+
328
+ The directory must contain `ls_to_template_SyN_0GenericAffine.mat`,
329
+ `ls_to_template_SyN_1InverseWarp.nii.gz` and a geometry sidecar (below): ANTs
330
+ SyN outputs registering this brain's selected channel to the SmartSPIM LCA
331
+ template, or to another template whose CCF registration
332
+ `template_to_ccf_asset` (below) supplies.
333
+
334
+ The final directory name selects the channel after stripping a leading
335
+ `ccf_`. For this example, the stitched asset must contain
336
+ `image_tile_fusing/OMEZarr/Ex_639_Em_667.zarr/`. Images and metadata come
337
+ from the stitched asset; the override needs only transforms and their sidecar.
338
+
339
+ Every separate registration needs a geometry sidecar beside the transforms;
340
+ a directory without one fails. Preprocessing checks
341
+ `ls_to_template_transform_information.json`, then `transform_information.json`.
342
+ Its `moving_domain` must describe the actual registration input in LPS
343
+ millimeters and match the selected volume's native or pipeline-anchored frame.
344
+ Its `fixed_domain` must match the template the template-to-CCF chain starts
345
+ from: the SmartSPIM LCA template unless `template_to_ccf_asset` is set. Missing
346
+ or incompatible domains fail, since a wrong frame or template still puts the
347
+ probes somewhere plausible.
348
+
349
+ **QC:** Additional-channel CCF volumes (`emit_qc=True`) also need
350
+ `ls_to_template_SyN_1Warp.nii.gz` in the override directory; without it the
351
+ QC warp fails rather than falling back to the stitched asset's registration.
352
+
353
+ #### Generate the sidecar from ANTs outputs
354
+
355
+ Use the actual fixed template and moving histology images passed to ANTs,
356
+ including any preprocessing of their geometry. Transform files alone cannot
357
+ recover these domains. Headers must be 3D, axis-aligned, and in millimeters.
358
+
359
+ Run `uv sync` in the preprocessing library checkout, then save this as
360
+ `generate_registration_sidecar.py`:
361
+
362
+ ```python
363
+ import sys
364
+ from pathlib import Path
365
+
366
+ import SimpleITK as sitk
367
+ from aind_ants_transform_sidecar import dump_package, load_package
368
+ from aind_registration_utils.domains import ImageHeader, make_syn_sidecar
369
+
370
+
371
+ def read_header(path):
372
+ reader = sitk.ImageFileReader()
373
+ reader.SetFileName(str(path))
374
+ reader.ReadImageInformation() # Read geometry without loading voxel data.
375
+ if reader.GetDimension() != 3:
376
+ raise ValueError("Registration inputs must be 3D")
377
+ return ImageHeader(
378
+ origin=reader.GetOrigin(),
379
+ spacing=reader.GetSpacing(),
380
+ direction=reader.GetDirection(),
381
+ size=reader.GetSize(),
382
+ )
383
+
384
+
385
+ registration_dir, fixed_image, moving_image = map(Path, sys.argv[1:])
386
+ sidecar = make_syn_sidecar(
387
+ fixed_image_header=read_header(fixed_image),
388
+ moving_image_header=read_header(moving_image),
389
+ affine="ls_to_template_SyN_0GenericAffine.mat",
390
+ warp="ls_to_template_SyN_1Warp.nii.gz",
391
+ inverse_warp="ls_to_template_SyN_1InverseWarp.nii.gz",
392
+ )
393
+ text = dump_package(sidecar)
394
+ load_package(text) # Validate the serialized package.
395
+ destination = registration_dir / "ls_to_template_transform_information.json"
396
+ destination.write_text(text + "\n")
397
+ print(destination)
398
+ ```
399
+
400
+ Pass the transform directory, fixed image, and moving image in that order:
401
+
402
+ ```bash
403
+ uv run python generate_registration_sidecar.py \
404
+ /path/to/SmartSPIM_750108_reg/ccf_Ex_639_Em_667 \
405
+ /path/to/fixed_template_used_by_ants.nii.gz \
406
+ /path/to/moving_histology_used_by_ants.nii.gz
407
+ ```
408
+
409
+ The script reads only image headers. Transform filenames are relative to the
410
+ sidecar; rename raw ANTs outputs to the expected names. The schema requires a
411
+ forward-warp filename, but normal preprocessing needs only the affine and
412
+ inverse warp. The helper does not verify transform files.
413
+
414
+ Generate the JSON beside the transforms before uploading the asset;
415
+ Code Ocean's `/data` mounts are read-only.
416
+
417
+ ### Separate template-to-CCF registration
418
+
419
+ A brain registered to a template other than the SmartSPIM LCA template (an
420
+ iDISCO template, say) needs that template's CCF registration. Set the optional
421
+ `template_to_ccf_asset` column to its directory relative to `data_root`, one
422
+ path per brain like `registration_asset`:
423
+
424
+ ```csv
425
+ mouseid,sorted_recording,probe_file,histology_track_id,ephys_collection,registration_asset,template_to_ccf_asset
426
+ 750107,recording_sorted,probeA,A0001,ProbeA,idisco_reg/750107,idisco_template_2026-10/template_to_ccf
427
+ ```
428
+
429
+ The directory must contain `syn_0GenericAffine.mat` and
430
+ `syn_1InverseWarp.nii.gz`, ANTs SyN outputs with CCF fixed and the template
431
+ moving; `emit_qc=True` also needs `syn_1Warp.nii.gz`. They replace the
432
+ pipeline's `spim_template_to_ccf_syn_*_25` transforms in the probe and QC
433
+ chains, and the datapackage refers to them under the `template_to_ccf` asset,
434
+ named after the directory's first component under `data_root`.
435
+
436
+ The column requires a `registration_asset` with a sidecar, whose `fixed_domain`
437
+ is checked against this template. If the directory ships its own sidecar
438
+ (`template_to_ccf_transform_information.json` or `transform_information.json`),
439
+ the registration's `fixed_domain` must match its `moving_domain`. Without one,
440
+ the registration's `fixed_domain` must *not* match the SmartSPIM LCA template;
441
+ to replace the LCA template's own CCF registration, ship the sidecar.
266
442
 
267
443
  ### Reference volumes
268
444
 
@@ -339,9 +515,13 @@ results_root/
339
515
  +-- band_corr/ # Full matrices + row_channels.json
340
516
  ```
341
517
 
342
- The `datapackage.json` file contains a structured manifest of all outputs,
343
- including transform chain paths, histology volume paths, and per-probe
344
- metadata. It can be loaded back with:
518
+ The `datapackage.json` file describes the mouse, recordings, ephys collections,
519
+ transform chain, histology, channel tables, and track mappings. It references
520
+ each ephys directory; individual spike/QC files and `band_corr/` contents are
521
+ loaded by convention rather than enumerated in the JSON. See
522
+ [the datapackage contract](docs/external_asset_references.md) for asset
523
+ descriptions versus runtime location resolution, Code Ocean/desktop examples,
524
+ shank joins, and validation limits. The model can be loaded back with:
345
525
 
346
526
  ```python
347
527
  from aind_ibl_ephys_alignment_preprocessing.datapackage import load_datapackage
@@ -366,7 +546,9 @@ The pipeline performs the following steps:
366
546
  `xyz_picks.json` files for the alignment GUI.
367
547
  5. **Ephys extraction** (optional) -- Convert spike-sorted data into IBL ALF
368
548
  format.
369
- 6. **Manifest generation** -- Write `datapackage.json` summarizing all outputs.
549
+ 6. **Manifest generation** -- Write `datapackage.json` describing explicit
550
+ references and ephys collection directories; preserve their conventional
551
+ contents, including QC files not individually listed in the JSON.
370
552
 
371
553
  ## Adapting to your own data
372
554
 
@@ -394,31 +576,8 @@ origin at bregma, in meters.
394
576
 
395
577
  ## Development
396
578
 
397
- To develop the code, run:
398
- ```bash
399
- uv sync
400
- ```
401
-
402
- Please test your changes using the full linting and testing suite:
403
-
404
- ```bash
405
- ./scripts/run_linters_and_checks.sh -c
406
- ```
407
-
408
- Or run individual commands:
409
- ```bash
410
- uv run --frozen ruff format # Code formatting
411
- uv run --frozen ruff check # Linting
412
- uv run --frozen mypy # Type checking
413
- uv run --frozen interrogate -v src # Documentation coverage
414
- uv run --frozen codespell --check-filenames # Spell checking
415
- uv run --frozen pytest --cov aind_ibl_ephys_alignment_preprocessing # Tests with coverage
416
- ```
417
-
418
- ### Documentation
419
- ```bash
420
- sphinx-build -b html docs/source/ docs/build/html
421
- ```
579
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for developer setup, checks, and
580
+ repository conventions. Coding agents should also read [AGENTS.md](AGENTS.md).
422
581
 
423
582
  ## License
424
583