brkraw 0.5.5__tar.gz → 0.5.7__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 (219) hide show
  1. {brkraw-0.5.5 → brkraw-0.5.7}/.github/workflows/release.yml +14 -7
  2. {brkraw-0.5.5 → brkraw-0.5.7}/.github/workflows/release_on_merge.yml +35 -10
  3. {brkraw-0.5.5 → brkraw-0.5.7}/.markdownlint.yaml +1 -0
  4. brkraw-0.5.7/.vscode/settings.json +5 -0
  5. brkraw-0.5.7/.vscode/tasks.json +86 -0
  6. {brkraw-0.5.5 → brkraw-0.5.7}/CITATION.cff +1 -1
  7. {brkraw-0.5.5 → brkraw-0.5.7}/PKG-INFO +3 -3
  8. {brkraw-0.5.5 → brkraw-0.5.7}/README.md +2 -2
  9. brkraw-0.5.7/RELEASE_NOTES.md +15 -0
  10. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/__init__.py +1 -1
  11. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/apps/addon/dependencies.py +84 -8
  12. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/apps/hook/core.py +21 -2
  13. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/apps/loader/core.py +3 -6
  14. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/apps/loader/info/scan.yaml +4 -0
  15. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/apps/loader/info/study.yaml +2 -2
  16. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/apps/loader/types.py +2 -2
  17. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/cli/commands/convert.py +12 -3
  18. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/cli/commands/hook.py +14 -1
  19. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/cli/commands/info.py +1 -1
  20. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/cli/commands/prune.py +1 -1
  21. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/cli/utils.py +11 -3
  22. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/resolver/affine.py +39 -6
  23. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/resolver/nifti.py +1 -1
  24. {brkraw-0.5.5 → brkraw-0.5.7}/docs/api/addon.md +12 -8
  25. brkraw-0.5.7/docs/api/convert.md +218 -0
  26. {brkraw-0.5.5 → brkraw-0.5.7}/docs/api/data-access.md +8 -2
  27. {brkraw-0.5.5 → brkraw-0.5.7}/docs/api/hook.md +38 -7
  28. {brkraw-0.5.5 → brkraw-0.5.7}/docs/api/info.md +22 -7
  29. {brkraw-0.5.5 → brkraw-0.5.7}/docs/api/layout.md +38 -12
  30. {brkraw-0.5.5 → brkraw-0.5.7}/docs/api/overview.md +27 -1
  31. {brkraw-0.5.5 → brkraw-0.5.7}/docs/api/prune.md +27 -29
  32. brkraw-0.5.7/docs/assets/gif/intro.gif +0 -0
  33. {brkraw-0.5.5 → brkraw-0.5.7}/docs/assets/zenodo_badge.svg +2 -2
  34. brkraw-0.5.7/docs/cli/addon.md +106 -0
  35. {brkraw-0.5.5 → brkraw-0.5.7}/docs/cli/cache.md +3 -0
  36. {brkraw-0.5.5 → brkraw-0.5.7}/docs/cli/config.md +1 -0
  37. brkraw-0.5.7/docs/cli/convert.md +276 -0
  38. brkraw-0.5.7/docs/cli/hook.md +129 -0
  39. {brkraw-0.5.5 → brkraw-0.5.7}/docs/cli/index.md +14 -0
  40. brkraw-0.5.7/docs/cli/info.md +75 -0
  41. brkraw-0.5.7/docs/cli/init.md +69 -0
  42. brkraw-0.5.7/docs/cli/params.md +79 -0
  43. {brkraw-0.5.5 → brkraw-0.5.7}/docs/cli/prune.md +12 -1
  44. brkraw-0.5.7/docs/cli/session.md +216 -0
  45. {brkraw-0.5.5 → brkraw-0.5.7}/docs/dev/cli-extensions.md +30 -5
  46. {brkraw-0.5.5 → brkraw-0.5.7}/docs/dev/contributing.md +4 -4
  47. {brkraw-0.5.5 → brkraw-0.5.7}/docs/dev/contributors.md +1 -1
  48. {brkraw-0.5.5 → brkraw-0.5.7}/docs/dev/documentation.md +5 -6
  49. {brkraw-0.5.5 → brkraw-0.5.7}/docs/dev/hook-packages.md +34 -17
  50. {brkraw-0.5.5 → brkraw-0.5.7}/docs/dev/roadmap.md +29 -30
  51. {brkraw-0.5.5/docs/reference → brkraw-0.5.7/docs/extensions}/addons-and-plugins.md +13 -0
  52. {brkraw-0.5.5/docs/reference → brkraw-0.5.7/docs/extensions}/cli-extensions.md +5 -1
  53. {brkraw-0.5.5/docs/reference → brkraw-0.5.7/docs/extensions}/context-map.md +4 -4
  54. {brkraw-0.5.5/docs/reference → brkraw-0.5.7/docs/extensions}/extensibility.md +3 -3
  55. {brkraw-0.5.5/docs/reference → brkraw-0.5.7/docs/extensions}/hook-packages.md +1 -1
  56. {brkraw-0.5.5/docs/reference → brkraw-0.5.7/docs/extensions}/layout.md +14 -11
  57. {brkraw-0.5.5/docs/reference → brkraw-0.5.7/docs/extensions}/rules.md +29 -21
  58. {brkraw-0.5.5/docs/reference → brkraw-0.5.7/docs/extensions}/specs.md +87 -41
  59. brkraw-0.5.7/docs/getting-started/api.md +217 -0
  60. brkraw-0.5.7/docs/getting-started/bids.md +126 -0
  61. brkraw-0.5.7/docs/getting-started/cli.md +214 -0
  62. brkraw-0.5.5/docs/getting-started/configuration.md → brkraw-0.5.7/docs/getting-started/config.md +6 -57
  63. brkraw-0.5.7/docs/getting-started/gui.md +25 -0
  64. brkraw-0.5.7/docs/getting-started/index.md +76 -0
  65. brkraw-0.5.7/docs/index.md +18 -0
  66. brkraw-0.5.7/docs/stylesheets/extra.css +69 -0
  67. {brkraw-0.5.5 → brkraw-0.5.7}/mkdocs.yml +19 -19
  68. {brkraw-0.5.5 → brkraw-0.5.7}/pyproject.toml +1 -0
  69. brkraw-0.5.7/tests/test_03_resolver/test_affine.py +75 -0
  70. brkraw-0.5.7/tests/test_apps/test_addon_dependencies.py +91 -0
  71. {brkraw-0.5.5 → brkraw-0.5.7}/tests/test_cli/test_hook_preset.py +13 -0
  72. brkraw-0.5.5/.vscode/settings.json +0 -4
  73. brkraw-0.5.5/.vscode/tasks.json +0 -86
  74. brkraw-0.5.5/RELEASE_NOTES.md +0 -17
  75. brkraw-0.5.5/docs/api/convert.md +0 -316
  76. brkraw-0.5.5/docs/cli/addon.md +0 -171
  77. brkraw-0.5.5/docs/cli/convert.md +0 -316
  78. brkraw-0.5.5/docs/cli/hook.md +0 -190
  79. brkraw-0.5.5/docs/cli/info.md +0 -126
  80. brkraw-0.5.5/docs/cli/session.md +0 -117
  81. brkraw-0.5.5/docs/getting-started/api.md +0 -148
  82. brkraw-0.5.5/docs/getting-started/backup.md +0 -10
  83. brkraw-0.5.5/docs/getting-started/bids.md +0 -19
  84. brkraw-0.5.5/docs/getting-started/cli.md +0 -136
  85. brkraw-0.5.5/docs/getting-started/hooks.md +0 -127
  86. brkraw-0.5.5/docs/getting-started/index.md +0 -89
  87. brkraw-0.5.5/docs/getting-started/mrs.md +0 -10
  88. brkraw-0.5.5/docs/getting-started/viewer.md +0 -15
  89. brkraw-0.5.5/docs/index.md +0 -101
  90. brkraw-0.5.5/docs/stylesheets/extra.css +0 -23
  91. brkraw-0.5.5/tests/test_03_resolver/test_affine.py +0 -32
  92. {brkraw-0.5.5 → brkraw-0.5.7}/.github/DISCUSSION_TEMPLATE/proposal.yml +0 -0
  93. {brkraw-0.5.5 → brkraw-0.5.7}/.github/DISCUSSION_TEMPLATE/question.yml +0 -0
  94. {brkraw-0.5.5 → brkraw-0.5.7}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  95. {brkraw-0.5.5 → brkraw-0.5.7}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  96. {brkraw-0.5.5 → brkraw-0.5.7}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  97. {brkraw-0.5.5 → brkraw-0.5.7}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  98. {brkraw-0.5.5 → brkraw-0.5.7}/.github/copilot-instructions.md +0 -0
  99. {brkraw-0.5.5 → brkraw-0.5.7}/.github/workflows/ci.yml +0 -0
  100. {brkraw-0.5.5 → brkraw-0.5.7}/.github/workflows/docs.yml +0 -0
  101. {brkraw-0.5.5 → brkraw-0.5.7}/.github/workflows/publish.yml +0 -0
  102. {brkraw-0.5.5 → brkraw-0.5.7}/.gitignore +0 -0
  103. {brkraw-0.5.5 → brkraw-0.5.7}/AGENTS.md +0 -0
  104. {brkraw-0.5.5 → brkraw-0.5.7}/CODE_OF_CONDUCT.md +0 -0
  105. {brkraw-0.5.5 → brkraw-0.5.7}/CONTRIBUTING.md +0 -0
  106. {brkraw-0.5.5 → brkraw-0.5.7}/LICENSE +0 -0
  107. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/api/__init__.py +0 -0
  108. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/api/types.py +0 -0
  109. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/apps/__init__.py +0 -0
  110. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/apps/addon/__init__.py +0 -0
  111. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/apps/addon/core.py +0 -0
  112. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/apps/addon/installation.py +0 -0
  113. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/apps/addon/io.py +0 -0
  114. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/apps/hook/__init__.py +0 -0
  115. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/apps/loader/__init__.py +0 -0
  116. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/apps/loader/formatter.py +0 -0
  117. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/apps/loader/helper.py +0 -0
  118. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/apps/loader/info/__init__.py +0 -0
  119. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/apps/loader/info/scan.py +0 -0
  120. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/apps/loader/info/study.py +0 -0
  121. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/apps/loader/info/transform.py +0 -0
  122. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/cli/__init__.py +0 -0
  123. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/cli/commands/__init__.py +0 -0
  124. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/cli/commands/addon.py +0 -0
  125. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/cli/commands/cache.py +0 -0
  126. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/cli/commands/config.py +0 -0
  127. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/cli/commands/init.py +0 -0
  128. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/cli/commands/params.py +0 -0
  129. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/cli/commands/session.py +0 -0
  130. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/cli/hook_args.py +0 -0
  131. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/cli/main.py +0 -0
  132. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/core/__init__.py +0 -0
  133. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/core/cache.py +0 -0
  134. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/core/config.py +0 -0
  135. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/core/entrypoints.py +0 -0
  136. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/core/formatter.py +0 -0
  137. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/core/fs.py +0 -0
  138. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/core/jcamp.py +0 -0
  139. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/core/layout.py +0 -0
  140. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/core/parameters.py +0 -0
  141. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/core/zip.py +0 -0
  142. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/dataclasses/__init__.py +0 -0
  143. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/dataclasses/node.py +0 -0
  144. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/dataclasses/reco.py +0 -0
  145. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/dataclasses/scan.py +0 -0
  146. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/dataclasses/study.py +0 -0
  147. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/default/__init__.py +0 -0
  148. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/default/pruner_specs/deid4share.yaml +0 -0
  149. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/default/rules/00_default.yaml +0 -0
  150. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/default/specs/metadata_dicom.yaml +0 -0
  151. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/default/specs/metadata_transforms.py +0 -0
  152. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/resolver/__init__.py +0 -0
  153. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/resolver/datatype.py +0 -0
  154. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/resolver/fid.py +0 -0
  155. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/resolver/helpers.py +0 -0
  156. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/resolver/image.py +0 -0
  157. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/resolver/shape.py +0 -0
  158. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/schema/__init__.py +0 -0
  159. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/schema/context_map.yaml +0 -0
  160. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/schema/meta.yaml +0 -0
  161. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/schema/niftiheader.yaml +0 -0
  162. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/schema/pruner.yaml +0 -0
  163. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/schema/remapper.yaml +0 -0
  164. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/schema/rules.yaml +0 -0
  165. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/specs/__init__.py +0 -0
  166. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/specs/hook/__init__.py +0 -0
  167. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/specs/hook/logic.py +0 -0
  168. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/specs/hook/validator.py +0 -0
  169. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/specs/meta/__init__.py +0 -0
  170. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/specs/meta/validator.py +0 -0
  171. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/specs/pruner/__init__.py +0 -0
  172. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/specs/pruner/logic.py +0 -0
  173. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/specs/pruner/validator.py +0 -0
  174. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/specs/remapper/__init__.py +0 -0
  175. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/specs/remapper/logic.py +0 -0
  176. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/specs/remapper/validator.py +0 -0
  177. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/specs/rules/__init__.py +0 -0
  178. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/specs/rules/logic.py +0 -0
  179. {brkraw-0.5.5 → brkraw-0.5.7}/brkraw/specs/rules/validator.py +0 -0
  180. {brkraw-0.5.5 → brkraw-0.5.7}/docs/assets/brkraw-logo-dark.svg +0 -0
  181. {brkraw-0.5.5 → brkraw-0.5.7}/docs/assets/brkraw-logo-light.svg +0 -0
  182. {brkraw-0.5.5 → brkraw-0.5.7}/docs/assets/brkraw-logo.svg +0 -0
  183. {brkraw-0.5.5 → brkraw-0.5.7}/docs/assets/favicon-16.png +0 -0
  184. {brkraw-0.5.5 → brkraw-0.5.7}/docs/assets/favicon-192.png +0 -0
  185. {brkraw-0.5.5 → brkraw-0.5.7}/docs/assets/favicon-32.png +0 -0
  186. {brkraw-0.5.5 → brkraw-0.5.7}/docs/assets/favicon-512.png +0 -0
  187. {brkraw-0.5.5 → brkraw-0.5.7}/docs/assets/favicon-dark.svg +0 -0
  188. {brkraw-0.5.5 → brkraw-0.5.7}/docs/assets/favicon-light.svg +0 -0
  189. {brkraw-0.5.5 → brkraw-0.5.7}/docs/assets/favicon.ico +0 -0
  190. {brkraw-0.5.5 → brkraw-0.5.7}/docs/assets/favicon.svg +0 -0
  191. {brkraw-0.5.5 → brkraw-0.5.7}/docs/assets/site.webmanifest +0 -0
  192. {brkraw-0.5.5 → brkraw-0.5.7}/docs/dev/core-vs-addon.md +0 -0
  193. {brkraw-0.5.5 → brkraw-0.5.7}/docs/overrides/404.html +0 -0
  194. {brkraw-0.5.5 → brkraw-0.5.7}/docs/overrides/partials/extrahead.html +0 -0
  195. {brkraw-0.5.5 → brkraw-0.5.7}/scripts/release_pr.py +10 -10
  196. {brkraw-0.5.5 → brkraw-0.5.7}/scripts/release_prep.py +0 -0
  197. {brkraw-0.5.5 → brkraw-0.5.7}/scripts/tag_and_push.py +0 -0
  198. {brkraw-0.5.5 → brkraw-0.5.7}/scripts/update_contributors.py +0 -0
  199. {brkraw-0.5.5 → brkraw-0.5.7}/scripts/update_readme_bibtex.py +0 -0
  200. {brkraw-0.5.5 → brkraw-0.5.7}/scripts/update_zenodo_badge.py +0 -0
  201. {brkraw-0.5.5 → brkraw-0.5.7}/tests/conftest.py +0 -0
  202. {brkraw-0.5.5 → brkraw-0.5.7}/tests/helpers.py +0 -0
  203. {brkraw-0.5.5 → brkraw-0.5.7}/tests/test_01_core/__init__.py +0 -0
  204. {brkraw-0.5.5 → brkraw-0.5.7}/tests/test_01_core/fixtures/acqp_EPI_pv5_1.jdx +0 -0
  205. {brkraw-0.5.5 → brkraw-0.5.7}/tests/test_01_core/fixtures/method_EPI_pv5_1.jdx +0 -0
  206. {brkraw-0.5.5 → brkraw-0.5.7}/tests/test_01_core/fixtures/method_FLASH_pv360_3_1.jdx +0 -0
  207. {brkraw-0.5.5 → brkraw-0.5.7}/tests/test_01_core/test_formatter.py +0 -0
  208. {brkraw-0.5.5 → brkraw-0.5.7}/tests/test_01_core/test_fs.py +0 -0
  209. {brkraw-0.5.5 → brkraw-0.5.7}/tests/test_01_core/test_jcamp.py +0 -0
  210. {brkraw-0.5.5 → brkraw-0.5.7}/tests/test_01_core/test_parameters.py +0 -0
  211. {brkraw-0.5.5 → brkraw-0.5.7}/tests/test_01_core/test_resources.py +0 -0
  212. {brkraw-0.5.5 → brkraw-0.5.7}/tests/test_01_core/test_zip.py +0 -0
  213. {brkraw-0.5.5 → brkraw-0.5.7}/tests/test_02_dataclasses/test_dir-vs-zipped.py +0 -0
  214. {brkraw-0.5.5 → brkraw-0.5.7}/tests/test_04_specs/test_context_map_cases.py +0 -0
  215. {brkraw-0.5.5 → brkraw-0.5.7}/tests/test_04_specs/test_remapper.py +0 -0
  216. {brkraw-0.5.5 → brkraw-0.5.7}/tests/test_apps/test_affine_post_transform.py +0 -0
  217. {brkraw-0.5.5 → brkraw-0.5.7}/tests/test_apps/test_loader_info_missing_visu_pars.py +0 -0
  218. {brkraw-0.5.5 → brkraw-0.5.7}/tests/test_cli/test_hook_args_yaml.py +0 -0
  219. {brkraw-0.5.5 → brkraw-0.5.7}/tests/test_scripts/test_update_contributors.py +0 -0
