dfc-kit 1.0.2__tar.gz → 1.0.3__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 (182) hide show
  1. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/CHANGELOG.md +13 -0
  2. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/CITATION.cff +2 -2
  3. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/PKG-INFO +1 -1
  4. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/docs/api.md +4 -3
  5. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/docs/cli.md +23 -0
  6. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/docs/state_alignment.md +2 -1
  7. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/docs/states.md +38 -14
  8. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/docs/storage.md +31 -16
  9. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/pyproject.toml +1 -1
  10. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfc_kit.egg-info/PKG-INFO +1 -1
  11. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfc_kit.egg-info/SOURCES.txt +1 -0
  12. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/__init__.py +1 -1
  13. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/_preprocessing.py +34 -3
  14. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/commands/parser.py +4 -0
  15. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/commands/source.py +1 -0
  16. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/states/__init__.py +2 -1
  17. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/states/alignment.py +2 -3
  18. dfc_kit-1.0.3/src/dfckit/states/cap.py +124 -0
  19. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/storage/builders.py +13 -7
  20. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/integration/test_xcpd_state_pipeline.py +59 -0
  21. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/regression/test_nbs_reference.py +5 -3
  22. dfc_kit-1.0.3/tests/unit/test_cap.py +230 -0
  23. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_states.py +4 -3
  24. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_storage.py +25 -3
  25. dfc_kit-1.0.2/src/dfckit/states/cap.py +0 -50
  26. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/CONTRIBUTING.md +0 -0
  27. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/LICENSE +0 -0
  28. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/MANIFEST.in +0 -0
  29. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/README.md +0 -0
  30. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/benchmarks/README.md +0 -0
  31. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/benchmarks/profile_core.py +0 -0
  32. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/benchmarks/profile_information.py +0 -0
  33. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/docs/architecture.md +0 -0
  34. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/docs/correlation.md +0 -0
  35. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/docs/datasets.md +0 -0
  36. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/docs/getting_started.md +0 -0
  37. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/docs/granger.md +0 -0
  38. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/docs/group_inference.md +0 -0
  39. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/docs/hmm.md +0 -0
  40. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/docs/index.md +0 -0
  41. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/docs/inference.md +0 -0
  42. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/docs/inference_matching.md +0 -0
  43. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/docs/information.md +0 -0
  44. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/docs/instantaneous_edges.md +0 -0
  45. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/docs/leida.md +0 -0
  46. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/docs/lowrank.md +0 -0
  47. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/docs/method_inventory.md +0 -0
  48. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/docs/model_artifacts.md +0 -0
  49. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/docs/nbs.md +0 -0
  50. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/docs/partition_graphs.md +0 -0
  51. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/docs/reference.md +0 -0
  52. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/docs/release.md +0 -0
  53. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/docs/state_scoring.md +0 -0
  54. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/docs/state_selection.md +0 -0
  55. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/docs/state_stability.md +0 -0
  56. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/docs/tutorial_xcpd_to_states.md +0 -0
  57. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/docs/xcpd_input.md +0 -0
  58. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/mkdocs.yml +0 -0
  59. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/setup.cfg +0 -0
  60. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfc_kit.egg-info/dependency_links.txt +0 -0
  61. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfc_kit.egg-info/entry_points.txt +0 -0
  62. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfc_kit.egg-info/requires.txt +0 -0
  63. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfc_kit.egg-info/top_level.txt +0 -0
  64. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/_arrays.py +0 -0
  65. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/_feature_selection.py +0 -0
  66. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/_validation.py +0 -0
  67. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/artifacts/__init__.py +0 -0
  68. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/artifacts/_fields.py +0 -0
  69. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/artifacts/_json.py +0 -0
  70. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/artifacts/_numpy.py +0 -0
  71. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/artifacts/models.py +0 -0
  72. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/artifacts/state_alignment.py +0 -0
  73. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/artifacts/state_results.py +0 -0
  74. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/artifacts/state_scoring.py +0 -0
  75. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/artifacts/state_stability.py +0 -0
  76. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/cli.py +0 -0
  77. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/commands/__init__.py +0 -0
  78. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/commands/reporting.py +0 -0
  79. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/commands/stability.py +0 -0
  80. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/commands/states.py +0 -0
  81. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/connectivity/__init__.py +0 -0
  82. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/connectivity/_edge_products.py +0 -0
  83. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/connectivity/correlation.py +0 -0
  84. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/connectivity/granger.py +0 -0
  85. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/connectivity/instantaneous.py +0 -0
  86. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/connectivity/leida.py +0 -0
  87. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/connectivity/lowrank.py +0 -0
  88. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/connectivity/partition.py +0 -0
  89. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/connectivity/windows.py +0 -0
  90. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/data.py +0 -0
  91. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/inference/__init__.py +0 -0
  92. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/inference/endpoints.py +0 -0
  93. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/inference/hc3.py +0 -0
  94. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/inference/independent_endpoints.py +0 -0
  95. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/inference/matching.py +0 -0
  96. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/inference/multiple_testing.py +0 -0
  97. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/inference/nbs.py +0 -0
  98. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/inference/nbs_endpoints.py +0 -0
  99. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/inference/paired.py +0 -0
  100. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/inference/state_metrics.py +0 -0
  101. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/information/__init__.py +0 -0
  102. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/information/_artifact.py +0 -0
  103. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/information/estimators.py +0 -0
  104. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/information/fixed.py +0 -0
  105. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/information/summary.py +0 -0
  106. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/io/__init__.py +0 -0
  107. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/io/xcpd.py +0 -0
  108. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/reference.py +0 -0
  109. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/segments.py +0 -0
  110. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/states/cross_validation.py +0 -0
  111. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/states/data.py +0 -0
  112. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/states/hmm.py +0 -0
  113. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/states/interpretation.py +0 -0
  114. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/states/kmeans.py +0 -0
  115. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/states/metrics.py +0 -0
  116. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/states/scoring.py +0 -0
  117. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/states/selection.py +0 -0
  118. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/states/stability.py +0 -0
  119. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/states/streaming.py +0 -0
  120. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/states/streaming_hmm.py +0 -0
  121. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/storage/__init__.py +0 -0
  122. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/storage/_statistics.py +0 -0
  123. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/storage/store.py +0 -0
  124. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/src/dfckit/storage/summary.py +0 -0
  125. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/__init__.py +0 -0
  126. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/fixtures/audited_kernels.json +0 -0
  127. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/integration/__init__.py +0 -0
  128. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/integration/test_leida_state_pipeline.py +0 -0
  129. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/integration/test_model_artifact_pipeline.py +0 -0
  130. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/integration/test_xcpd_information.py +0 -0
  131. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/integration/test_xcpd_lowrank.py +0 -0
  132. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/integration/test_xcpd_sliding_window.py +0 -0
  133. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/integration/test_xcpd_static_nbs.py +0 -0
  134. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/integration/test_xcpd_window_pattern.py +0 -0
  135. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/regression/__init__.py +0 -0
  136. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/regression/test_audited_kernels.py +0 -0
  137. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/regression/test_information_reference.py +0 -0
  138. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/regression/test_nbs_independent_reference.py +0 -0
  139. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/__init__.py +0 -0
  140. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_artifact_helpers.py +0 -0
  141. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_cli.py +0 -0
  142. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_correlation.py +0 -0
  143. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_cross_validation.py +0 -0
  144. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_data.py +0 -0
  145. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_endpoint_inference.py +0 -0
  146. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_ets.py +0 -0
  147. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_feature_selection.py +0 -0
  148. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_granger.py +0 -0
  149. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_granger_optional_dependency.py +0 -0
  150. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_hmm.py +0 -0
  151. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_independent_endpoint_inference.py +0 -0
  152. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_inference.py +0 -0
  153. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_inference_matching.py +0 -0
  154. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_information.py +0 -0
  155. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_information_io.py +0 -0
  156. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_information_parallel.py +0 -0
  157. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_information_summary.py +0 -0
  158. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_internal_helpers.py +0 -0
  159. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_leida.py +0 -0
  160. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_lowrank.py +0 -0
  161. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_model_io.py +0 -0
  162. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_mtd.py +0 -0
  163. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_nbs.py +0 -0
  164. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_nbs_endpoints.py +0 -0
  165. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_outofcore.py +0 -0
  166. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_outofcore_hmm.py +0 -0
  167. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_partition_graph.py +0 -0
  168. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_preprocessing.py +0 -0
  169. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_public_api.py +0 -0
  170. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_reference.py +0 -0
  171. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_sliding_window.py +0 -0
  172. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_stability.py +0 -0
  173. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_state_alignment_io.py +0 -0
  174. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_state_interpretation.py +0 -0
  175. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_state_metric_inference.py +0 -0
  176. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_state_results.py +0 -0
  177. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_state_scoring_io.py +0 -0
  178. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_state_selection.py +0 -0
  179. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_state_selection_io.py +0 -0
  180. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_state_stability_io.py +0 -0
  181. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_store_summary.py +0 -0
  182. {dfc_kit-1.0.2 → dfc_kit-1.0.3}/tests/unit/test_xcpd.py +0 -0
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.3 - 2026-10-08
4
+
5
+ - CAP now defaults to within-run ROI z-scoring and full-data Lloyd KMeans;
6
+ previously the defaults were within-segment z-scoring and MiniBatchKMeans.
7
+ Isolated retained frames contribute when their run has at least two frames.
8
+ - Added `cap_state_maps` to average standardized ROI activity by assigned state.
9
+ To retain earlier CAP behavior, choose `standardization="segment"` and
10
+ `algorithm="minibatch"`; for stores, use
11
+ `--cap-standardization segment` and a materialized MiniBatch fit.
12
+ - Clarified that result storage is explicit and computations are not automatically
13
+ cached across calls. Memory-mapped reads can be disabled with `mmap=False`;
14
+ this changes file loading, not computation reuse or operating-system caching.
15
+
3
16
  ## 1.0.2 - 2026-09-11
