openads 2.0.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.

Potentially problematic release.


This version of openads might be problematic. Click here for more details.

Files changed (232) hide show
  1. openads-2.0.0/CHANGELOG.md +112 -0
  2. openads-2.0.0/LICENSE +24 -0
  3. openads-2.0.0/MANIFEST.in +10 -0
  4. openads-2.0.0/PKG-INFO +342 -0
  5. openads-2.0.0/README.md +285 -0
  6. openads-2.0.0/docs/architecture.md +154 -0
  7. openads-2.0.0/docs/configuration.md +400 -0
  8. openads-2.0.0/docs/extending.md +269 -0
  9. openads-2.0.0/docs/refactor/decisions.md +627 -0
  10. openads-2.0.0/docs/refactor/functions.md +1683 -0
  11. openads-2.0.0/docs/refactor/mapping.md +464 -0
  12. openads-2.0.0/docs/refactor/notes/apps.md +195 -0
  13. openads-2.0.0/docs/refactor/notes/consolidation_a.md +184 -0
  14. openads-2.0.0/docs/refactor/notes/consolidation_b.md +179 -0
  15. openads-2.0.0/docs/refactor/notes/consolidation_c.md +102 -0
  16. openads-2.0.0/docs/refactor/notes/consolidation_d.md +179 -0
  17. openads-2.0.0/docs/refactor/notes/gen_ttp.md +82 -0
  18. openads-2.0.0/docs/refactor/notes/inference_dwi.md +155 -0
  19. openads-2.0.0/docs/refactor/notes/inference_pwi.md +99 -0
  20. openads-2.0.0/docs/refactor/notes/masking.md +119 -0
  21. openads-2.0.0/docs/refactor/notes/prepdata.md +148 -0
  22. openads-2.0.0/docs/refactor/notes/registration.md +146 -0
  23. openads-2.0.0/docs/refactor/notes/report.md +115 -0
  24. openads-2.0.0/docs/refactor/stage_guide.md +97 -0
  25. openads-2.0.0/docs/user_guide.md +157 -0
  26. openads-2.0.0/pyproject.toml +181 -0
  27. openads-2.0.0/setup.cfg +4 -0
  28. openads-2.0.0/src/openads/__init__.py +77 -0
  29. openads-2.0.0/src/openads/__main__.py +8 -0
  30. openads-2.0.0/src/openads/_lazy.py +70 -0
  31. openads-2.0.0/src/openads/_version.py +5 -0
  32. openads-2.0.0/src/openads/assets/__init__.py +162 -0
  33. openads-2.0.0/src/openads/assets/fetch.py +157 -0
  34. openads-2.0.0/src/openads/assets/manifest.json +545 -0
  35. openads-2.0.0/src/openads/assets/manifest.py +105 -0
  36. openads-2.0.0/src/openads/cli/__init__.py +5 -0
  37. openads-2.0.0/src/openads/cli/main.py +408 -0
  38. openads-2.0.0/src/openads/config/__init__.py +32 -0
  39. openads-2.0.0/src/openads/config/convert.py +155 -0
  40. openads-2.0.0/src/openads/config/defaults/dwi.yaml +123 -0
  41. openads-2.0.0/src/openads/config/defaults/keep_files.yaml +132 -0
  42. openads-2.0.0/src/openads/config/defaults/pwi.yaml +204 -0
  43. openads-2.0.0/src/openads/config/loader.py +254 -0
  44. openads-2.0.0/src/openads/config/schema.py +177 -0
  45. openads-2.0.0/src/openads/core/__init__.py +23 -0
  46. openads-2.0.0/src/openads/core/artifacts.py +192 -0
  47. openads-2.0.0/src/openads/core/layout.py +1180 -0
  48. openads-2.0.0/src/openads/core/result.py +55 -0
  49. openads-2.0.0/src/openads/core/types.py +42 -0
  50. openads-2.0.0/src/openads/core/workspace.py +327 -0
  51. openads-2.0.0/src/openads/errors.py +106 -0
  52. openads-2.0.0/src/openads/inputs/__init__.py +45 -0
  53. openads-2.0.0/src/openads/inputs/builtin_derivations.py +208 -0
  54. openads-2.0.0/src/openads/inputs/derivations.py +297 -0
  55. openads-2.0.0/src/openads/inputs/discovery.py +284 -0
  56. openads-2.0.0/src/openads/inputs/sidecars.py +102 -0
  57. openads-2.0.0/src/openads/inputs/spec.py +165 -0
  58. openads-2.0.0/src/openads/io/__init__.py +26 -0
  59. openads-2.0.0/src/openads/io/ants.py +244 -0
  60. openads-2.0.0/src/openads/io/cleanup.py +209 -0
  61. openads-2.0.0/src/openads/io/figures.py +48 -0
  62. openads-2.0.0/src/openads/io/json.py +39 -0
  63. openads-2.0.0/src/openads/io/manifest.py +150 -0
  64. openads-2.0.0/src/openads/io/nifti.py +224 -0
  65. openads-2.0.0/src/openads/io/surfa.py +53 -0
  66. openads-2.0.0/src/openads/io/tables.py +58 -0
  67. openads-2.0.0/src/openads/io/text.py +25 -0
  68. openads-2.0.0/src/openads/io/transforms.py +106 -0
  69. openads-2.0.0/src/openads/logging.py +37 -0
  70. openads-2.0.0/src/openads/models/__init__.py +19 -0
  71. openads-2.0.0/src/openads/models/checkpoints.py +112 -0
  72. openads-2.0.0/src/openads/models/dagmnet_dwi.py +679 -0
  73. openads-2.0.0/src/openads/models/dagmnet_pwi.py +199 -0
  74. openads-2.0.0/src/openads/models/device.py +95 -0
  75. openads-2.0.0/src/openads/models/synthstrip.py +300 -0
  76. openads-2.0.0/src/openads/ops/__init__.py +83 -0
  77. openads-2.0.0/src/openads/ops/adc.py +43 -0
  78. openads-2.0.0/src/openads/ops/atlas.py +858 -0
  79. openads-2.0.0/src/openads/ops/bimodal.py +84 -0
  80. openads-2.0.0/src/openads/ops/hp_symclassic.py +227 -0
  81. openads-2.0.0/src/openads/ops/inference_dwi.py +152 -0
  82. openads-2.0.0/src/openads/ops/inference_pwi.py +184 -0
  83. openads-2.0.0/src/openads/ops/interpret.py +392 -0
  84. openads-2.0.0/src/openads/ops/masks.py +97 -0
  85. openads-2.0.0/src/openads/ops/metrics.py +122 -0
  86. openads-2.0.0/src/openads/ops/morphology.py +190 -0
  87. openads-2.0.0/src/openads/ops/normalize.py +289 -0
  88. openads-2.0.0/src/openads/ops/padding.py +62 -0
  89. openads-2.0.0/src/openads/ops/postprocess.py +75 -0
  90. openads-2.0.0/src/openads/ops/postprocess_hp.py +104 -0
  91. openads-2.0.0/src/openads/ops/prob_is.py +91 -0
  92. openads-2.0.0/src/openads/ops/pseudo_dwi.py +95 -0
  93. openads-2.0.0/src/openads/ops/radiology.py +964 -0
  94. openads-2.0.0/src/openads/ops/registration.py +63 -0
  95. openads-2.0.0/src/openads/ops/ttp/__init__.py +17 -0
  96. openads-2.0.0/src/openads/ops/ttp/fallback.py +54 -0
  97. openads-2.0.0/src/openads/ops/ttp/fill.py +177 -0
  98. openads-2.0.0/src/openads/ops/ttp/gd.py +145 -0
  99. openads-2.0.0/src/openads/ops/ttp/motion.py +16 -0
  100. openads-2.0.0/src/openads/ops/ttp/peaks.py +96 -0
  101. openads-2.0.0/src/openads/ops/ttp/slicetime.py +152 -0
  102. openads-2.0.0/src/openads/ops/viz.py +273 -0
  103. openads-2.0.0/src/openads/ops/volume_order.py +137 -0
  104. openads-2.0.0/src/openads/ops/volumes.py +69 -0
  105. openads-2.0.0/src/openads/pipelines/__init__.py +59 -0
  106. openads-2.0.0/src/openads/pipelines/base.py +600 -0
  107. openads-2.0.0/src/openads/pipelines/combined.py +135 -0
  108. openads-2.0.0/src/openads/pipelines/dwi.py +50 -0
  109. openads-2.0.0/src/openads/pipelines/pwi.py +53 -0
  110. openads-2.0.0/src/openads/py.typed +0 -0
  111. openads-2.0.0/src/openads/registry/__init__.py +331 -0
  112. openads-2.0.0/src/openads/registry/base.py +97 -0
  113. openads-2.0.0/src/openads/stages/__init__.py +261 -0
  114. openads-2.0.0/src/openads/stages/base.py +378 -0
  115. openads-2.0.0/src/openads/stages/cleanup/__init__.py +55 -0
  116. openads-2.0.0/src/openads/stages/cleanup/methods/__init__.py +6 -0
  117. openads-2.0.0/src/openads/stages/cleanup/methods/keep_manifest.py +43 -0
  118. openads-2.0.0/src/openads/stages/cleanup/params.py +29 -0
  119. openads-2.0.0/src/openads/stages/gen_mask/__init__.py +53 -0
  120. openads-2.0.0/src/openads/stages/gen_mask/common.py +33 -0
  121. openads-2.0.0/src/openads/stages/gen_mask/methods/__init__.py +12 -0
  122. openads-2.0.0/src/openads/stages/gen_mask/methods/synthstrip.py +88 -0
  123. openads-2.0.0/src/openads/stages/gen_mask/params.py +37 -0
  124. openads-2.0.0/src/openads/stages/gen_ttp/__init__.py +55 -0
  125. openads-2.0.0/src/openads/stages/gen_ttp/common.py +169 -0
  126. openads-2.0.0/src/openads/stages/gen_ttp/methods/__init__.py +13 -0
  127. openads-2.0.0/src/openads/stages/gen_ttp/methods/argmin.py +47 -0
  128. openads-2.0.0/src/openads/stages/gen_ttp/methods/peak_legacy.py +393 -0
  129. openads-2.0.0/src/openads/stages/gen_ttp/moco.py +262 -0
  130. openads-2.0.0/src/openads/stages/gen_ttp/params.py +248 -0
  131. openads-2.0.0/src/openads/stages/inference/__init__.py +91 -0
  132. openads-2.0.0/src/openads/stages/inference/methods/__init__.py +13 -0
  133. openads-2.0.0/src/openads/stages/inference/methods/dagmnet_dwi.py +124 -0
  134. openads-2.0.0/src/openads/stages/inference/methods/dagmnet_pwi.py +280 -0
  135. openads-2.0.0/src/openads/stages/inference/params.py +144 -0
  136. openads-2.0.0/src/openads/stages/prepdata/__init__.py +60 -0
  137. openads-2.0.0/src/openads/stages/prepdata/methods/__init__.py +13 -0
  138. openads-2.0.0/src/openads/stages/prepdata/methods/standard.py +267 -0
  139. openads-2.0.0/src/openads/stages/prepdata/params.py +34 -0
  140. openads-2.0.0/src/openads/stages/registration/__init__.py +151 -0
  141. openads-2.0.0/src/openads/stages/registration/methods/__init__.py +12 -0
  142. openads-2.0.0/src/openads/stages/registration/methods/ants_affine_syn.py +337 -0
  143. openads-2.0.0/src/openads/stages/registration/params.py +78 -0
  144. openads-2.0.0/src/openads/stages/report/__init__.py +107 -0
  145. openads-2.0.0/src/openads/stages/report/methods/__init__.py +13 -0
  146. openads-2.0.0/src/openads/stages/report/methods/dwi_standard.py +96 -0
  147. openads-2.0.0/src/openads/stages/report/methods/pwi_standard.py +64 -0
  148. openads-2.0.0/src/openads/stages/report/params.py +103 -0
  149. openads-2.0.0/src/openads/stages/report/steps/__init__.py +13 -0
  150. openads-2.0.0/src/openads/stages/report/steps/figures.py +230 -0
  151. openads-2.0.0/src/openads/stages/report/steps/interpretation.py +83 -0
  152. openads-2.0.0/src/openads/stages/report/steps/qfv.py +113 -0
  153. openads-2.0.0/src/openads/stages/report/steps/radiology.py +281 -0
  154. openads-2.0.0/src/openads/stages/skull_strip/__init__.py +58 -0
  155. openads-2.0.0/src/openads/stages/skull_strip/methods/__init__.py +12 -0
  156. openads-2.0.0/src/openads/stages/skull_strip/methods/apply_mask.py +86 -0
  157. openads-2.0.0/src/openads/stages/skull_strip/params.py +36 -0
  158. openads-2.0.0/src/openads/stages/ttpadc_coreg/__init__.py +119 -0
  159. openads-2.0.0/src/openads/stages/ttpadc_coreg/methods/__init__.py +12 -0
  160. openads-2.0.0/src/openads/stages/ttpadc_coreg/methods/ants_affine_masks.py +157 -0
  161. openads-2.0.0/src/openads/stages/ttpadc_coreg/params.py +36 -0
  162. openads-2.0.0/src/openads/steps/__init__.py +38 -0
  163. openads-2.0.0/src/openads/steps/normalize.py +46 -0
  164. openads-2.0.0/src/openads/steps/segmentation_dwi.py +274 -0
  165. openads-2.0.0/src/openads/steps/segmentation_pwi.py +285 -0
  166. openads-2.0.0/src/openads/steps/warp.py +54 -0
  167. openads-2.0.0/src/openads.egg-info/PKG-INFO +342 -0
  168. openads-2.0.0/src/openads.egg-info/SOURCES.txt +230 -0
  169. openads-2.0.0/src/openads.egg-info/dependency_links.txt +1 -0
  170. openads-2.0.0/src/openads.egg-info/entry_points.txt +3 -0
  171. openads-2.0.0/src/openads.egg-info/requires.txt +29 -0
  172. openads-2.0.0/src/openads.egg-info/top_level.txt +1 -0
  173. openads-2.0.0/tests/conftest.py +95 -0
  174. openads-2.0.0/tests/data/expected_tree_sub-02e8eb42.txt +156 -0
  175. openads-2.0.0/tests/unit/stubs.py +73 -0
  176. openads-2.0.0/tests/unit/test_app_gui_command.py +165 -0
  177. openads-2.0.0/tests/unit/test_app_scripts.py +159 -0
  178. openads-2.0.0/tests/unit/test_app_server.py +230 -0
  179. openads-2.0.0/tests/unit/test_assets.py +92 -0
  180. openads-2.0.0/tests/unit/test_assets_download.py +65 -0
  181. openads-2.0.0/tests/unit/test_cleanup.py +212 -0
  182. openads-2.0.0/tests/unit/test_cli.py +226 -0
  183. openads-2.0.0/tests/unit/test_config.py +173 -0
  184. openads-2.0.0/tests/unit/test_convert.py +69 -0
  185. openads-2.0.0/tests/unit/test_derivations.py +102 -0
  186. openads-2.0.0/tests/unit/test_docs_examples.py +171 -0
  187. openads-2.0.0/tests/unit/test_gen_mask.py +166 -0
  188. openads-2.0.0/tests/unit/test_gen_mask_plugin.py +92 -0
  189. openads-2.0.0/tests/unit/test_gen_ttp_moco.py +105 -0
  190. openads-2.0.0/tests/unit/test_gen_ttp_stage.py +184 -0
  191. openads-2.0.0/tests/unit/test_import.py +76 -0
  192. openads-2.0.0/tests/unit/test_inference_dwi.py +184 -0
  193. openads-2.0.0/tests/unit/test_inference_dwi_plugin.py +65 -0
  194. openads-2.0.0/tests/unit/test_inference_pwi.py +154 -0
  195. openads-2.0.0/tests/unit/test_inputs.py +173 -0
  196. openads-2.0.0/tests/unit/test_io_ants.py +67 -0
  197. openads-2.0.0/tests/unit/test_io_misc.py +122 -0
  198. openads-2.0.0/tests/unit/test_io_nifti.py +70 -0
  199. openads-2.0.0/tests/unit/test_io_surfa.py +27 -0
  200. openads-2.0.0/tests/unit/test_layout.py +152 -0
  201. openads-2.0.0/tests/unit/test_models.py +154 -0
  202. openads-2.0.0/tests/unit/test_ops_adc.py +107 -0
  203. openads-2.0.0/tests/unit/test_ops_atlas.py +118 -0
  204. openads-2.0.0/tests/unit/test_ops_bimodal.py +28 -0
  205. openads-2.0.0/tests/unit/test_ops_hp_symclassic.py +106 -0
  206. openads-2.0.0/tests/unit/test_ops_inference_pwi.py +85 -0
  207. openads-2.0.0/tests/unit/test_ops_interpret.py +69 -0
  208. openads-2.0.0/tests/unit/test_ops_masks.py +73 -0
  209. openads-2.0.0/tests/unit/test_ops_metrics.py +34 -0
  210. openads-2.0.0/tests/unit/test_ops_morphology.py +31 -0
  211. openads-2.0.0/tests/unit/test_ops_normalize.py +96 -0
  212. openads-2.0.0/tests/unit/test_ops_padding.py +20 -0
  213. openads-2.0.0/tests/unit/test_ops_postprocess.py +26 -0
  214. openads-2.0.0/tests/unit/test_ops_postprocess_hp.py +42 -0
  215. openads-2.0.0/tests/unit/test_ops_prob_is.py +26 -0
  216. openads-2.0.0/tests/unit/test_ops_radiology.py +133 -0
  217. openads-2.0.0/tests/unit/test_ops_ttp_numerics.py +178 -0
  218. openads-2.0.0/tests/unit/test_ops_viz.py +46 -0
  219. openads-2.0.0/tests/unit/test_ops_volumes.py +29 -0
  220. openads-2.0.0/tests/unit/test_packaging.py +145 -0
  221. openads-2.0.0/tests/unit/test_pipelines.py +203 -0
  222. openads-2.0.0/tests/unit/test_prepdata.py +290 -0
  223. openads-2.0.0/tests/unit/test_registration.py +145 -0
  224. openads-2.0.0/tests/unit/test_registry.py +197 -0
  225. openads-2.0.0/tests/unit/test_report_stage.py +135 -0
  226. openads-2.0.0/tests/unit/test_run_stage.py +181 -0
  227. openads-2.0.0/tests/unit/test_skull_strip.py +137 -0
  228. openads-2.0.0/tests/unit/test_stage_base.py +190 -0
  229. openads-2.0.0/tests/unit/test_stage_plugin.py +245 -0
  230. openads-2.0.0/tests/unit/test_steps.py +149 -0
  231. openads-2.0.0/tests/unit/test_ttpadc_coreg.py +112 -0
  232. openads-2.0.0/tests/unit/test_workspace.py +98 -0