@@ -14,6 +14,8 @@ on:
14
14
  permissions:
15
15
  contents: write
16
16
  discussions: write
17
+ id-token: write
18
+ actions: write
17
19
 
18
20
  jobs:
19
21
  release:
@@ -148,21 +150,26 @@ jobs:
148
150
  echo "Zenodo badge update attempt $i/10"
149
151
  python3 scripts/update_zenodo_badge.py --up-to-date-exit-code 2
150
152
  exit_code=$?
153
+ set -e
154
+
151
155
  if [ "$exit_code" -eq 0 ]; then
152
156
  echo "Zenodo badge updated."
153
157
  exit 0
154
158
  fi
159
+
155
160
  if [ "$exit_code" -ne 2 ]; then
156
161
  exit "$exit_code"
157
162
  fi
163
+
158
164
  sleep 60
159
165
  done
166
+
160
167
  echo "Zenodo badge still up to date after 10 attempts; giving up."
161
168
 
162
- publish:
163
- if: ${{ github.repository == 'BrkRaw/brkraw' && (needs.release.outputs.prerelease == 'true' || needs.release.outputs.zenodo_success == 'true') }}
164
- needs: [release]
165
- uses: ./.github/workflows/publish.yml
166
- with:
167
- target: ${{ needs.release.outputs.prerelease == 'true' && 'testpypi' || 'pypi' }}
168
- tag: ${{ needs.release.outputs.tag }}
169
+ - name: Trigger publish workflow
170
+ env:
171
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
172
+ run: |
173
+ target="${{ steps.prerelease.outputs.value == 'true' && 'testpypi' || 'pypi' }}"
174
+ tag="${{ steps.tag.outputs.value }}"
175
+ gh workflow run publish.yml -f target="$target" -f tag="$tag"
@@ -3,9 +3,23 @@ name: Release On Merge
3
3
  on:
4
4
  push:
5
5
  branches: [main]
6
+ workflow_dispatch:
7
+ inputs:
8
+ tag:
9
+ description: "Tag to create and push (e.g., v0.4.2)"
10
+ required: true
11
+ run_release:
12
+ description: "Trigger release workflow after tagging"
13
+ required: false
14
+ default: "true"
15
+ type: choice
16
+ options:
17
+ - "true"
18
+ - "false"
6
19
 
7
20
  permissions:
8
21
  contents: write
22
+ actions: write
9
23
 
10
24
  jobs:
11
25
  tag_release:
@@ -28,12 +42,6 @@ jobs:
28
42
  # Default outputs
29
43
  echo "changed=false" >> "$GITHUB_OUTPUT"
30
44
 
31
- # Only proceed for merge commits ("Merge pull request ...")
32
- if [[ "${{ github.event.head_commit.message }}" != Merge\ pull\ request* ]]; then
33
- echo "Not a merge commit. Skipping."
34
- exit 0
35
- fi
36
-
37
45
  # Find the PR associated with this merge commit and ensure it has the 'release' label.
38
46
  api="https://api.github.com/repos/${REPO}/commits/${SHA}/pulls"
39
47
  pr_json=$(curl -fsSL \
@@ -58,7 +66,8 @@ jobs:
58
66
  )