4
17
 
5
18
  - Added exact-feature selection for in-memory and disk-backed state models,
@@ -4,6 +4,6 @@ title: "dfc-kit: Censor-aware dynamic functional connectivity tools"
4
4
  type: software
5
5
  authors:
6
6
  - name: "dfc-kit contributors"
7
- version: 1.0.2
8
- date-released: 2026-09-11
7
+ version: 1.0.3
8
+ date-released: 2026-10-08
9
9
  license: BSD-3-Clause
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dfc-kit
3
- Version: 1.0.2
3
+ Version: 1.0.3
4
4
  Summary: Censor-aware tools for dynamic functional connectivity analysis
5
5
  License-Expression: BSD-3-Clause
6
6
  Project-URL: Homepage, https://github.com/yidao9518/dfc-kit
@@ -11,9 +11,10 @@ from dfckit.io import load_xcpd_run, discover_xcpd_runs
11
11
  ```
12
12
 
13
13
  `TimeSeriesRun` carries ROI time series, original frame indices, and acquisition
14
- metadata. XCP-D loaders preserve censor gaps. ETS and CAP perform their required
15
- within-segment standardization internally; the shared standardization helper is
16
- an implementation detail rather than a public preprocessing API.
14
+ metadata. XCP-D loaders preserve censor gaps. ETS standardizes within each
15
+ retained segment. CAP standardizes within each run by default, with segment-wise
16
+ scaling available as an option. Both retain segment boundaries for temporal
17
+ summaries.
17
18
 
18
19
  ## Connectivity
19
20
 
@@ -6,6 +6,11 @@ whole-edge window-pattern and low-rank covariance endpoints, information
6
6
  estimates, state fitting,
7
7
  decoding, scoring, alignment, repeated-fit stability, and paired inference.
8
8
 
9
+ Commands do not automatically reuse previous computation results. Store and
10
+ model inputs are loaded only when explicitly supplied; output paths name saved
11
+ results, not cache directories. `build-store` and `fit-states` require new output
12
+ paths and fail if those paths already exist.
13
+
9
14
  ## Inspect and build a FeatureStore
10
15
 
11
16
  ```bash