@@ -0,0 +1,112 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
+
6
+ ## [2.0.0] - unreleased
7
+
8
+ ### Changed for the PyPI release (ADR-041)
9
+ - Import package renamed from `ads` to `openads` (`import openads as ads`); a
10
+ different PyPI distribution also installs a module `ads`. Console scripts:
11
+ `openads` and `ads`. Plugin entry-point groups: `openads.plugins`, `openads.stages`.
12
+ - `openads assets download [--kind model|atlas|example] [--assets-dir] [--url]`
13
+ (and `openads.download_assets()`): fetches the assets from `assets/` of
14
+ farialab/OpenADS on GitHub (pinned commit; `ADS_ASSETS_URL` for another mirror),
15
+ verifies SHA-256, prints the JHU licence it accepts.
16
+ - `runtime.device` defaults to `auto`: CUDA when visible, else the CPU (logged).
17
+ - The wheel and sdist contain code only; the sdist no longer carries the
18
+ repository-only regression harness (`tools/`) or verification reports.
19
+
20
+ A refactor of OpenADS 1.0 (HEAD 6c378d1) into one library with the same output
21
+ tree and the same numerics. Every original function is mapped in
22
+ `docs/refactor/mapping.md`; the decisions are ADRs in
23
+ `docs/refactor/decisions.md`; the regression evidence (stage gates and
24
+ verification reports) stays in the development repository.
25
+
26
+ ### Added
27
+ - Package layout `src/openads` with layers cli -> pipelines -> stages -> steps ->
28
+ {ops, io, models, core/config/registry/inputs/assets}
29
+ (`docs/architecture.md`).
30
+ - Python API: `openads.run_dwi`, `openads.run_pwi`, `openads.run_combined`,
31
+ `openads.run_stage` (run one stage on an existing output folder),
32
+ `openads.load_config`, `openads.SubjectWorkspace`; `import openads` loads no heavy
33
+ library.
34
+ - Method registry: `@register(stage, name, params=..., modalities=...)`,
35
+ modality-aware (`Registry.available(stage, modality)`), built-in methods
36
+ auto-discovered (`openads.registry.import_submodules`), plugins through the
37
+ `openads.plugins` entry-point group.
38
+ - Stage registry: `openads.stages.register_stage`, entry-point group `openads.stages`,
39
+ `Pipeline.with_stages(...)`, `OutputLayout.extended(...)`; cleanup keeps the
40
+ layout's non-transient artifacts.
41
+ - `openads.steps`: shared file-level building blocks (`write_normalized`,
42
+ `write_dwi_segmentation`, PWI segmentation helpers, `warp_to_affsyn`).
43
+ - `openads.ops`: the 1.0 numerics as pure, unit-tested functions (ADC, volume
44
+ order, pseudo-DWI, masks, KDE normaliser, padding, DAGMNet inference
45
+ helpers, symClassic, post-processing, metrics, TTP, atlas QFV, radiology
46
+ report, SHAP, figures).
47
+ - Typed configuration with packaged defaults (`openads/config/defaults`), strict
48
+ keys, `--config` / `--set key=value` layering, `ads config show`.
49
+ - One output-layout table (`openads.core.layout`) for all 156 files of the 1.0
50
+ tree; data import with input specs, discovery and named derivations
51
+ (`openads.inputs`).
52
+ - Asset resolution (`ADS_ASSETS_DIR`, repository checkout, user cache) with a
53
+ SHA-256 manifest: `ads assets where|verify`.
54
+ - CLI: `ads dwi|pwi|combined|stages list|stages run|config show|assets|version`
55
+ (in-process), `--set`, `--dwi-set`/`--pwi-set`; stage help generated from
56
+ the stage specs; `ads dwi|pwi --dry-run` and `ads stages list --json` print
57
+ JSON for other tools.
58
+ - Client helpers: `Pipeline.plan()` / `openads.RunPlan` (stages and output folder
59
+ without running), `openads.example_subjects()`, `openads.parse_ref` /
60
+ `openads.stages.resolve_ref` (stage input/output references).
61
+ - User documentation: README, `docs/user_guide.md`, `docs/configuration.md`
62
+ (generated by `tools/gen_config_doc.py`), `docs/extending.md`,
63
+ `docs/architecture.md`.
64
+ - Refactor records: `docs/refactor/{mapping,decisions,harness,functions}.md`,
65
+ verification reports, regression harness in `tools/` (multi-run noise
66
+ envelope, population noise for motion-correction transforms, strict2
67
+ reference with the cuDNN autotuner pinned off).
68
+
69
+ - Thin clients on the public API, outside the wheel: web server `apps/server`
70
+ (FastAPI, 1.0 endpoints + a job API), Qt GUI launcher `apps/gui`, web pages
71
+ `apps/web`, Docker images `docker/`, 1.0-compatible `scripts/*.sh` wrappers.
72
+
73
+ ### Changed
74
+ - Faster DWI inference (ADR-039): DAGMNet-DWI's transposed convolutions run on
75
+ the model device (`stages.inference.params.transpose_backend: torch`; 1.0's
76
+ CPU NumPy loop is `numpy`): the stage takes ~10 s instead of ~60 s, with
77
+ identical output files on the example subject. `runtime.cudnn_benchmark`
78
+ defaults to `false` (1.0: `true`): SynthStrip 2.4 s instead of 5.8 s and no
79
+ run-to-run mask flips from the cuDNN autotuner.
80
+ - Faster HP_symClassic generation (ADR-040): vectorised supervoxel selection
81
+ and threaded per-slice SLIC, ~2 s instead of ~17 s, bit-identical masks.
82
+ - Distribution renamed to `openads` (import package and console script stay
83
+ `ads`).
84
+ - The CLI no longer shells out to `scripts/run_ads_*.py`; the shell wrappers
85
+ are replaced by `ads dwi|pwi|combined`.
86
+ - Fail fast: no silent CUDA->CPU or `cuda:1`->`cuda:0` fallback (`DeviceError`);
87
+ places where 1.0 logged an error and continued now raise (listed per stage in
88
+ the ADRs). Tolerated, configurable exceptions: the motion-correction timeout
89
+ skip and the argmin TTP for PWI without a JSON sidecar
90
+ (`no_json_policy: legacy`).
91
+ - Motion correction runs in a `fork`ed child by default (as 1.0); `spawn` is
92
+ opt-in (`motion_correction.start_method`).
93
+ - Packaging: PEP 639 license metadata (`LicenseRef-JHU-Non-Profit-Research`),
94
+ `setuptools>=77`, dependency ranges instead of exact pins, `MANIFEST.in`
95
+ (no assets and no verification reports in the sdist). Every dependency of
96
+ `ads dwi --all` / `ads pwi --all` is in the default install.
97
+ - Default output root is `output` relative to the working directory.
98
+ - The `report` numerics (`openads.ops.interpret`, `openads.ops.radiology`) import
99
+ pandas lazily.
100
+
101
+ ### Removed
102
+ - Unused 1.0 modules (dead parallel implementations, unused services, domain
103
+ dataclasses, legacy loaders); each is listed with its reason in
104
+ `docs/refactor/mapping.md`.
105
+ - `tqdm` dependency (never imported).
106
+
107
+ ### Known behaviour kept from 1.0
108
+ - Run-to-run differences of the original method: multithreaded ANTs
109
+ registration, the cuDNN autotuner (1-10 voxels of a network mask), and the
110
+ bistable KDE ADC normaliser (ADR-020 to ADR-022). A 1-voxel brain-mask
111
+ difference can change the downstream registration (ADR-026).
112
+ - Numerical quirks of 1.0 are kept verbatim and listed in ADR-034.
openads-2.0.0/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ JHU Non-Profit Research Software License Agreement
2
+
3
+ This license agreement ("License") is effective automatically between you, a non-profit academic or non-profit research institution ("Licensee"), and The Johns Hopkins University (“JHU”) for use of the software with which this License is distributed (“Software”) so long as Licensee complies with the following terms and conditions:
4
+
5
+ The requirement to acknowledge the copyright of JHU as follows: “© The Johns Hopkins University” and copyrights of any incorporated third-party software as described in the associated documentation.
6
+
7
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the source code must retain the above copyright notice and these terms and conditions.
8
+
9
+ Neither the name of JHU nor its affiliates may be used to endorse or promote products derived from this Software without specific prior written permission from an authorized JHU representative.
10
+
11
+ THIS SOFTWARE IS PROVIDED BY JHU "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JHU BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. LICENSEE AGREES TO DEFEND, INDEMNIFY AND HOLD HARMLESS JHU FOR ANY CLAIMS ARISING FROM LICENSEE’S USE OF THE SOFTWARE TO THE FULLEST EXTENT PERMITTED BY LAW.
12
+
13
+ Only a non-exclusive, nontransferable license is granted to Licensee to use the Software for non-commercial, non-profit purposes. Commercial use of the Software, including research use by for-profit entities, requires a separately executed written license agreement.
14
+
15
+ Licensee agrees that it will use the Software, and any modifications, improvements, or derivatives of the Software that the Licensee may create (collectively, "Improvements") solely for non-commercial, non-profit purposes and shall not distribute or transfer the Software or Improvements to any third-party without requiring that such third-parties adhere to the terms of this License. Licensee agrees that any Improvements made by Licensee shall be subject to the same terms and conditions as the Software.
16
+
17
+ Licensee acknowledges that JHU holds copyright in the Software or portions of the Software, and that the Software may incorporate third-party software which may be subject to additional terms and conditions. Licensee shall comply with any additional terms and conditions appliable to such third-party software.
18
+
19
+ Licensee agrees that any publication of results obtained with the Software will acknowledge its use by an appropriate citation.
20
+
21
+ Licensee’s rights under this License terminate automatically without notice from JHU if Licensee fail to comply with any term(s) of this License.
22
+
23
+ This License shall be governed by the laws of the State of Maryland, excluding the application of its conflicts of law rules. Licensee agrees that any dispute shall be appropriate only in the state and federal courts located within the State of Maryland.
24
+
@@ -0,0 +1,10 @@
1
+ # sdist contents. The wheel only ships src/openads (+ package data in pyproject.toml).
2
+ include LICENSE README.md CHANGELOG.md pyproject.toml MANIFEST.in
3
+ graft src/openads
4
+ graft tests
5
+ graft docs
6
+ # Models, atlases and examples (~320 MB) are never packaged; see ADS_ASSETS_DIR.
7
+ prune assets
8
+ # Gate reports reference local baseline runs; they stay in the repository only.
9
+ prune docs/refactor/verification
10
+ global-exclude __pycache__ *.py[cod] *.so .DS_Store
openads-2.0.0/PKG-INFO ADDED
@@ -0,0 +1,342 @@
1
+ Metadata-Version: 2.4
2
+ Name: openads
3
+ Version: 2.0.0
4
+ Summary: OpenADS: acute stroke detection pipelines for DWI and PWI MRI
5
+ Author: Joshua Shun Liu, Chin-Fu Liu
6
+ Author-email: "Andreia V. Faria" <andreia.faria@gmail.com>
7
+ License-Expression: LicenseRef-JHU-Non-Profit-Research
8
+ Project-URL: Homepage, https://github.com/farialab/OpenADS
9
+ Project-URL: Repository, https://github.com/farialab/OpenADS
10
+ Project-URL: Issues, https://github.com/farialab/OpenADS/issues
11
+ Project-URL: Changelog, https://github.com/farialab/OpenADS/blob/main/CHANGELOG.md
12
+ Project-URL: Documentation, https://github.com/farialab/OpenADS/tree/main/docs
13
+ Keywords: stroke,MRI,DWI,PWI,segmentation,neuroimaging
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Environment :: GPU :: NVIDIA CUDA
16
+ Classifier: Intended Audience :: Science/Research
17
+ Classifier: Intended Audience :: Healthcare Industry
18
+ Classifier: Operating System :: POSIX :: Linux
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3 :: Only
21
+ Classifier: Programming Language :: Python :: 3.10
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Topic :: Scientific/Engineering :: Image Processing
25
+ Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
26
+ Classifier: Typing :: Typed
27
+ Requires-Python: <3.13,>=3.10
28
+ Description-Content-Type: text/markdown
29
+ License-File: LICENSE
30
+ Requires-Dist: antspyx<0.7,>=0.5.4
31
+ Requires-Dist: joblib<2,>=1.4
32
+ Requires-Dist: matplotlib<4,>=3.9
33
+ Requires-Dist: nibabel<6,>=5.3
34
+ Requires-Dist: numpy<3,>=2.0
35
+ Requires-Dist: pandas<3,>=2.2
36
+ Requires-Dist: pyyaml<7,>=6.0
37
+ Requires-Dist: scikit-image<1,>=0.24
38
+ Requires-Dist: scikit-learn<2,>=1.7
39
+ Requires-Dist: scipy<2,>=1.13
40
+ Requires-Dist: shap<1,>=0.50
41
+ Requires-Dist: surfa<0.7,>=0.6.3
42
+ Requires-Dist: torch<3,>=2.5
43
+ Provides-Extra: dev
44
+ Requires-Dist: pytest>=8; extra == "dev"
45
+ Requires-Dist: ruff>=0.13; extra == "dev"
46
+ Requires-Dist: mypy>=1.10; extra == "dev"
47
+ Requires-Dist: types-PyYAML; extra == "dev"
48
+ Requires-Dist: build; extra == "dev"
49
+ Requires-Dist: twine; extra == "dev"
50
+ Provides-Extra: server
51
+ Requires-Dist: fastapi>=0.110; extra == "server"
52
+ Requires-Dist: uvicorn[standard]>=0.30; extra == "server"
53
+ Requires-Dist: python-multipart; extra == "server"
54
+ Provides-Extra: gui
55
+ Requires-Dist: PyQt5>=5.15; extra == "gui"
56
+ Dynamic: license-file
57
+
58
+ # OpenADS
59
+
60
+ OpenADS is an end-to-end pipeline for acute stroke MRI. For one subject folder
61
+ it runs preprocessing, brain masking, MNI registration, lesion segmentation
62
+ (DWI) or hypoperfusion segmentation (PWI), and report generation, and writes
63
+ NIfTI masks, metrics, QFV/lesion-load tables, a text radiological report, SHAP
64
+ interpretation PDFs and PNG figures.
65
+
66
+ - Distribution name: `openads`. Import package: `openads` (`import openads as ads`).
67
+ Console scripts: `openads` and `ads` (the same program).
68
+ - Version 2.0 is a refactor of OpenADS 1.0. It writes the same output tree with
69
+ the same numerics (see [`docs/refactor/`](docs/refactor/)), and adds a
70
+ Python API, a method and stage plugin system, and typed configuration.
71
+ - Platform: Linux, Python 3.10-3.12. An NVIDIA GPU with CUDA is used when
72
+ present; without one OpenADS runs on the CPU (slower).
73
+
74
+ ## Install
75
+
76
+ ```bash
77
+ python -m venv .venv && source .venv/bin/activate
78
+ python -m pip install -U pip
79
+ python -m pip install openads
80
+ openads assets download # model weights, atlases, example subjects (~360 MB, once)
81
+ openads version
82
+ ```
83
+
84
+ From a Jupyter notebook, install into the notebook's own environment with `%pip`
85
+ (not `!pip`, which may use another Python):
86
+
87
+ ```ipython
88
+ %pip install openads
89
+ !openads assets download
90
+ ```
91
+
92
+ **CPU or GPU.** `pip install openads` installs torch as well. On Linux, PyPI's
93
+ torch is the CUDA build (large); on Windows and macOS it is the CPU build. To
94
+ choose explicitly, install torch first and then OpenADS, which keeps the torch
95
+ you already have:
96
+
97
+ ```bash
98
+ pip install torch --index-url https://download.pytorch.org/whl/cpu # CPU only, small
99
+ # or: uv pip install --torch-backend=auto openads # uv picks CUDA or CPU for this machine
100
+ pip install openads
101
+ ```
102
+
103
+ In an environment that already has a CUDA build of torch (>= 2.5), `pip install
104
+ openads` uses it. The default `runtime.device: auto` takes the GPU when torch
105
+ sees one and otherwise the CPU, and logs which; `--set runtime.device=cuda`
106
+ insists on the GPU and fails without one.
107
+
108
+ Every dependency that `ads dwi --all` and `ads pwi --all` need is in the
109
+ default install (torch, antspyx, nibabel, scipy, scikit-image, scikit-learn,
110
+ pandas, matplotlib, shap, surfa, joblib, PyYAML). Optional extras: `dev`
111
+ (pytest, ruff, mypy, build, twine), `server` and `gui` (for the web server and
112
+ GUI launcher, see [GUI / server / Docker](#gui--server--docker)).
113
+
114
+ For development, from a source checkout: `pip install -e ".[dev]"`.
115
+
116
+ ## Assets (models, atlases, examples)
117
+
118
+ The model checkpoints, atlases and example subjects are not in the wheel.
119
+ `openads assets download` fetches them from the `assets/` folder of the official
120
+ [farialab/OpenADS](https://github.com/farialab/OpenADS) repository (pinned commit), checks
121
+ every file against the SHA-256 in the packaged manifest, and stores them in
122
+ `~/.cache/openads/assets` (or `ADS_ASSETS_DIR`). It prints the licence first:
123
+ **downloading accepts the JHU Non-Profit Research Software License** (see
124
+ [License](#license)). Re-running it skips files that are already correct.
125
+
126
+ ```bash
127
+ openads assets download # everything
128
+ openads assets download --kind atlas # only atlases (also: model, example)
129
+ openads assets download --assets-dir DIR # somewhere else
130
+ ADS_ASSETS_URL=https://my.mirror/openads-assets openads assets download # another mirror
131
+ ```
132
+
133
+ OpenADS looks for the asset root in this order and never downloads on its own:
134
+
135
+ 1. an explicit `paths.assets_dir` in the configuration,
136
+ 2. the environment variable `ADS_ASSETS_DIR`,
137
+ 3. `<repository>/assets` when OpenADS runs from a source checkout,
138
+ 4. `~/.cache/openads/assets`.
139
+
140
+ ```bash
141
+ openads assets where # show the candidates and the root in use
142
+ openads assets verify # check sizes and SHA-256 against the manifest
143
+ openads assets verify --no-hash # sizes only (fast)
144
+ ```
145
+
146
+ ## Command line quick start
147
+
148
+ Each OpenADS 1.0 command has a 2.0 equivalent:
149
+
150
+ | OpenADS 1.0 | OpenADS 2.0 |
151
+ |---|---|
152
+ | `./scripts/run_dwi.sh SUBJECT --all --gpu 1` | `ads dwi --subject-path SUBJECT --all --gpu 1` |
153
+ | `./scripts/run_pwi.sh SUBJECT --all --gpu 1` | `ads pwi --subject-path SUBJECT --all --gpu 1` |
154
+ | `./scripts/run_combined.sh --dwi D --pwi P --all --gpu 1` | `ads combined --dwi-subject-path D --pwi-subject-path P --all --gpu 1` |
155
+ | `python scripts/run_ads_dwi.py --subject-path S --config C ...` | `ads dwi --subject-path S --config my.yaml ...` |
156
+ | `python -m openads.cli dwi ...` | `ads dwi ...` or `python -m openads dwi ...` |
157
+
158
+ ```bash
159
+ # Full DWI and PWI pipelines on the example subject (after `openads assets download`;
160
+ # from a source checkout the examples are in assets/examples, else ~/.cache/openads/assets/examples)
161
+ ads dwi --subject-path assets/examples/dwi/sub-02e8eb42 --all --gpu 0 --output-root out
162
+ ads pwi --subject-path assets/examples/pwi/sub-02e8eb42 --all --gpu 0 --output-root out
163
+
164
+ # Selected stages, a custom output root
165
+ ads dwi --subject-path /data/dwi/sub-0037761d \
166
+ --stages prepdata,gen_mask,skull_strip,registration,inference,report --gpu 0 --output-root /output
167
+ ads pwi --subject-path /data/pwi/sub-0037761d \
168
+ --stages prepdata,gen_mask,skull_strip,gen_ttp,registration,ttpadc_coreg,inference,report \
169
+ --gpu 0 --output-root /output
170
+
171
+ # Combined: DWI first, then PWI (the DWI brain mask is copied into PWI when missing)
172
+ ads combined --dwi-subject-path assets/examples/dwi/sub-02e8eb42 \
173
+ --pwi-subject-path assets/examples/pwi/sub-02e8eb42 --all --gpu 0
174
+ ads combined --dwi-subject-path D --pwi-subject-path P \
175
+ --dwi-stages prepdata,gen_mask,skull_strip,registration,inference,report \
176
+ --pwi-stages prepdata,gen_mask,skull_strip,gen_ttp,registration,ttpadc_coreg,inference,report \
177
+ --gpu 0 --output-root /data/openads_output
178
+
179
+ # One stage on an existing output folder
180
+ ads stages run gen_mask --modality dwi --subject-id sub-02e8eb42 --output-root out --gpu 0
181
+
182
+ # Inspect
183
+ ads stages list dwi # stages and the methods available for DWI (--json for tools)
184
+ ads pwi --subject-path DIR --all --dry-run # print stages and output folder as JSON, run nothing
185
+ ads config show pwi # the merged configuration (packaged defaults + --config + --set)
186
+ ```
187
+
188
+ Configuration is layered: packaged defaults < `--config my.yaml` <
189
+ `--set key=value` (repeatable, value parsed as YAML), for example
190
+ `--set stages.report.enabled=false` or `--set runtime.device=cuda:1`.
191
+ `--gpu N` sets `CUDA_VISIBLE_DEVICES`; `--gpu cpu` hides all GPUs, and the
192
+ default `runtime.device: auto` then runs on the CPU (it logs the choice; an
193
+ explicit `--set runtime.device=cuda` fails instead). See [`docs/configuration.md`](docs/configuration.md).
194
+
195
+ Pass one subject folder to `--subject-path`, not a dataset root. The subject
196
+ folder holds files such as `<id>_DWI.nii.gz`, `<id>_b0.nii.gz`, `<id>_ADC.nii.gz`,
197
+ `<id>_stroke.nii.gz` (DWI) and additionally `<id>_PWI.nii.gz`, `<id>_PWI.json`,
198
+ `<id>_HP_manual.nii.gz` (PWI). A missing ADC is computed from DWI and b0; a
199
+ 4D DWI is split into b0 and DWI ([`docs/user_guide.md`](docs/user_guide.md)).
200
+
201
+ ## Python API quick start
202
+
203
+ `import openads` is light (no torch, ANTs or pandas). Heavy libraries load when a
204
+ stage runs. The examples use the full name; `import openads as ads` works the same way.
205
+
206
+ ```python
207
+ import openads
208
+ from openads.models.device import set_visible_gpus
209
+
210
+ set_visible_gpus("0") # like --gpu 0; call before anything initialises CUDA
211
+ result = openads.run_dwi("assets/examples/dwi/sub-02e8eb42", all_stages=True, output_root="out")
212
+ print(result.workspace.modality_root) # out/sub-02e8eb42/DWI
213
+ print(sorted(result.results)) # stage name -> StageResult
214
+ ```
215
+
216
+ Plan a run without running it, and find the bundled example subjects:
217
+
218
+ ```python
219
+ plan = openads.get_pipeline("dwi").plan("assets/examples/dwi/sub-02e8eb42", output_root="out")
220
+ print(plan.output.subject_root) # out/sub-02e8eb42 (where run() writes)
221
+ print([p.name for p in openads.example_subjects("dwi")]) # ['sub-02e8eb42']
222
+ ```
223
+
224
+ Configuration objects and overrides:
225
+
226
+ ```python
227
+ cfg = openads.load_config(pipeline="dwi", overrides=["stages.report.enabled=false"])
228
+ print(cfg.stage("gen_mask").method) # synthstrip
229
+ print(cfg.stage("report").enabled) # False
230
+ ```
231
+
232
+ Run one stage on an existing output folder (its inputs must already be there):
233
+
234
+ ```python
235
+ res = openads.run_stage("gen_mask", subject_id="sub-02e8eb42", modality="DWI", output_root="out")
236
+ print(res.produced["preprocess.brain_mask"]) # out/sub-02e8eb42/DWI/preprocess/sub-02e8eb42_DWIbrain-mask.nii.gz
237
+ ```
238
+
239
+ The numerics are plain functions on arrays in `openads.ops`:
240
+
241
+ ```python
242
+ import numpy as np
243
+ from openads.ops.adc import compute_adc
244
+
245
+ dwi = np.full((4, 4, 4), 400.0)
246
+ b0 = np.full((4, 4, 4), 1000.0)
247
+ adc = compute_adc(dwi, b0, bvalue=1000.0) # float32, -ln(dwi/b0)/b
248
+ print(round(float(adc[0, 0, 0]), 6)) # 0.000916
249
+ ```
250
+
251
+ Add your own method (here a brain-mask method) in one file and select it by
252
+ configuration. See [`docs/extending.md`](docs/extending.md) for plugins and new
253
+ stages.
254
+
255
+ ```python
256
+ from dataclasses import dataclass
257
+ from pathlib import Path
258
+
259
+ import nibabel as nib
260
+ import numpy as np
261
+
262
+ from openads.registry import register
263
+ from openads.stages import StageContext
264
+
265
+
266
+ @dataclass(frozen=True)
267
+ class ThresholdParams:
268
+ level: float = 50.0
269
+
270
+
271
+ @register("gen_mask", "threshold", params=ThresholdParams)
272
+ def threshold(ctx: StageContext) -> dict[str, Path]:
273
+ """Toy brain mask: DWI > level."""
274
+ img = nib.load(ctx.ws.path("preprocess.dwi"))
275
+ mask = (np.asarray(img.dataobj) > ctx.params.level).astype(np.uint8)
276
+ out = ctx.ws.path("preprocess.brain_mask")
277
+ nib.save(nib.Nifti1Image(mask, img.affine), out)
278
+ return {"preprocess.brain_mask": out}
279
+
280
+
281
+ res = openads.run_stage(
282
+ "gen_mask", subject_id="sub-02e8eb42", modality="DWI", output_root="out",
283
+ method="threshold", overrides=["stages.gen_mask.params.level=55"],
284
+ )
285
+ print(res.method) # threshold
286
+ ```
287
+
288
+ ## Output tree
289
+
290
+ ```
291
+ out/sub-02e8eb42
292
+ ├── DWI
293
+ │ ├── preprocess DWI, B0, ADC (+ _brain), DWIbrain-mask, stroke
294
+ │ ├── registration *_space-MNI152_aff[_desc-norm], *_affsyn, transforms (.mat, warps), manifest
295
+ │ ├── segment stroke-mask (native), _space-MNI152, _space-MNI152_affsyn, metrics.json
296
+ │ └── reporting *_QFV.csv, *_lesionload.csv, automatic_radiological_report.txt,
297
+ │ *_report_interpretation.pdf, DWIstroke*.png
298
+ └── PWI
299
+ ├── preprocess PWI, PWI.json, PWI_STC, PWI_average, TTP, masks, *_PWI_moco_aff/*.mat
300
+ ├── registration TTP/HP_manual in DWI and MNI space, coregistration transforms
301
+ ├── segmentation HP-mask (native), _space-MNI152[_affsyn|_desc-postproc], metrics.json
302
+ └── reporting *_HPQFV.csv, *_HPload.csv, automatic_radiological_report.txt, HP*.png
303
+ ```
304
+
305
+ A PWI run after a DWI run of the same subject reuses the DWI registration. The
306
+ file names are defined in one table (`openads.core.layout`); cleanup keeps exactly
307
+ the files of this tree.
308
+
309
+ ## GUI / server / Docker
310
+
311
+ Thin clients in [`apps/`](apps/) build `ads ...` command lines (stages from
312
+ `openads.get_pipeline(...).stage_names`) and run them as subprocesses. They are not
313
+ in the wheel: run them from a source checkout with the extras installed.
314
+
315
+ ```bash
316
+ pip install -e ".[server,gui]"
317
+ python -m apps.server --host 0.0.0.0 --port 8000 # web pages + API on :8000 (apps/web)
318
+ python -m apps.gui # PyQt5 launcher (1.0 GUI_launcher.py)
319
+ scripts/run_dwi.sh SUBJECT --all --gpu 0 --dry-run # 1.0 wrappers: print or run `ads ...`
320
+ ```
321
+
322
+ The server keeps the 1.0 endpoints (`/api/run` streams the log,
323
+ `/api/outputs`, `/api/download`, `/api/download-all`, `/api/upload-subject`,
324
+ `/api/example-subjects`) and adds `/api/stages` and a job API
325
+ (`/api/jobs`, `/api/jobs/{id}`, `/api/jobs/{id}/log`, `/api/jobs/{id}/stream`).
326
+ Docker images (GPU and CPU) and their entrypoint: [`docker/README.md`](docker/README.md).
327
+
328
+ ## Documentation
329
+
330
+ - [`docs/user_guide.md`](docs/user_guide.md): inputs, stages, outputs, GPU, troubleshooting.
331
+ - [`docs/configuration.md`](docs/configuration.md): every configuration key and default.
332
+ - [`docs/extending.md`](docs/extending.md): new methods, plugins, new stages, `openads.steps`.
333
+ - [`docs/architecture.md`](docs/architecture.md): layers, the data-import design, design decisions.
334
+ - [`docs/refactor/`](docs/refactor/): the 1.0 -> 2.0 refactor records (mapping,
335
+ ADRs, regression harness, API catalogue).
336
+ - [`CHANGELOG.md`](CHANGELOG.md).
337
+
338
+ ## License
339
+
340
+ JHU Non-Profit Research Software License (see [`LICENSE`](LICENSE)): non-profit
341
+ research use only; commercial use needs a separate license from Johns Hopkins
342
+ University.