59
67
 
60
68
  if [[ -z "${pr_number}" ]]; then
61
- echo "No PR associated with ${SHA}. Skipping."
69
+ echo "No PR associated with ${SHA}. Skipping."
70
+ echo "This can happen for direct pushes, API failures, or insufficient token scopes."
62
71
  exit 0
63
72
  fi
64
73
 
@@ -78,7 +87,8 @@ jobs:
78
87
  )
79
88
 
80
89
  if [[ "${has_release}" != "true" ]]; then
81
- echo "PR #${pr_number} does not have 'release' label. Skipping."
90
+ echo "PR #${pr_number} does not have 'release' label. Skipping."
91
+ echo "If this was a squash/rebase merge, ensure the PR is labeled before merging."
82
92
  exit 0
83
93
  fi
84
94
 
@@ -106,13 +116,28 @@ jobs:
106
116
  echo "changed=true" >> "$GITHUB_OUTPUT"
107
117
  echo "value=$version" >> "$GITHUB_OUTPUT"
108
118
  shell: bash
119
+ - name: Manual tag inputs
120
+ id: manual
121
+ if: ${{ github.event_name == 'workflow_dispatch' }}
122
+ run: |
123
+ set -euo pipefail
124
+ echo "changed=true" >> "$GITHUB_OUTPUT"
125
+ echo "value=${{ inputs.tag }}" >> "$GITHUB_OUTPUT"
126
+ echo "run_release=${{ inputs.run_release }}" >> "$GITHUB_OUTPUT"
109
127
  - name: Create and push tag
110
- if: ${{ steps.version.outputs.changed == 'true' }}
128
+ if: ${{ steps.version.outputs.changed == 'true' || steps.manual.outputs.changed == 'true' }}
111
129
  run: |
112
- tag="${{ steps.version.outputs.value }}"
130
+ tag="${{ steps.manual.outputs.value || steps.version.outputs.value }}"
113
131
  if git rev-parse "$tag" >/dev/null 2>&1; then
114
132
  echo "Tag $tag already exists. Skipping."
115
133
  exit 0
116
134
  fi
117
135
  git tag "$tag" "${{ github.sha }}"
118
136
  git push origin "$tag"
137
+ - name: Trigger release workflow
138
+ if: ${{ (steps.version.outputs.changed == 'true') || (steps.manual.outputs.changed == 'true' && steps.manual.outputs.run_release == 'true') }}
139
+ env:
140
+ GH_TOKEN: ${{ github.token }}
141
+ run: |
142
+ tag="${{ steps.manual.outputs.value || steps.version.outputs.value }}"
143
+ gh workflow run release.yml -f tag="$tag"
@@ -4,3 +4,4 @@ MD013:
4
4
  tables: false