@@ -24,6 +29,24 @@ dfc-kit build-store /path/to/xcpd /path/to/features.store \
24
29
  All temporal methods preserve original frame indices and never cross a censor
25
30
  gap. `--method` accepts `window-fc`, `cap`, `ets`, `mtd`, or `leida`.
26
31
 
32
+ For CAP, build a store of instantaneous ROI patterns and fit materialized Lloyd
33
+ KMeans without a second pooled standardization:
34
+
35
+ ```bash
36
+ dfc-kit build-store /path/to/xcpd /path/to/cap.store \
37
+ --atlas Schaefer200 --space MNI152NLin2009cAsym \
38
+ --method cap --cap-standardization run
39
+ dfc-kit fit-states /path/to/cap.store /path/to/models/cap-k5.model \
40
+ --method kmeans --n-states 5 --seed 20260818 \
41
+ --n-init 20 --max-iter 300 \
42
+ --fitting-mode materialized --algorithm lloyd --no-standardize-features
43
+ ```
44
+
45
+ `--cap-standardization` defaults to `run`: each ROI is standardized over all
46
+ retained frames of a run, while censor-bounded sequence boundaries remain.
47
+ Choose `segment` for the earlier per-segment scaling. The default `fit-states`
48
+ streaming path is MiniBatchKMeans, not the in-memory CAP Lloyd default.
49
+
27
50
  ## Fit, decode, and score states
28
51
 
29
52
  ```bash
@@ -47,7 +47,8 @@ future predictions from the returned model already use reference numbering.
47
47
  CAP fits use the same `KMeansStateModel` contract but expose a domain-specific
48
48
  alignment entry point. Use `align_cap_centroids` for CAP models. It defaults to Pearson matching because
49
49
  CAP centres describe relative spatial co-activation configurations after
50
- within-segment ROI standardization:
50
+ ROI standardization. This matching step is separate from the Euclidean distance
51
+ used to fit the clusters:
51
52
 
52
53
  ```python
53
54
  cap_alignment = align_cap_centroids(reference_cap.model, candidate_cap.model)
@@ -186,39 +186,63 @@ materialized Lloyd KMeans; incompatible engines are rejected explicitly.
186
186
  ## CAP
187
187
 
188
188
  ```python
189
- from dfckit.states import fit_cap_states
189
+ from dfckit.states import (
190
+ cap_sequences,
191
+ cap_state_maps,
192
+ fit_cap_states,
193
+ predict_kmeans_states,
194
+ summarize_state_assignments,
195
+ )
190
196
 
197
+ features = cap_sequences(training_dataset)
191
198
  fit = fit_cap_states(training_dataset, n_states=5, seed=20260818)
199
+ maps = cap_state_maps(features, fit.assignments)
200
+ metrics = summarize_state_assignments(fit.assignments)
201
+
202
+ unseen_features = cap_sequences(unseen_dataset)
203
+ unseen_assignments = predict_kmeans_states(fit.model, unseen_features)
192
204
  ```
193
205
 
194
- CAP input consists of instantaneous ROI patterns. Each uninterrupted retained
195
- segment is centered and scaled ROI by ROI before fitting. The CAP wrapper uses
196
- MiniBatchKMeans and does not apply a second pooled feature standardization.
197
- CAP centroids are co-activation patterns, not connectivity matrices.
206
+ CAP uses every retained instantaneous ROI pattern. By default, each ROI is
207
+ centered and scaled over all retained frames of its run (`ddof=0`), with no
208
+ second scaling across participants. Runs need at least two retained frames;
209
+ isolated retained frames still contribute after run-wise scaling. Censor gaps
210
+ remain sequence boundaries for dwell and transition metrics. `fit_cap_states`
211
+ returns a `KMeansFitResult` and defaults to full-data Euclidean Lloyd KMeans
212
+ without PCA (`n_init=20`, `max_iter=300`). Set `algorithm="minibatch"` for
213
+ MiniBatchKMeans. `standardization="segment"` retains the earlier per-segment
214
+ scaling, which omits segments shorter than two frames.
215
+
216
+ `maps` is a read-only `(n_states, n_rois)` array: for each state, it averages
217
+ the standardized BOLD patterns of frames assigned that final label, with equal
218
+ weight per frame. A state with no assigned frames has a NaN map. These activity
219
+ maps are not FC matrices and do not change the fitted centers or prediction.
198
220
  Use `align_cap_centroids` to align repeated CAP fits. Its default is Pearson
199
221
  pattern matching; generic KMeans and HMM alignment continues to default to
200
222
  standardized Euclidean distance.
201
223
 
202
224
  For a disk-backed XCP-D cohort, build the equivalent CAP FeatureStore with
203
225
  `dfc-kit build-store --method cap` or `write_cap_store`. The store uses the
204
- source contract `cap:within-segment-roi-zscore-ddof0`, one ROI feature per row,
205
- and one sequence per uninterrupted retained segment. Its rows preserve the
206
- original frame indices, and segments shorter than two retained frames are
207
- omitted. To reproduce the in-memory CAP wrapper from that store, use materialized
208
- MiniBatchKMeans with `--no-standardize-features`:
226
+ source contract `cap:within-run-roi-zscore-ddof0`, one ROI feature per column,
227
+ and one sequence per uninterrupted retained segment. Its rows preserve original
228
+ frame indices. To fit the same geometry as the in-memory default, use
229
+ materialized Lloyd KMeans without pooled feature standardization:
209
230
 
210
231
  ```bash
211
232
  dfc-kit fit-states cap.store models/cap-k5.model \
212
233
  --method kmeans --n-states 5 --seed 20260818 \
213
234
  --n-init 20 --max-iter 300 \
214
- --fitting-mode materialized --algorithm minibatch \
235
+ --fitting-mode materialized --algorithm lloyd \
215
236
  --no-standardize-features
216
237
  ```
217
238
 
218
239
  Materialized fitting loads the selected cohort and calls one complete