5
5
  MD022: false
6
6
  MD032: false
7
+ MD033: false
@@ -0,0 +1,5 @@
1
+ {
2
+ "python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python",
3
+ "python.terminal.activateEnvironment": true,
4
+ "terminal.integrated.defaultProfile.osx": "zsh"
5
+ }
@@ -0,0 +1,86 @@
1
+ {
2
+ "version": "2.0.0",
3
+ "inputs": [
4
+ {
5
+ "id": "releaseVersion",
6
+ "type": "promptString",
7
+ "description": "Release version (e.g., 1.0.0)"
8
+ }
9
+ ],
10
+ "tasks": [
11
+ {
12
+ "label": "Standard: Setup venv + deps",
13
+ "type": "shell",
14
+ "command": "\"${workspaceFolder}/.venv/bin/python\" -m venv .venv && \"${workspaceFolder}/.venv/bin/python\" -m pip install -U pip && \"${workspaceFolder}/.venv/bin/python\" -m pip install -e \".[dev]\"",
15
+ "windows": {
16
+ "command": "\"${workspaceFolder}\\.venv\\Scripts\\python\" -m venv .venv; \"${workspaceFolder}\\.venv\\Scripts\\python\" -m pip install -U pip; \"${workspaceFolder}\\.venv\\Scripts\\python\" -m pip install -e \".[dev]\"",
17
+ "options": {
18
+ "shell": {
19
+ "executable": "pwsh"
20
+ }
21
+ }
22
+ },
23
+ "group": "build",
24
+ "problemMatcher": []
25
+ },
26
+ {
27
+ "label": "Standard: Release Prep PR (2-step)",
28
+ "type": "shell",
29
+ "command": "\"${workspaceFolder}/.venv/bin/python\" scripts/release_pr.py --version ${input:releaseVersion} --base upstream/main --remote-upstream upstream --remote-origin origin",
30
+ "group": "build",
31
+ "problemMatcher": []
32
+ },
33
+ {
34
+ "label": "DryRun: Release Prep PR (2-step)",
35
+ "type": "shell",
36
+ "command": "\"${workspaceFolder}/.venv/bin/python\" scripts/release_pr.py --dry-run --version ${input:releaseVersion} --base upstream/main --remote-upstream upstream --remote-origin origin",
37
+ "group": "build",
38
+ "problemMatcher": []
39
+ },
40
+ {
41
+ "label": "Standard: MkDocs Serve",
42
+ "type": "shell",
43
+ "command": "\"${workspaceFolder}/.venv/bin/python\" -m mkdocs serve",
44
+ "group": "build",
45
+ "problemMatcher": []
46
+ },
47
+ {
48
+ "label": "Optional: Release Prep (bump + notes)",
49
+ "type": "shell",
50
+ "command": "\"${workspaceFolder}/.venv/bin/python\" scripts/update_contributors.py --source github --repo brkraw/brkraw --output docs/dev/contributors.md && \"${workspaceFolder}/.venv/bin/python\" scripts/release_prep.py --version ${input:releaseVersion} --fetch-tags --remote upstream",
51
+ "windows": {
52
+ "command": "pwsh -NoProfile -Command \"& \\\"${workspaceFolder}\\.venv\\Scripts\\python\\\" scripts/update_contributors.py --source github --repo brkraw/brkraw --output docs/dev/contributors.md; if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }; & \\\"${workspaceFolder}\\.venv\\Scripts\\python\\\" scripts/release_prep.py --version ${input:releaseVersion} --fetch-tags --remote upstream\""
53
+ },
54
+ "group": "build",
55
+ "problemMatcher": []
56
+ },
57
+ {
58
+ "label": "Optional: Release Notes Only",
59
+ "type": "shell",
60
+ "command": "\"${workspaceFolder}/.venv/bin/python\" scripts/release_notes.py --fetch-tags --remote upstream",
61
+ "group": "build",
62
+ "problemMatcher": []
63
+ },
64
+ {
65
+ "label": "Optional: Release Tag + Push (upstream)",
66
+ "type": "shell",
67
+ "command": "\"${workspaceFolder}/.venv/bin/python\" scripts/tag_and_push.py",
68
+ "group": "build",
69
+ "problemMatcher": []
70
+ },
71
+ {
72
+ "label": "Optional: Docs Update Contributors (git)",
73
+ "type": "shell",
74
+ "command": "\"${workspaceFolder}/.venv/bin/python\" scripts/update_contributors.py --source git --output docs/dev/contributors.md",
75
+ "group": "build",
76
+ "problemMatcher": []
77
+ },
78
+ {
79
+ "label": "Optional: Docs Update Contributors (GitHub API)",
80
+ "type": "shell",
81
+ "command": "\"${workspaceFolder}/.venv/bin/python\" scripts/update_contributors.py --source github --repo brkraw/brkraw --output docs/dev/contributors.md",
82
+ "group": "build",
83
+ "problemMatcher": []
84
+ }
85
+ ]
86
+ }
@@ -34,4 +34,4 @@ keywords:
34
34
  - BIDS
35
35
  - neuroimaging
36
36
 
37
- version: "0.5.5"
37
+ version: "0.5.7"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: brkraw
3
- Version: 0.5.5
3
+ Version: 0.5.7
4
4
  Summary: Toolkit for loading Bruker Paravision datasets, mapping metadata, and exporting NIfTI
5
5
  Project-URL: Homepage, https://brkraw.github.io
6
6
  Maintainer-email: SungHo Lee <shlee@unc.edu>
@@ -49,7 +49,7 @@ Description-Content-Type: text/markdown
49
49
 
50
50
  A modular toolkit for Bruker MRI raw-data handling.
51
51
 
52
- BrkRaw (v0.5.5) converts raw data into standardized, neuroimaging-ready
52
+ BrkRaw (v0.5.7) converts raw data into standardized, neuroimaging-ready
53
53
  datasets, with extensible rules/specs and plugin hooks.