219
- `MiniBatchKMeans.fit`; it is therefore intended for moderate stores and can
220
- reproduce historical native geometry. The default CLI path is bounded-memory
221
- streaming `MiniBatchKMeans.partial_fit`, which is a different fitting contract.
240
+ `KMeans.fit`; it is intended for moderate stores. The default CLI fitting path
241
+ uses bounded-memory streaming `MiniBatchKMeans.partial_fit`, so select
242
+ `--fitting-mode materialized --algorithm lloyd` explicitly for this CAP fit.
243
+
244
+ For background, see the [original full-frame CAP study](https://pmc.ncbi.nlm.nih.gov/articles/PMC3913885/)
245
+ and an [ROI-pattern CAP study](https://pmc.ncbi.nlm.nih.gov/articles/PMC4386757/).
222
246
 
223
247
  ## LEiDA states
224
248
 
@@ -14,6 +14,15 @@ FeatureStore accepts only the current manifest schema. Every sequence record
14
14
  therefore has an explicit acquisition field, which may be `None` when no
15
15
  acquisition label is available.
16
16
 
17
+ ## Storage versus computation caches
18
+
19
+ dfc-kit does not automatically cache computed results across calls. Numerical
20
+ analysis calls recompute their outputs; disk storage is explicit through store
21
+ writers and artifact save/load functions. Creating a store at an existing path
22
+ raises an error rather than silently reusing its contents. To compute features
23
+ without writing them to disk, use the estimator's in-memory API, such as
24
+ `SlidingWindowFC.transform(run)`, instead of a store writer.
25
+
17
26
  ## Stream sliding-window FC
18
27
 
19
28
  ```python
@@ -63,9 +72,8 @@ is required; that path avoids edge materialization.
63
72
  ## Stream CAP
64
73
 
65
74
  CAP stores contain instantaneous ROI patterns, not ROI-pair edges. The writer
66
- standardizes every ROI over the complete uninterrupted retained segment with
67
- population standard deviation (`ddof=0`) and then writes the resulting rows in
68
- bounded chunks:
75
+ standardizes every ROI across all retained frames of each run with population
76
+ standard deviation (`ddof=0`) and writes rows in bounded chunks:
69
77
 
70
78
  ```python
71
79
  from dfckit.storage import append_cap, write_cap_store
@@ -80,14 +88,16 @@ store = write_cap_store(
80
88
  append_cap(store, another_run, chunk_size=256)
81
89
  ```
82
90
 
83
- Each retained segment becomes its own FeatureStore sequence. Segments shorter
84
- than two retained frames are omitted, because a segment-level standardized CAP
85
- sequence cannot provide a usable temporal sample. Every stored row retains the
86
- original frame index in both `sample_start_indices` and `sample_end_indices`,
87
- and censor gaps therefore cannot be joined during later state metrics. The
88
- source contract is `cap:within-segment-roi-zscore-ddof0`, and the feature keys
89
- are the ROI names in their input order. Standardization is performed before
90
- chunking, so changing `chunk_size` does not change CAP values.
91
+ Runs need at least two retained frames. Each retained censor-bounded segment
92
+ becomes its own FeatureStore sequence, including isolated retained frames.
93
+ Every stored row retains its original frame index in both
94
+ `sample_start_indices` and `sample_end_indices`, so later state metrics do not
95
+ join censor gaps. The source contract is `cap:within-run-roi-zscore-ddof0`,
96
+ and the feature keys are the ROI names in input order. Set
97
+ `standardization="segment"` in `write_cap_store` or `append_cap` to use the
98
+ earlier per-segment scaling and omit segments shorter than two frames; its
99
+ source contract is `cap:within-segment-roi-zscore-ddof0`. Standardization
100
+ precedes chunking, so `chunk_size` does not change CAP values.
91
101
 
92
102
  ## Stream LEiDA
93
103
 
@@ -129,6 +139,11 @@ sequence_part = store.read_sequence(
129
139
  append_window_fc(store, another_run, estimator, chunk_size=128)
130
140
  ```
131
141
 
142
+ `iter_chunks()` uses read-only memory mapping by default. Pass `mmap=False` to
143
+ load each chunk into ordinary NumPy arrays instead. This controls how existing
144
+ files are read, not whether previous computations are reused. Neither mode
145
+ disables the operating system's filesystem cache.
146
+
132
147
  `read_dataset()` reconstructs ordinary `FeatureSequenceDataset` objects for
133
148
  existing in-memory state APIs. For genuinely large analyses, iterate chunks or
134
149
  read selected sequences instead of reconstructing the full dataset.
@@ -266,7 +281,7 @@ fit = fit_kmeans_store_materialized(
266
281
  seed=20260818,
267
282
  n_init=20,
268
283
  max_iter=300,
269
- algorithm="minibatch",
284
+ algorithm="lloyd",
270
285
  standardize_features=False,
271
286
  )
272
287
  ```
@@ -275,8 +290,8 @@ Materialized fitting loads the selected cohort into memory; it is not an
275
290
  out-of-core operation. `algorithm="lloyd"` calls scikit-learn `KMeans.fit`,
276
291
  whereas `algorithm="minibatch"` calls `MiniBatchKMeans.fit`. Both retain the
277
292
  FeatureStore source contract, feature identities, and selected fit subjects in
278
- the returned model. CAP rows have already been
279
- standardized within segment, so CAP reproduction normally uses
293
+ the returned model. CAP rows have already been standardized within run (or
294
+ within segment when requested), so CAP fitting normally uses
280
295
  `standardize_features=False`; enabling it adds a second pooled feature
281
296
  standardization and changes the fitted geometry.
282
297
 
@@ -285,8 +300,8 @@ The two KMeans paths have distinct numerical and memory semantics:
285
300
  | Mode | Algorithm | Feature rows in RAM | Estimator operation | Typical use |
286
301
  | --- | --- | --- | --- | --- |
287
302
  | `streaming` | `minibatch` only | bounded chunks | repeated `partial_fit` passes | large stores |
288
- | `materialized` | `lloyd` | selected cohort | one `KMeans.fit` call | historical Lloyd fits |
289
- | `materialized` | `minibatch` | selected cohort | one `MiniBatchKMeans.fit` call | historical CAP/MiniBatch fits |
303
+ | `materialized` | `lloyd` | selected cohort | one `KMeans.fit` call | default in-memory CAP fit |
304
+ | `materialized` | `minibatch` | selected cohort | one `MiniBatchKMeans.fit` call | optional MiniBatch fit |
290
305
 
291
306
  The streaming path is not a hidden alias for `MiniBatchKMeans.fit`: its
292
307
  initialization sample, chunk traversal, and repeated `partial_fit` updates are
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "dfc-kit"
7
- version = "1.0.2"
7
+ version = "1.0.3"
8
8
  description = "Censor-aware tools for dynamic functional connectivity analysis"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dfc-kit
3
- Version: 1.0.2
3
+ Version: 1.0.3
4
4
  Summary: Censor-aware tools for dynamic functional connectivity analysis
5
5
  License-Expression: BSD-3-Clause
6
6
  Project-URL: Homepage, https://github.com/yidao9518/dfc-kit
@@ -132,6 +132,7 @@ tests/regression/test_nbs_independent_reference.py
132
132
  tests/regression/test_nbs_reference.py
133
133
  tests/unit/__init__.py
134
134
  tests/unit/test_artifact_helpers.py
135
+ tests/unit/test_cap.py
135
136
  tests/unit/test_cli.py
136
137
  tests/unit/test_correlation.py
137
138
  tests/unit/test_cross_validation.py
@@ -8,4 +8,4 @@ __all__ = [
8
8
  "TimeWindow",
9
9
  "validate_subject_disjoint",
10
10
  ]
11
- __version__ = "1.0.2"
11
+ __version__ = "1.0.3"
@@ -9,6 +9,39 @@ from ._arrays import readonly_copy as _readonly
9
9
  from .data import TimeSeriesRun
10
10
 
11
11
 
12
+ def _roi_zscore(values: NDArray[np.float64]) -> NDArray[np.float64]:
13
+ scale = values.std(axis=0, ddof=0)
14
+ scale = np.where(scale < 1e-8, 1.0, scale)
15
+ return (values - values.mean(axis=0)) / scale
16
+
17
+
18
+ def _standardized_samples(
19
+ run: TimeSeriesRun,
20
+ *,
21
+ standardization: str = "run",
22
+ method_name: str = "CAP",
23
+ ) -> tuple[NDArray[np.float64], NDArray[np.int64], NDArray[np.int64]]:
24
+ """Standardize retained ROI samples while preserving censor boundaries.
25
+
26
+ Run scaling uses all retained frames together, including isolated frames.
27
+ Segment scaling omits segments shorter than two frames.
28
+ """
29
+ if standardization == "segment":
30
+ return _segment_standardized_samples(run, method_name=method_name)
31
+ if standardization != "run":
32
+ raise ValueError("standardization must be 'run' or 'segment'")
33
+ if run.n_frames < 2:
34
+ raise ValueError(f"{method_name} requires at least two retained frames per run")
35
+ values = _roi_zscore(run.values)
36
+ if not np.isfinite(values).all():
37
+ raise ValueError(f"standardized {method_name} samples contain non-finite values")
38
+ segment_ids = np.concatenate([
39
+ np.full(len(positions), segment_id, dtype=np.int64)
40
+ for segment_id, positions in enumerate(run.segments())
41
+ ])
42
+ return _readonly(values), _readonly(run.original_indices), _readonly(segment_ids)
43
+
44
+
12
45
  def _segment_standardized_samples(
13
46
  run: TimeSeriesRun,
14
47
  *,
@@ -28,9 +61,7 @@ def _segment_standardized_samples(
28
61
  if len(positions) < 2:
29
62
  continue
30
63
  values = run.values[positions]
31
- scale = values.std(axis=0, ddof=0)
32
- scale = np.where(scale < 1e-8, 1.0, scale)
33
- samples.append((values - values.mean(axis=0)) / scale)
64
+ samples.append(_roi_zscore(values))
34
65
  original_indices.append(run.original_indices[positions])
35
66
  segment_ids.append(np.full(len(positions), segment_id, dtype=np.int64))
36
67
 
@@ -71,6 +71,10 @@ def build_parser() -> argparse.ArgumentParser:
71
71
  build.add_argument("--window-step", type=int, default=8)
72
72
  build.add_argument("--taper", choices=("hamming", "uniform"), default="hamming")
73
73
  build.add_argument("--minimum-segment-length", type=int, default=20)
74
+ build.add_argument(
75
+ "--cap-standardization", choices=("run", "segment"), default="run",
76
+ help="CAP ROI z-score scope; run uses all retained frames per acquisition",
77
+ )
74
78
 
75
79
  information = subparsers.add_parser(
76
80
  "fixed-information",
@@ -164,6 +164,7 @@ def build_store(namespace: argparse.Namespace) -> dict[str, object]:
164
164
  store = write_cap_store(
165
165
  namespace.output,
166
166
  dataset.runs,
167
+ standardization=namespace.cap_standardization,
167
168
  chunk_size=namespace.chunk_size,
168
169
  dtype=namespace.dtype,
169
170
  )
@@ -11,7 +11,7 @@ from .alignment import (
11
11
  relabel_gaussian_hmm_model,
12
12
  relabel_kmeans_model,
13
13
  )
14
- from .cap import cap_sequences, fit_cap_states
14
+ from .cap import cap_sequences, cap_state_maps, fit_cap_states
15
15
  from .cross_validation import SubjectValidationFold, make_subject_validation_folds
16
16
  from .data import (
17
17
  FeatureSequence,
@@ -77,6 +77,7 @@ __all__ = [
77
77
  "apply_gaussian_hmm_alignment",
78
78
  "apply_state_alignment",
79
79
  "cap_sequences",
80
+ "cap_state_maps",
80
81
  "compare_state_model_scores",
81
82
  "fit_cap_states",
82
83
  "fit_gaussian_hmm_states",
@@ -193,9 +193,8 @@ def align_cap_centroids(
193
193
  ) -> StateAlignment:
194
194
  """Align CAP state centres, using pattern-shape matching by default.
195
195
 
196
- CAP centres are relative ROI activation configurations because each input
197
- segment is ROI-wise z-scored. Pearson matching therefore compares the
198
- spatial configuration without treating a proportional amplitude change as
196
+ CAP centres describe ROI-wise standardized activity. Pearson matching
197
+ compares the spatial configuration without treating a proportional amplitude change as
199
198
  a different CAP state. Pass ``metric="euclidean"`` when amplitude should
200
199
  also contribute to the CAP pairing.
201
200
  """
@@ -0,0 +1,124 @@
1
+ """Co-activation pattern feature construction and KMeans fitting."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import numpy as np
6
+ from numpy.typing import NDArray
7
+
8
+ from .._arrays import readonly_copy
9
+ from .._preprocessing import _standardized_samples
10
+ from ..data import TimeSeriesDataset
11
+ from .data import (
12
+ FeatureSequence,
13
+ FeatureSequenceDataset,
14
+ StateAssignments,
15
+ _segment_feature_sequences,
16
+ )
17
+ from .kmeans import KMeansFitResult, fit_kmeans_states
18
+
19
+
20
+ def cap_sequences(
21
+ dataset: TimeSeriesDataset,
22
+ *,
23
+ standardization: str = "run",
24
+ ) -> FeatureSequenceDataset:
25
+ """Create instantaneous ROI patterns from all retained frames.
26
+
27
+ Each run is ROI-wise z-scored over its retained frames by default (ddof=0).
28
+ ``standardization='segment'`` instead scales each contiguous segment and
29
+ omits singleton segments. Both modes keep censor gaps as sequence boundaries.
30
+ """
31
+ dataset.require_subject_ids("CAP state modeling")
32
+ feature_keys = tuple((roi,) for roi in dataset.roi_names)
33
+ sequences: list[FeatureSequence] = []
34
+ for run in dataset.runs:
35
+ assert run.subject is not None
36
+ standardized, original_indices, segment_ids = _standardized_samples(
37
+ run, standardization=standardization, method_name="CAP"
38
+ )
39
+ sequences.extend(
40
+ _segment_feature_sequences(
41
+ run, standardized, original_indices, original_indices, segment_ids,
42
+ feature_keys=feature_keys,
43
+ source_contract=f"cap:within-{standardization}-roi-zscore-ddof0",
44
+ interval=run.tr,
45
+ )
46
+ )
47
+ return FeatureSequenceDataset(sequences)
48
+
49
+
50
+ def fit_cap_states(
51
+ dataset: TimeSeriesDataset,
52
+ *,
53
+ n_states: int,
54
+ seed: int,
55
+ n_init: int = 20,
56
+ max_iter: int = 300,
57
+ standardization: str = "run",
58
+ algorithm: str = "lloyd",
59
+ batch_size: int = 4096,
60
+ reassignment_ratio: float = 0.01,
61
+ ) -> KMeansFitResult:
62
+ """Fit Euclidean KMeans to standardized instantaneous ROI patterns.
63
+
64
+ The default uses full-batch Lloyd KMeans, with no PCA or second feature
65
+ standardization. ``algorithm='minibatch'`` enables approximate fitting.
66
+ Use :func:`cap_state_maps` for exact frame-average activity maps under the
67
+ final assignments, rather than assuming estimated centers equal those maps.
68
+ """
69
+ return fit_kmeans_states(
70
+ cap_sequences(dataset, standardization=standardization),
71
+ n_states=n_states,
72
+ seed=seed,
73
+ n_init=n_init,
74
+ max_iter=max_iter,
75
+ algorithm=algorithm,
76
+ standardize_features=False,
77
+ batch_size=batch_size,
78
+ reassignment_ratio=reassignment_ratio,
79
+ )
80
+
81
+
82
+ def cap_state_maps(
83
+ features: FeatureSequenceDataset,
84
+ assignments: StateAssignments,
85
+ ) -> NDArray[np.float64]:
86
+ """Average standardized ROI activity over the frames assigned to each CAP.
87
+
88
+ Returns a read-only states-by-ROIs array in ``features.feature_keys`` order.
89
+ Frames have equal weight; states with no assigned frames have NaN rows.
90
+ Sequences may be reordered, but acquisition identities and frame indices
91
+ must match. These descriptive maps do not change the fitted model centers.
92
+ """
93
+ if not features.source_contract.startswith("cap:") or any(
94
+ len(key) != 1 for key in features.feature_keys
95
+ ):
96
+ raise ValueError("CAP maps require CAP ROI features from cap_sequences or a CAP store")
97
+ if features.source_contract != assignments.source_contract:
98
+ raise ValueError("CAP maps require matching standardization source contracts")
99
+ left, right = features.sample_interval_seconds, assignments.sample_interval_seconds
100
+ if (left is None) != (right is None) or (
101
+ left is not None and right is not None and not np.isclose(left, right, rtol=0, atol=1e-9)
102
+ ):
103
+ raise ValueError("CAP maps require matching sample intervals")
104
+
105
+ def identity(sequence):
106
+ return sequence.subject, sequence.session, sequence.acquisition_id, sequence.segment_id
107
+
108
+ labels_by_sequence = {identity(sequence): sequence for sequence in assignments.sequences}
109
+ if {identity(sequence) for sequence in features.sequences} != set(labels_by_sequence):
110
+ raise ValueError("CAP maps require matching sequence identities")
111
+ sums = np.zeros((assignments.n_states, len(features.feature_keys)), dtype=float)
112
+ counts = np.zeros(assignments.n_states, dtype=np.int64)
113
+ for sequence in features.sequences:
114
+ assigned = labels_by_sequence[identity(sequence)]
115
+ if not (
116
+ np.array_equal(sequence.sample_start_indices, assigned.sample_start_indices)
117
+ and np.array_equal(sequence.sample_end_indices, assigned.sample_end_indices)
118
+ ):
119
+ raise ValueError("CAP maps require matching frame indices")
120
+ np.add.at(sums, assigned.labels, sequence.values)
121
+ counts += np.bincount(assigned.labels, minlength=assignments.n_states)
122
+ maps = np.full(sums.shape, np.nan)
123
+ np.divide(sums, counts[:, None], out=maps, where=counts[:, None] > 0)
124
+ return readonly_copy(maps)
@@ -8,7 +8,7 @@ from pathlib import Path
8
8
  import numpy as np
9
9
  from numpy.typing import NDArray
10
10
 
11
- from .._preprocessing import _segment_standardized_samples
11
+ from .._preprocessing import _standardized_samples
12
12
  from ..connectivity._edge_products import edge_products
13
13
  from ..connectivity.correlation import edge_index, weighted_correlation
14
14
  from ..connectivity.instantaneous import _InstantaneousEstimator, _InstantaneousRows
@@ -211,20 +211,23 @@ def append_cap(
211
211
  store: FeatureStore,
212
212
  run: TimeSeriesRun,
213
213
  *,
214
+ standardization: str = "run",
214
215
  chunk_size: int = 128,
215
216
  ) -> None:
216
- """Append segment-standardized instantaneous ROI patterns in bounded chunks."""
217
+ """Append run- or segment-standardized ROI patterns in bounded chunks."""
217
218
  if run.subject is None:
218
219
  raise ValueError("stored CAP requires a subject identifier")
219
220
  size = _validated_chunk_size(chunk_size)
220
221
  keys = _roi_feature_keys(run.roi_names)
221
- contract = "cap:within-segment-roi-zscore-ddof0"
222
+ contract = f"cap:within-{standardization}-roi-zscore-ddof0"
222
223
  store.require_contract(
223
224
  feature_keys=keys,
224
225
  source_contract=contract,
225
226
  sample_interval_seconds=run.tr,
226
227
  )
227
- standardized, original, segment_ids = _segment_standardized_samples(run, method_name="CAP")
228
+ standardized, original, segment_ids = _standardized_samples(
229
+ run, standardization=standardization, method_name="CAP"
230
+ )
228
231
  _append_segment_rows(
229
232
  store, run, standardized, original, original, segment_ids, chunk_size=size,
230
233
  )
@@ -234,21 +237,24 @@ def write_cap_store(
234
237
  root: str | Path,
235
238
  runs: Sequence[TimeSeriesRun],
236
239
  *,
240
+ standardization: str = "run",
237
241
  chunk_size: int = 128,
238
242
  dtype: str | np.dtype = "float64",
239
243
  ) -> FeatureStore:
240
- """Create a store of segment-standardized instantaneous ROI patterns."""
244
+ """Store CAP patterns, using run-wise ROI z-scores by default."""
245
+ if standardization not in {"run", "segment"}:
246
+ raise ValueError("standardization must be 'run' or 'segment'")
241
247
  dataset = TimeSeriesDataset(runs)
242
248
  dataset.require_subject_ids("stored CAP")
243
249
  store = FeatureStore.create(
244
250
  root,
245
251
  feature_keys=_roi_feature_keys(dataset.roi_names),
246
- source_contract="cap:within-segment-roi-zscore-ddof0",
252
+ source_contract=f"cap:within-{standardization}-roi-zscore-ddof0",
247
253
  sample_interval_seconds=dataset.tr,
248
254
  dtype=dtype,
249
255
  )
250
256
  for run in dataset.runs:
251
- append_cap(store, run, chunk_size=chunk_size)
257
+ append_cap(store, run, standardization=standardization, chunk_size=chunk_size)
252
258
  return store
253
259
 
254
260