54
54
 
55
55
  - Documentation: [brkraw.github.io](https://brkraw.github.io/)
@@ -70,7 +70,7 @@ If you use BrkRaw in your research, please cite it.
70
70
  @software{brkraw,
71
71
  author = {Lee, Sung-Ho and Devenyi, Gabriel A. and Ban, Woomi and Shih, Yen-Yu Ian},
72
72
  title = {BrkRaw: A modular toolkit for Bruker MRI raw-data handling},
73
- version = {0.5.5},
73
+ version = {0.5.7},
74
74
  doi = {10.5281/zenodo.3818614},
75
75
  url = {https://github.com/BrkRaw/brkraw},
76
76
  note = {Documentation: https://brkraw.github.io},
@@ -10,7 +10,7 @@
10
10
 
11
11
  A modular toolkit for Bruker MRI raw-data handling.
12
12
 
13
- BrkRaw (v0.5.5) converts raw data into standardized, neuroimaging-ready
13
+ BrkRaw (v0.5.7) converts raw data into standardized, neuroimaging-ready
14
14
  datasets, with extensible rules/specs and plugin hooks.
15
15
 
16
16
  - Documentation: [brkraw.github.io](https://brkraw.github.io/)
@@ -31,7 +31,7 @@ If you use BrkRaw in your research, please cite it.
31
31
  @software{brkraw,
32
32
  author = {Lee, Sung-Ho and Devenyi, Gabriel A. and Ban, Woomi and Shih, Yen-Yu Ian},
33
33
  title = {BrkRaw: A modular toolkit for Bruker MRI raw-data handling},
34
- version = {0.5.5},
34
+ version = {0.5.7},
35
35
  doi = {10.5281/zenodo.3818614},
36
36
  url = {https://github.com/BrkRaw/brkraw},
37
37
  note = {Documentation: https://brkraw.github.io},
@@ -0,0 +1,15 @@
1
+ # Release v0.5.7
2
+
3
+ Date: 2026-02-03
4
+ Changes since 0.5.6
5
+
6
+ - docs: release notes for v0.5.7 (0fbaf73)
7
+ - chore: prepare release v0.5.7 (3e98477)
8
+ - docs: update contributors (3f18c3b)
9
+ - doc: markdown lint (2fde999)
10
+ - Fix hook spec documentation (5c271e1)
11
+ - update (dev): CI workflow / dev configurations (502c98a)
12
+ - patch(info_spec): added missing slice info (13fbc3e)
13
+ - fix(cli): ensure to make outout directory during convertion (6dfb153)
14
+ - Fix hook uninstall deps, affine slicepacks, and info stdout (da01975)
15
+ - fix (spec): typo in Study info spec (ef6c980)
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = '0.5.5'
3
+ __version__ = '0.5.7'
4
4
  from .apps.loader import BrukerLoader
5
5
 
6
6
 
@@ -17,11 +17,23 @@ RULE_KEYS = {"info_spec", "metadata_spec", "converter_hook"}
17
17
  _SPEC_EXTS = (".yaml", ".yml")
18
18
 
19
19
 
20
- def warn_dependencies(target: Path, *, kind: str, root: Optional[Union[str, Path]]) -> bool:
20
+ def warn_dependencies(
21
+ target: Path,
22
+ *,
23
+ kind: str,
24
+ root: Optional[Union[str, Path]],
25
+ ignore_rules_dir: Optional[Path] = None,
26
+ ignore_specs_dir: Optional[Path] = None,
27
+ ) -> bool:
21
28
  paths = config_core.paths(root=root)
22
29
  warned = False
23
30
  if kind == "spec":
24
- used_by_rules = rules_using_spec(target.name, paths.rules_dir)
31
+ used_by_rules = rules_using_spec(
32
+ target,
33
+ paths.rules_dir,
34
+ root=paths.root,
35
+ ignore_dir=ignore_rules_dir,
36
+ )
25
37
  if used_by_rules:
26
38
  logger.warning(
27
39
  "Spec %s is referenced by rules: %s",
@@ -29,7 +41,11 @@ def warn_dependencies(target: Path, *, kind: str, root: Optional[Union[str, Path
29
41
  ", ".join(sorted(used_by_rules)),
30
42
  )
31
43
  warned = True
32
- included_by = specs_including_spec(target.name, paths.specs_dir)
44
+ included_by = specs_including_spec(
45
+ target,
46
+ paths.specs_dir,
47
+ ignore_dir=ignore_specs_dir,
48
+ )
33
49
  if included_by:
34
50
  logger.warning(
35
51
  "Spec %s is included by: %s",
@@ -54,12 +70,34 @@ def warn_dependencies(target: Path, *, kind: str, root: Optional[Union[str, Path
54
70
  return warned
55
71
 
56
72
 
57
- def rules_using_spec(spec_name: str, rules_dir: Path) -> Set[str]:
73
+ def _is_under(path: Path, base: Optional[Path]) -> bool:
74
+ if base is None:
75
+ return False
76
+ try:
77
+ path.relative_to(base)
78
+ except ValueError:
79
+ return False
80
+ return True
81
+
82
+
83
+ def rules_using_spec(
84
+ target: Union[str, Path],
85
+ rules_dir: Path,
86
+ *,
87
+ root: Optional[Union[str, Path]] = None,
88
+ ignore_dir: Optional[Path] = None,
89
+ ) -> Set[str]:
58
90
  used_by: Set[str] = set()
59
91
  if not rules_dir.exists():
60
92
  return used_by
93
+ if isinstance(target, Path):
94
+ target_path = target.resolve()
95
+ else:
96
+ target_path = None
61
97
  files = list(rules_dir.rglob("*.yaml")) + list(rules_dir.rglob("*.yml"))
62
98
  for path in files:
99
+ if _is_under(path, ignore_dir):
100
+ continue
63
101
  data = yaml.safe_load(path.read_text(encoding="utf-8"))
64
102
  if not isinstance(data, dict):
65
103
  continue
@@ -70,17 +108,46 @@ def rules_using_spec(spec_name: str, rules_dir: Path) -> Set[str]:
70
108
  if not isinstance(item, dict):
71
109
  continue
72
110
  use = item.get("use")
73
- if isinstance(use, str) and Path(use).name == spec_name:
111
+ if not isinstance(use, str):
112
+ continue
113
+ if target_path is None:
114
+ if Path(use).name == str(target):
115
+ used_by.add(path.name)
116
+ continue
117
+ if root is None:
118
+ continue
119
+ version = item.get("version") if isinstance(item.get("version"), str) else None
120
+ try:
121
+ resolved = resolve_spec_reference(
122
+ use,
123
+ category=key,
124
+ version=version,
125
+ root=root,
126
+ )
127
+ except Exception:
128
+ continue
129
+ if resolved.resolve() == target_path:
74
130
  used_by.add(path.name)
75
131
  return used_by
76
132
 
77
133
 
78
- def specs_including_spec(spec_name: str, specs_dir: Path) -> Set[str]:
134
+ def specs_including_spec(
135
+ target: Union[str, Path],
136
+ specs_dir: Path,
137
+ *,
138
+ ignore_dir: Optional[Path] = None,
139
+ ) -> Set[str]:
79
140
  included_by: Set[str] = set()
80
141
  if not specs_dir.exists():
81
142
  return included_by
143
+ if isinstance(target, Path):
144
+ target_path = target.resolve()
145
+ else:
146
+ target_path = None
82
147
  files = list(specs_dir.rglob("*.yaml")) + list(specs_dir.rglob("*.yml"))
83
148
  for path in files:
149
+ if _is_under(path, ignore_dir):
150
+ continue
84
151
  data = yaml.safe_load(path.read_text(encoding="utf-8"))
85
152
  if not isinstance(data, dict):
86
153
  continue
@@ -92,8 +159,17 @@ def specs_including_spec(spec_name: str, specs_dir: Path) -> Set[str]:
92
159
  include_list = [include]
93
160
  elif isinstance(include, list) and all(isinstance(item, str) for item in include):
94
161
  include_list = include
95
- if any(Path(item).name == spec_name for item in include_list):
96
- included_by.add(path.name)
162
+ for item in include_list:
163
+ inc_path = Path(item)
164
+ if not inc_path.is_absolute():
165
+ inc_path = (path.parent / inc_path).resolve()
166
+ if target_path is None:
167
+ if inc_path.name == str(target):
168
+ included_by.add(path.name)
169
+ break
170
+ elif inc_path == target_path:
171
+ included_by.add(path.name)
172
+ break
97
173
  return included_by
98
174
 
99
175
 
@@ -150,12 +150,18 @@ def uninstall_hook(
150
150
  "transforms": [],
151
151
  }
152
152
  root_path = config_core.resolve_root(root)
153
+ namespace = entry.get("namespace") if isinstance(entry, dict) else None
153
154
  for kind in ("specs", "pruner_specs", "rules", "transforms"):
154
155
  for relpath in entry.get(kind, []) if isinstance(entry, dict) else []:
155
156
  target_path = root_path / relpath
156
157
  if not target_path.exists():
157
158
  continue
158
- if _has_dependencies(target_path, kind, root=root_path) and not force:
159
+ if _has_dependencies(
160
+ target_path,
161
+ kind,
162
+ root=root_path,
163
+ namespace=namespace,
164
+ ) and not force:
159
165
  raise RuntimeError("Dependencies found; use --force to remove.")
160
166
  target_path.unlink()
161
167
  removed[kind].append(relpath)
@@ -587,9 +593,22 @@ def _has_dependencies(
587
593
  kind: str,
588
594
  *,
589
595
  root: Optional[Union[str, Path]],
596
+ namespace: Optional[str] = None,
590
597
  ) -> bool:
591
598
  try:
592
- return addon_deps.warn_dependencies(target, kind=_kind_to_remove(kind), root=root)
599
+ ignore_rules_dir = None
600
+ ignore_specs_dir = None
601
+ if namespace:
602
+ paths = config_core.paths(root=root)
603
+ ignore_rules_dir = paths.rules_dir / namespace
604
+ ignore_specs_dir = paths.specs_dir / namespace
605
+ return addon_deps.warn_dependencies(
606
+ target,
607
+ kind=_kind_to_remove(kind),
608
+ root=root,
609
+ ignore_rules_dir=ignore_rules_dir,
610
+ ignore_specs_dir=ignore_specs_dir,
611
+ )
593
612
  except Exception:
594
613
  return False
595
614
 
@@ -565,7 +565,6 @@ class BrukerLoader:
565
565
 
566
566
  if scope == 'scan':
567
567
  if not as_dict:
568
- config_core.configure_logging(root=base, stream=sys.stdout)
569
568
  text = format_info_tables(
570
569
  {"Scan(s)": scan_info},
571
570
  width=width,
@@ -574,7 +573,7 @@ class BrukerLoader:
574
573
  scan_transpose=scan_transpose,
575
574
  float_decimals=float_decimals,
576
575
  )
577
- logger.info("%s", text)
576
+ print(text)
578
577
  return None
579
578
  return scan_info
580
579
 
@@ -586,19 +585,17 @@ class BrukerLoader:
586
585
 
587
586
  if scope == 'study':
588
587
  if not as_dict:
589
- config_core.configure_logging(root=base, stream=sys.stdout)
590
588
  text = format_info_tables(
591
589
  study_info,
592
590
  width=width,
593
591
  float_decimals=float_decimals,
594
592
  )
595
- logger.info("%s", text)
593
+ print(text)
596
594
  return None
597
595
  return study_info
598
596
 
599
597
  study_info['Scan(s)'] = scan_info
600
598
  if not as_dict:
601
- config_core.configure_logging(root=base, stream=sys.stdout)
602
599
  text = format_info_tables(
603
600
  study_info,
604
601
  width=width,
@@ -607,7 +604,7 @@ class BrukerLoader:
607
604
  scan_transpose=scan_transpose,
608
605
  float_decimals=float_decimals,
609
606
  )
610
- logger.info("%s", text)
607
+ print(text)
611
608
  return None
612
609
  return study_info
613
610
 
@@ -56,6 +56,10 @@ FOV (mm):
56
56
  - file: method
57
57
  key: PVM_Fov
58
58
  transform: convert_to_list
59
+ Num Slices:
60
+ sources:
61
+ - file: acqp
62
+ key: NSLICES
59
63
  NumSlicePack:
60
64
  sources:
61
65
  - file: method
@@ -5,7 +5,7 @@ study:
5
5
  description: "Study-level info mapping for Bruker datasets."
6
6
  category: "info_spec"
7
7
  transforms_source: "transform.py"
8
- Study.Opperator:
8
+ Study.Operator:
9
9
  sources:
10
10
  - file: subject
11
11
  key: SUBJECT_referral
@@ -91,7 +91,7 @@ scan:
91
91
  description: "Scan-level fallback info mapping for Bruker datasets."
92
92
  category: "info_spec"
93
93
  transforms_source: "transform.py"
94
- Study.Opperator:
94
+ Study.Operator:
95
95
  sources:
96
96
  - file: visu_pars
97
97
  key: VisuStudyReferringPhysician
@@ -28,10 +28,10 @@ if TYPE_CHECKING:
28
28
  from ...resolver.nifti import Nifti1HeaderContents, XYZUNIT, TUNIT
29
29
 
30
30
 
31
- InfoScope = Literal['full', 'study', 'scan']
31
+ InfoScope: TypeAlias = Literal['full', 'study', 'scan']
32
32
  Dataobjs = Optional[Union[NDArray, Tuple[NDArray, ...]]]
33
33
  Affines = Optional[Union[NDArray, Tuple[NDArray, ...]]]
34
- AffineSpace = Literal["raw", "scanner", "subject_ras"]
34
+ AffineSpace: TypeAlias = Literal["raw", "scanner", "subject_ras"]
35
35
  ConvertedObj = Optional[Union["ToFilename", Tuple["ToFilename", ...]]]
36
36
  Metadata = Optional[Union[Dict, Tuple[Optional[Dict], ...]]]
37
37
  HookArgs = Optional[Mapping[str, Mapping[str, Any]]]
@@ -12,7 +12,7 @@ import logging
12
12
  import os
13
13
  import re
14
14
  from pathlib import Path
15
- from typing import Any, Mapping, Optional, Dict, List, Tuple, cast, get_args
15
+ from typing import Any, Mapping, Optional, Dict, List, Tuple, Sequence, cast, get_args
16
16
 
17
17
  import numpy as np
18
18
  from brkraw.cli.utils import load
@@ -387,6 +387,8 @@ def cmd_convert(args: argparse.Namespace) -> int:
387
387
  for path in output_paths:
388
388
  reserved_paths.add(path)
389
389
 
390
+ _ensure_output_dirs(output_paths)
391
+
390
392
  sidecar_meta = None
391
393
  if args.sidecar:
392
394
  sidecar_meta = loader.get_metadata(
@@ -397,12 +399,10 @@ def cmd_convert(args: argparse.Namespace) -> int:
397
399
 
398
400
  if args.no_convert:
399
401
  for path in output_paths:
400
- path.parent.mkdir(parents=True, exist_ok=True)
401
402
  _write_sidecar(path, sidecar_meta)
402
403
  total_written += 1
403
404
  else:
404
405
  for path, obj in zip(output_paths, nii_list):
405
- path.parent.mkdir(parents=True, exist_ok=True)
406
406
  obj.to_filename(str(path))
407
407
  logger.info("Wrote NIfTI: %s", path)
408
408
  total_written += 1
@@ -713,6 +713,15 @@ def _parse_hook_args(values: List[str]) -> Dict[str, Dict[str, Any]]:
713
713
  return parsed
714
714
 
715
715
 
716
+ def _ensure_output_dirs(paths: Sequence[Path]) -> None:
717
+ for path in paths:
718
+ try:
719
+ path.parent.mkdir(parents=True, exist_ok=True)
720
+ except OSError as exc:
721
+ logger.error("Failed to create output directory %s: %s", path.parent, exc)
722
+ raise
723
+
724
+
716
725
  def _uses_counter_tag(
717
726
  *,
718
727
  layout_template: Optional[str],
@@ -274,7 +274,8 @@ def cmd_preset(args: argparse.Namespace) -> int:
274
274
  return 2
275
275
  preset = _infer_hook_preset(entry)
276
276
  payload = {"hooks": {args.target: preset}}
277
- text = yaml.safe_dump(payload, sort_keys=False)
277
+ normalized = _normalize_yaml_payload(payload)
278
+ text = yaml.safe_dump(normalized, sort_keys=False)
278
279
  if args.output:
279
280
  Path(args.output).expanduser().write_text(text, encoding="utf-8")
280
281
  logger.info("Wrote preset: %s", args.output)
@@ -283,6 +284,18 @@ def cmd_preset(args: argparse.Namespace) -> int:
283
284
  return 0
284
285
 
285
286
 
287
+ def _normalize_yaml_payload(value: Any) -> Any:
288
+ if isinstance(value, Path):
289
+ return str(value)
290
+ if dataclasses.is_dataclass(value) and not isinstance(value, type):
291
+ return _normalize_yaml_payload(dataclasses.asdict(value))
292
+ if isinstance(value, dict):
293
+ return {key: _normalize_yaml_payload(item) for key, item in value.items()}
294
+ if isinstance(value, (list, tuple, set)):
295
+ return [_normalize_yaml_payload(item) for item in value]
296
+ return value
297
+
298
+
286
299
  def register(subparsers: argparse._SubParsersAction) -> None: # type: ignore[name-defined]
287
300
  hook_parser = subparsers.add_parser(
288
301
  "hook",
@@ -39,7 +39,7 @@ def cmd_info(args: argparse.Namespace) -> int:
39
39
  width=width,
40
40
  )
41
41
  if text is not None:
42
- logger.info("%s", text)
42
+ print(text)
43
43
  return 0
44
44
 
45
45