junifer 0.0.4.dev831__py3-none-any.whl → 0.0.5__py3-none-any.whl

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 (206) hide show
  1. junifer/__init__.py +17 -0
  2. junifer/_version.py +2 -2
  3. junifer/api/__init__.py +4 -1
  4. junifer/api/cli.py +91 -1
  5. junifer/api/decorators.py +9 -0
  6. junifer/api/functions.py +56 -10
  7. junifer/api/parser.py +3 -0
  8. junifer/api/queue_context/__init__.py +4 -1
  9. junifer/api/queue_context/gnu_parallel_local_adapter.py +16 -6
  10. junifer/api/queue_context/htcondor_adapter.py +16 -5
  11. junifer/api/queue_context/tests/test_gnu_parallel_local_adapter.py +41 -12
  12. junifer/api/queue_context/tests/test_htcondor_adapter.py +48 -15
  13. junifer/api/res/afni/run_afni_docker.sh +1 -1
  14. junifer/api/res/ants/run_ants_docker.sh +1 -1
  15. junifer/api/res/freesurfer/mri_binarize +3 -0
  16. junifer/api/res/freesurfer/mri_mc +3 -0
  17. junifer/api/res/freesurfer/mri_pretess +3 -0
  18. junifer/api/res/freesurfer/mris_convert +3 -0
  19. junifer/api/res/freesurfer/run_freesurfer_docker.sh +61 -0
  20. junifer/api/res/fsl/run_fsl_docker.sh +1 -1
  21. junifer/api/res/{run_conda.sh → run_conda.bash} +1 -1
  22. junifer/api/res/run_conda.zsh +23 -0
  23. junifer/api/res/run_venv.bash +22 -0
  24. junifer/api/res/{run_venv.sh → run_venv.zsh} +1 -1
  25. junifer/api/tests/test_api_utils.py +4 -2
  26. junifer/api/tests/test_cli.py +83 -0
  27. junifer/api/tests/test_functions.py +27 -2
  28. junifer/configs/__init__.py +1 -1
  29. junifer/configs/juseless/__init__.py +4 -1
  30. junifer/configs/juseless/datagrabbers/__init__.py +10 -1
  31. junifer/configs/juseless/datagrabbers/aomic_id1000_vbm.py +4 -3
  32. junifer/configs/juseless/datagrabbers/camcan_vbm.py +3 -0
  33. junifer/configs/juseless/datagrabbers/ixi_vbm.py +4 -3
  34. junifer/configs/juseless/datagrabbers/tests/test_ucla.py +1 -3
  35. junifer/configs/juseless/datagrabbers/ucla.py +12 -9
  36. junifer/configs/juseless/datagrabbers/ukb_vbm.py +3 -0
  37. junifer/data/__init__.py +21 -1
  38. junifer/data/coordinates.py +10 -19
  39. junifer/data/masks/ukb/UKB_15K_GM_template.nii.gz +0 -0
  40. junifer/data/masks.py +58 -87
  41. junifer/data/parcellations.py +14 -3
  42. junifer/data/template_spaces.py +4 -1
  43. junifer/data/tests/test_masks.py +26 -37
  44. junifer/data/utils.py +3 -0
  45. junifer/datagrabber/__init__.py +18 -1
  46. junifer/datagrabber/aomic/__init__.py +3 -0
  47. junifer/datagrabber/aomic/id1000.py +70 -37
  48. junifer/datagrabber/aomic/piop1.py +69 -36
  49. junifer/datagrabber/aomic/piop2.py +71 -38
  50. junifer/datagrabber/aomic/tests/test_id1000.py +44 -100
  51. junifer/datagrabber/aomic/tests/test_piop1.py +65 -108
  52. junifer/datagrabber/aomic/tests/test_piop2.py +45 -102
  53. junifer/datagrabber/base.py +13 -6
  54. junifer/datagrabber/datalad_base.py +13 -1
  55. junifer/datagrabber/dmcc13_benchmark.py +36 -53
  56. junifer/datagrabber/hcp1200/__init__.py +3 -0
  57. junifer/datagrabber/hcp1200/datalad_hcp1200.py +3 -0
  58. junifer/datagrabber/hcp1200/hcp1200.py +4 -1
  59. junifer/datagrabber/multiple.py +45 -6
  60. junifer/datagrabber/pattern.py +170 -62
  61. junifer/datagrabber/pattern_datalad.py +25 -12
  62. junifer/datagrabber/pattern_validation_mixin.py +388 -0
  63. junifer/datagrabber/tests/test_datalad_base.py +4 -4
  64. junifer/datagrabber/tests/test_dmcc13_benchmark.py +46 -19
  65. junifer/datagrabber/tests/test_multiple.py +161 -84
  66. junifer/datagrabber/tests/test_pattern.py +45 -0
  67. junifer/datagrabber/tests/test_pattern_datalad.py +4 -4
  68. junifer/datagrabber/tests/test_pattern_validation_mixin.py +249 -0
  69. junifer/datareader/__init__.py +4 -1
  70. junifer/datareader/default.py +95 -43
  71. junifer/external/BrainPrint/brainprint/__init__.py +4 -0
  72. junifer/external/BrainPrint/brainprint/_version.py +3 -0
  73. junifer/external/BrainPrint/brainprint/asymmetry.py +91 -0
  74. junifer/external/BrainPrint/brainprint/brainprint.py +441 -0
  75. junifer/external/BrainPrint/brainprint/surfaces.py +258 -0
  76. junifer/external/BrainPrint/brainprint/utils/__init__.py +1 -0
  77. junifer/external/BrainPrint/brainprint/utils/_config.py +112 -0
  78. junifer/external/BrainPrint/brainprint/utils/utils.py +188 -0
  79. junifer/external/__init__.py +1 -1
  80. junifer/external/nilearn/__init__.py +5 -1
  81. junifer/external/nilearn/junifer_connectivity_measure.py +483 -0
  82. junifer/external/nilearn/junifer_nifti_spheres_masker.py +23 -9
  83. junifer/external/nilearn/tests/test_junifer_connectivity_measure.py +1089 -0
  84. junifer/external/nilearn/tests/test_junifer_nifti_spheres_masker.py +76 -1
  85. junifer/markers/__init__.py +23 -1
  86. junifer/markers/base.py +68 -28
  87. junifer/markers/brainprint.py +459 -0
  88. junifer/markers/collection.py +10 -2
  89. junifer/markers/complexity/__init__.py +10 -0
  90. junifer/markers/complexity/complexity_base.py +26 -43
  91. junifer/markers/complexity/hurst_exponent.py +3 -0
  92. junifer/markers/complexity/multiscale_entropy_auc.py +3 -0
  93. junifer/markers/complexity/perm_entropy.py +3 -0
  94. junifer/markers/complexity/range_entropy.py +3 -0
  95. junifer/markers/complexity/range_entropy_auc.py +3 -0
  96. junifer/markers/complexity/sample_entropy.py +3 -0
  97. junifer/markers/complexity/tests/test_hurst_exponent.py +11 -3
  98. junifer/markers/complexity/tests/test_multiscale_entropy_auc.py +11 -3
  99. junifer/markers/complexity/tests/test_perm_entropy.py +11 -3
  100. junifer/markers/complexity/tests/test_range_entropy.py +11 -3
  101. junifer/markers/complexity/tests/test_range_entropy_auc.py +11 -3
  102. junifer/markers/complexity/tests/test_sample_entropy.py +11 -3
  103. junifer/markers/complexity/tests/test_weighted_perm_entropy.py +11 -3
  104. junifer/markers/complexity/weighted_perm_entropy.py +3 -0
  105. junifer/markers/ets_rss.py +27 -42
  106. junifer/markers/falff/__init__.py +3 -0
  107. junifer/markers/falff/_afni_falff.py +5 -2
  108. junifer/markers/falff/_junifer_falff.py +3 -0
  109. junifer/markers/falff/falff_base.py +20 -46
  110. junifer/markers/falff/falff_parcels.py +56 -27
  111. junifer/markers/falff/falff_spheres.py +60 -29
  112. junifer/markers/falff/tests/test_falff_parcels.py +39 -23
  113. junifer/markers/falff/tests/test_falff_spheres.py +39 -23
  114. junifer/markers/functional_connectivity/__init__.py +9 -0
  115. junifer/markers/functional_connectivity/crossparcellation_functional_connectivity.py +63 -60
  116. junifer/markers/functional_connectivity/edge_functional_connectivity_parcels.py +45 -32
  117. junifer/markers/functional_connectivity/edge_functional_connectivity_spheres.py +49 -36
  118. junifer/markers/functional_connectivity/functional_connectivity_base.py +71 -70
  119. junifer/markers/functional_connectivity/functional_connectivity_parcels.py +34 -25
  120. junifer/markers/functional_connectivity/functional_connectivity_spheres.py +40 -30
  121. junifer/markers/functional_connectivity/tests/test_crossparcellation_functional_connectivity.py +11 -7
  122. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_parcels.py +27 -7
  123. junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_spheres.py +28 -12
  124. junifer/markers/functional_connectivity/tests/test_functional_connectivity_parcels.py +35 -11
  125. junifer/markers/functional_connectivity/tests/test_functional_connectivity_spheres.py +36 -62
  126. junifer/markers/parcel_aggregation.py +47 -61
  127. junifer/markers/reho/__init__.py +3 -0
  128. junifer/markers/reho/_afni_reho.py +5 -2
  129. junifer/markers/reho/_junifer_reho.py +4 -1
  130. junifer/markers/reho/reho_base.py +8 -27
  131. junifer/markers/reho/reho_parcels.py +28 -17
  132. junifer/markers/reho/reho_spheres.py +27 -18
  133. junifer/markers/reho/tests/test_reho_parcels.py +8 -3
  134. junifer/markers/reho/tests/test_reho_spheres.py +8 -3
  135. junifer/markers/sphere_aggregation.py +43 -59
  136. junifer/markers/temporal_snr/__init__.py +3 -0
  137. junifer/markers/temporal_snr/temporal_snr_base.py +23 -32
  138. junifer/markers/temporal_snr/temporal_snr_parcels.py +9 -6
  139. junifer/markers/temporal_snr/temporal_snr_spheres.py +9 -6
  140. junifer/markers/temporal_snr/tests/test_temporal_snr_parcels.py +6 -3
  141. junifer/markers/temporal_snr/tests/test_temporal_snr_spheres.py +6 -3
  142. junifer/markers/tests/test_brainprint.py +58 -0
  143. junifer/markers/tests/test_collection.py +9 -8
  144. junifer/markers/tests/test_ets_rss.py +15 -9
  145. junifer/markers/tests/test_markers_base.py +17 -18
  146. junifer/markers/tests/test_parcel_aggregation.py +93 -32
  147. junifer/markers/tests/test_sphere_aggregation.py +72 -19
  148. junifer/onthefly/__init__.py +4 -1
  149. junifer/onthefly/read_transform.py +3 -0
  150. junifer/pipeline/__init__.py +9 -1
  151. junifer/pipeline/pipeline_step_mixin.py +21 -4
  152. junifer/pipeline/registry.py +3 -0
  153. junifer/pipeline/singleton.py +3 -0
  154. junifer/pipeline/tests/test_registry.py +1 -1
  155. junifer/pipeline/update_meta_mixin.py +3 -0
  156. junifer/pipeline/utils.py +67 -1
  157. junifer/pipeline/workdir_manager.py +3 -0
  158. junifer/preprocess/__init__.py +10 -2
  159. junifer/preprocess/base.py +6 -3
  160. junifer/preprocess/confounds/__init__.py +3 -0
  161. junifer/preprocess/confounds/fmriprep_confound_remover.py +47 -60
  162. junifer/preprocess/confounds/tests/test_fmriprep_confound_remover.py +72 -113
  163. junifer/preprocess/smoothing/__init__.py +9 -0
  164. junifer/preprocess/smoothing/_afni_smoothing.py +119 -0
  165. junifer/preprocess/smoothing/_fsl_smoothing.py +116 -0
  166. junifer/preprocess/smoothing/_nilearn_smoothing.py +69 -0
  167. junifer/preprocess/smoothing/smoothing.py +174 -0
  168. junifer/preprocess/smoothing/tests/test_smoothing.py +94 -0
  169. junifer/preprocess/warping/__init__.py +3 -0
  170. junifer/preprocess/warping/_ants_warper.py +3 -0
  171. junifer/preprocess/warping/_fsl_warper.py +3 -0
  172. junifer/stats.py +4 -1
  173. junifer/storage/__init__.py +9 -1
  174. junifer/storage/base.py +40 -1
  175. junifer/storage/hdf5.py +71 -9
  176. junifer/storage/pandas_base.py +3 -0
  177. junifer/storage/sqlite.py +3 -0
  178. junifer/storage/tests/test_hdf5.py +82 -10
  179. junifer/storage/utils.py +9 -0
  180. junifer/testing/__init__.py +4 -1
  181. junifer/testing/datagrabbers.py +13 -6
  182. junifer/testing/tests/test_partlycloudytesting_datagrabber.py +7 -7
  183. junifer/testing/utils.py +3 -0
  184. junifer/utils/__init__.py +13 -2
  185. junifer/utils/fs.py +3 -0
  186. junifer/utils/helpers.py +32 -1
  187. junifer/utils/logging.py +33 -4
  188. junifer/utils/tests/test_logging.py +8 -0
  189. {junifer-0.0.4.dev831.dist-info → junifer-0.0.5.dist-info}/METADATA +17 -16
  190. junifer-0.0.5.dist-info/RECORD +275 -0
  191. {junifer-0.0.4.dev831.dist-info → junifer-0.0.5.dist-info}/WHEEL +1 -1
  192. junifer/datagrabber/tests/test_datagrabber_utils.py +0 -218
  193. junifer/datagrabber/utils.py +0 -230
  194. junifer/preprocess/ants/__init__.py +0 -4
  195. junifer/preprocess/ants/ants_apply_transforms_warper.py +0 -185
  196. junifer/preprocess/ants/tests/test_ants_apply_transforms_warper.py +0 -56
  197. junifer/preprocess/bold_warper.py +0 -265
  198. junifer/preprocess/fsl/__init__.py +0 -4
  199. junifer/preprocess/fsl/apply_warper.py +0 -179
  200. junifer/preprocess/fsl/tests/test_apply_warper.py +0 -45
  201. junifer/preprocess/tests/test_bold_warper.py +0 -159
  202. junifer-0.0.4.dev831.dist-info/RECORD +0 -257
  203. {junifer-0.0.4.dev831.dist-info → junifer-0.0.5.dist-info}/AUTHORS.rst +0 -0
  204. {junifer-0.0.4.dev831.dist-info → junifer-0.0.5.dist-info}/LICENSE.md +0 -0
  205. {junifer-0.0.4.dev831.dist-info → junifer-0.0.5.dist-info}/entry_points.txt +0 -0
  206. {junifer-0.0.4.dev831.dist-info → junifer-0.0.5.dist-info}/top_level.txt +0 -0
@@ -25,28 +25,26 @@ def test_MultipleDataGrabber() -> None:
25
25
  repo_uri = _testing_dataset["example_bids_ses"]["uri"]
26
26
  rootdir = "example_bids_ses"
27
27
  replacements = ["subject", "session"]
28
- pattern1 = {
29
- "T1w": {
30
- "pattern": (
31
- "{subject}/{session}/anat/{subject}_{session}_T1w.nii.gz"
32
- ),
33
- "space": "native",
34
- },
35
- }
36
- pattern2 = {
37
- "BOLD": {
38
- "pattern": (
39
- "{subject}/{session}/func/"
40
- "{subject}_{session}_task-rest_bold.nii.gz"
41
- ),
42
- "space": "MNI152NLin6Asym",
43
- },
44
- }
28
+
45
29
  dg1 = PatternDataladDataGrabber(
46
30
  rootdir=rootdir,
47
31
  uri=repo_uri,
48
32
  types=["T1w"],
49
- patterns=pattern1,
33
+ patterns={
34
+ "T1w": {
35
+ "pattern": (
36
+ "{subject}/{session}/anat/{subject}_{session}_T1w.nii.gz"
37
+ ),
38
+ "space": "native",
39
+ "mask": {
40
+ "pattern": (
41
+ "{subject}/{session}/anat/{subject}_{session}_"
42
+ "brain_mask.nii.gz"
43
+ ),
44
+ "space": "native",
45
+ },
46
+ },
47
+ },
50
48
  replacements=replacements,
51
49
  )
52
50
 
@@ -54,7 +52,22 @@ def test_MultipleDataGrabber() -> None:
54
52
  rootdir=rootdir,
55
53
  uri=repo_uri,
56
54
  types=["BOLD"],
57
- patterns=pattern2,
55
+ patterns={
56
+ "BOLD": {
57
+ "pattern": (
58
+ "{subject}/{session}/func/"
59
+ "{subject}_{session}_task-rest_bold.nii.gz"
60
+ ),
61
+ "space": "MNI152NLin6Asym",
62
+ "mask": {
63
+ "pattern": (
64
+ "{subject}/{session}/func/"
65
+ "{subject}_{session}_task-rest_brain_mask.nii.gz"
66
+ ),
67
+ "space": "MNI152NLin6Asym",
68
+ },
69
+ },
70
+ },
58
71
  replacements=replacements,
59
72
  )
60
73
 
@@ -73,14 +86,17 @@ def test_MultipleDataGrabber() -> None:
73
86
  with dg:
74
87
  subs = list(dg)
75
88
  assert set(subs) == set(expected_subs)
76
-
89
+ # Check data type
77
90
  elem = dg[("sub-01", "ses-01")]
91
+ # Check data types
78
92
  assert "T1w" in elem
79
93
  assert "BOLD" in elem
94
+ # Check meta
80
95
  assert "meta" in elem["BOLD"]
81
96
  meta = elem["BOLD"]["meta"]["datagrabber"]
82
97
  assert "class" in meta
83
98
  assert meta["class"] == "MultipleDataGrabber"
99
+ # Check datagrabbers
84
100
  assert "datagrabbers" in meta
85
101
  assert len(meta["datagrabbers"]) == 2
86
102
  assert meta["datagrabbers"][0]["class"] == "PatternDataladDataGrabber"
@@ -89,40 +105,37 @@ def test_MultipleDataGrabber() -> None:
89
105
 
90
106
  def test_MultipleDataGrabber_no_intersection() -> None:
91
107
  """Test MultipleDataGrabber without intersection (0 elements)."""
92
- repo_uri1 = _testing_dataset["example_bids"]["uri"]
93
- repo_uri2 = _testing_dataset["example_bids_ses"]["uri"]
94
108
  rootdir = "example_bids_ses"
95
109
  replacements = ["subject", "session"]
96
- pattern1 = {
97
- "T1w": {
98
- "pattern": (
99
- "{subject}/{session}/anat/{subject}_{session}_T1w.nii.gz"
100
- ),
101
- "space": "native",
102
- },
103
- }
104
- pattern2 = {
105
- "BOLD": {
106
- "pattern": (
107
- "{subject}/{session}/func/"
108
- "{subject}_{session}_task-rest_bold.nii.gz"
109
- ),
110
- "space": "MNI152NLin6Asym",
111
- },
112
- }
110
+
113
111
  dg1 = PatternDataladDataGrabber(
114
112
  rootdir=rootdir,
115
- uri=repo_uri1,
113
+ uri=_testing_dataset["example_bids"]["uri"],
116
114
  types=["T1w"],
117
- patterns=pattern1,
115
+ patterns={
116
+ "T1w": {
117
+ "pattern": (
118
+ "{subject}/{session}/anat/{subject}_{session}_T1w.nii.gz"
119
+ ),
120
+ "space": "native",
121
+ },
122
+ },
118
123
  replacements=replacements,
119
124
  )
120
125
 
121
126
  dg2 = PatternDataladDataGrabber(
122
127
  rootdir=rootdir,
123
- uri=repo_uri2,
128
+ uri=_testing_dataset["example_bids_ses"]["uri"],
124
129
  types=["BOLD"],
125
- patterns=pattern2,
130
+ patterns={
131
+ "BOLD": {
132
+ "pattern": (
133
+ "{subject}/{session}/func/"
134
+ "{subject}_{session}_task-rest_bold.nii.gz"
135
+ ),
136
+ "space": "MNI152NLin6Asym",
137
+ },
138
+ },
126
139
  replacements=replacements,
127
140
  )
128
141
 
@@ -135,23 +148,19 @@ def test_MultipleDataGrabber_no_intersection() -> None:
135
148
 
136
149
  def test_MultipleDataGrabber_get_item() -> None:
137
150
  """Test MultipleDataGrabber get_item() error."""
138
- repo_uri1 = _testing_dataset["example_bids"]["uri"]
139
- rootdir = "example_bids_ses"
140
- replacements = ["subject", "session"]
141
- pattern1 = {
142
- "T1w": {
143
- "pattern": (
144
- "{subject}/{session}/anat/{subject}_{session}_T1w.nii.gz"
145
- ),
146
- "space": "native",
147
- },
148
- }
149
151
  dg1 = PatternDataladDataGrabber(
150
- rootdir=rootdir,
151
- uri=repo_uri1,
152
+ rootdir="example_bids_ses",
153
+ uri=_testing_dataset["example_bids"]["uri"],
152
154
  types=["T1w"],
153
- patterns=pattern1,
154
- replacements=replacements,
155
+ patterns={
156
+ "T1w": {
157
+ "pattern": (
158
+ "{subject}/{session}/anat/{subject}_{session}_T1w.nii.gz"
159
+ ),
160
+ "space": "native",
161
+ },
162
+ },
163
+ replacements=["subject", "session"],
155
164
  )
156
165
 
157
166
  dg = MultipleDataGrabber([dg1])
@@ -161,43 +170,111 @@ def test_MultipleDataGrabber_get_item() -> None:
161
170
 
162
171
  def test_MultipleDataGrabber_validation() -> None:
163
172
  """Test MultipleDataGrabber init validation."""
164
- repo_uri1 = _testing_dataset["example_bids"]["uri"]
165
- repo_uri2 = _testing_dataset["example_bids_ses"]["uri"]
166
173
  rootdir = "example_bids_ses"
167
- replacement1 = ["subject", "session"]
168
- replacement2 = ["subject"]
169
- pattern1 = {
170
- "T1w": {
171
- "pattern": (
172
- "{subject}/{session}/anat/{subject}_{session}_T1w.nii.gz"
173
- ),
174
- "space": "native",
175
- },
176
- }
177
- pattern2 = {
178
- "BOLD": {
179
- "pattern": "{subject}/func/{subject}_task-rest_bold.nii.gz",
180
- "space": "MNI152NLin6Asym",
181
- },
182
- }
174
+
183
175
  dg1 = PatternDataladDataGrabber(
184
176
  rootdir=rootdir,
185
- uri=repo_uri1,
177
+ uri=_testing_dataset["example_bids"]["uri"],
186
178
  types=["T1w"],
187
- patterns=pattern1,
188
- replacements=replacement1,
179
+ patterns={
180
+ "T1w": {
181
+ "pattern": (
182
+ "{subject}/{session}/anat/{subject}_{session}_T1w.nii.gz"
183
+ ),
184
+ "space": "native",
185
+ },
186
+ },
187
+ replacements=["subject", "session"],
189
188
  )
190
189
 
191
190
  dg2 = PatternDataladDataGrabber(
192
191
  rootdir=rootdir,
193
- uri=repo_uri2,
192
+ uri=_testing_dataset["example_bids_ses"]["uri"],
194
193
  types=["BOLD"],
195
- patterns=pattern2,
196
- replacements=replacement2,
194
+ patterns={
195
+ "BOLD": {
196
+ "pattern": "{subject}/func/{subject}_task-rest_bold.nii.gz",
197
+ "space": "MNI152NLin6Asym",
198
+ },
199
+ },
200
+ replacements=["subject"],
197
201
  )
198
202
 
199
- with pytest.raises(ValueError, match="different element key"):
203
+ with pytest.raises(RuntimeError, match="have different element keys"):
200
204
  MultipleDataGrabber([dg1, dg2])
201
205
 
202
- with pytest.raises(ValueError, match="overlapping types"):
206
+ with pytest.raises(RuntimeError, match="have overlapping mandatory"):
203
207
  MultipleDataGrabber([dg1, dg1])
208
+
209
+
210
+ def test_MultipleDataGrabber_partial_pattern() -> None:
211
+ """Test MultipleDataGrabber partial pattern."""
212
+ repo_uri = _testing_dataset["example_bids_ses"]["uri"]
213
+ rootdir = "example_bids_ses"
214
+ replacements = ["subject", "session"]
215
+
216
+ dg1 = PatternDataladDataGrabber(
217
+ rootdir=rootdir,
218
+ uri=repo_uri,
219
+ types=["BOLD"],
220
+ patterns={
221
+ "BOLD": {
222
+ "pattern": (
223
+ "{subject}/{session}/func/"
224
+ "{subject}_{session}_task-rest_bold.nii.gz"
225
+ ),
226
+ "space": "MNI152NLin6Asym",
227
+ },
228
+ },
229
+ replacements=replacements,
230
+ )
231
+
232
+ dg2 = PatternDataladDataGrabber(
233
+ rootdir=rootdir,
234
+ uri=repo_uri,
235
+ types=["BOLD"],
236
+ patterns={
237
+ "BOLD": {
238
+ "confounds": {
239
+ "pattern": (
240
+ "{subject}/{session}/func/"
241
+ "{subject}_{session}_task-rest_"
242
+ "confounds_regressors.tsv"
243
+ ),
244
+ "format": "fmriprep",
245
+ },
246
+ },
247
+ },
248
+ replacements=["subject", "session"],
249
+ partial_pattern_ok=True,
250
+ )
251
+
252
+ dg = MultipleDataGrabber([dg1, dg2])
253
+
254
+ types = dg.get_types()
255
+ assert "BOLD" in types
256
+
257
+ expected_subs = [
258
+ (f"sub-{i:02d}", f"ses-{j:02d}")
259
+ for j in range(1, 3)
260
+ for i in range(1, 10)
261
+ ]
262
+
263
+ with dg:
264
+ subs = list(dg)
265
+ assert set(subs) == set(expected_subs)
266
+ # Fetch element
267
+ elem = dg[("sub-01", "ses-01")]
268
+ # Check data type and nested data type
269
+ assert "BOLD" in elem
270
+ assert "confounds" in elem["BOLD"]
271
+ # Check meta
272
+ assert "meta" in elem["BOLD"]
273
+ meta = elem["BOLD"]["meta"]["datagrabber"]
274
+ assert "class" in meta
275
+ assert meta["class"] == "MultipleDataGrabber"
276
+ # Check datagrabbers
277
+ assert "datagrabbers" in meta
278
+ assert len(meta["datagrabbers"]) == 2
279
+ assert meta["datagrabbers"][0]["class"] == "PatternDataladDataGrabber"
280
+ assert meta["datagrabbers"][1]["class"] == "PatternDataladDataGrabber"
@@ -236,6 +236,51 @@ def test_PatternDataGrabber(tmp_path: Path) -> None:
236
236
  assert out1["VBM_GM"]["path"] != out2["VBM_GM"]["path"]
237
237
 
238
238
 
239
+ def test_PatternDataGrabber_unix_path_expansion(tmp_path: Path) -> None:
240
+ """Test PatterDataGrabber for patterns with unix path expansion.
241
+
242
+ Parameters
243
+ ----------
244
+ tmp_path : pathlib.Path
245
+ The path to the test directory.
246
+
247
+ """
248
+ # Create test data root dir
249
+ freesurfer_dir = tmp_path / "derivatives" / "freesurfer"
250
+ freesurfer_dir.mkdir(parents=True, exist_ok=True)
251
+ # Create test data sub dirs and files
252
+ for dir_name in ["fsaverage", "sub-0001"]:
253
+ mri_dir = freesurfer_dir / dir_name / "mri"
254
+ mri_dir.mkdir(parents=True, exist_ok=True)
255
+ # Create files
256
+ (mri_dir / "T1.mgz").touch(exist_ok=True)
257
+ (mri_dir / "aseg.mgz").touch(exist_ok=True)
258
+ # Create datagrabber
259
+ dg = PatternDataGrabber(
260
+ datadir=tmp_path,
261
+ types=["FreeSurfer"],
262
+ patterns={
263
+ "FreeSurfer": {
264
+ "pattern": "derivatives/freesurfer/[!f]{subject}/mri/T1.mg[z]",
265
+ "aseg": {
266
+ "pattern": (
267
+ "derivatives/freesurfer/[!f]{subject}/mri/aseg.mg[z]"
268
+ )
269
+ },
270
+ },
271
+ },
272
+ replacements=["subject"],
273
+ )
274
+ # Check that "fsaverage" is filtered
275
+ elements = dg.get_elements()
276
+ assert elements == ["sub-0001"]
277
+ # Fetch data
278
+ out = dg["sub-0001"]
279
+ # Check paths are found
280
+ assert set(out["FreeSurfer"].keys()) == {"path", "aseg", "meta"}
281
+ assert list(out["FreeSurfer"]["aseg"].keys()) == ["path"]
282
+
283
+
239
284
  def test_PatternDataGrabber_confounds_format_error_on_init() -> None:
240
285
  """Test PatterDataGrabber confounds format error on initialisation."""
241
286
  with pytest.raises(
@@ -15,13 +15,13 @@ from junifer.datagrabber import PatternDataladDataGrabber
15
15
  _testing_dataset = {
16
16
  "example_bids": {
17
17
  "uri": "https://gin.g-node.org/juaml/datalad-example-bids",
18
- "commit": "522dfb203afcd2cd55799bf347f9b211919a7338",
19
- "id": "fec92475-d9c0-4409-92ba-f041b6a12c40",
18
+ "commit": "b87897cbe51bf0ee5514becaa5c7dd76491db5ad",
19
+ "id": "8fddff30-6993-420a-9d1e-b5b028c59468",
20
20
  },
21
21
  "example_bids_ses": {
22
22
  "uri": "https://gin.g-node.org/juaml/datalad-example-bids-ses",
23
- "commit": "3d08d55d1faad4f12ab64ac9497544a0d924d47a",
24
- "id": "c83500d0-532f-45be-baf1-0dab703bdc2a",
23
+ "commit": "6b163aa98af76a9eac0272273c27e14127850181",
24
+ "id": "715c17cf-a1b9-42d6-9af8-9f74c1a4a724",
25
25
  },
26
26
  }
27
27
 
@@ -0,0 +1,249 @@
1
+ """Provide tests for PatternValidationMixin."""
2
+
3
+ # Authors: Federico Raimondo <f.raimondo@fz-juelich.de>
4
+ # Synchon Mandal <s.mandal@fz-juelich.de>
5
+ # License: AGPL
6
+
7
+ from contextlib import nullcontext
8
+ from typing import ContextManager, Dict, List, Union
9
+
10
+ import pytest
11
+
12
+ from junifer.datagrabber.pattern_validation_mixin import PatternValidationMixin
13
+
14
+
15
+ @pytest.mark.parametrize(
16
+ "types, replacements, patterns, expect",
17
+ [
18
+ (
19
+ "wrong",
20
+ [],
21
+ {},
22
+ pytest.raises(TypeError, match="`types` must be a list"),
23
+ ),
24
+ (
25
+ [1],
26
+ [],
27
+ {},
28
+ pytest.raises(
29
+ TypeError, match="`types` must be a list of strings"
30
+ ),
31
+ ),
32
+ (
33
+ ["BOLD"],
34
+ [],
35
+ "wrong",
36
+ pytest.raises(TypeError, match="`patterns` must be a dict"),
37
+ ),
38
+ (
39
+ ["T1w", "BOLD"],
40
+ "",
41
+ {
42
+ "T1w": {"pattern": "{subject}/anat/{subject}_T1w.nii.gz"},
43
+ },
44
+ pytest.raises(
45
+ ValueError,
46
+ match="Length of `types` more than that of `patterns`",
47
+ ),
48
+ ),
49
+ (
50
+ ["T1w", "BOLD"],
51
+ "",
52
+ {
53
+ "T1w": {"pattern": "{subject}/anat/{subject}_T1w.nii.gz"},
54
+ "T2w": {"pattern": "{subject}/anat/{subject}_T2w.nii.gz"},
55
+ },
56
+ pytest.raises(
57
+ ValueError, match="`patterns` must contain all `types`"
58
+ ),
59
+ ),
60
+ (
61
+ ["T3w"],
62
+ "",
63
+ {
64
+ "T3w": {"pattern": "{subject}/anat/{subject}_T3w.nii.gz"},
65
+ },
66
+ pytest.raises(ValueError, match="Unknown data type"),
67
+ ),
68
+ (
69
+ ["BOLD"],
70
+ "",
71
+ {
72
+ "BOLD": {"patterns": "{subject}/func/{subject}_BOLD.nii.gz"},
73
+ },
74
+ pytest.raises(KeyError, match="Mandatory key"),
75
+ ),
76
+ (
77
+ ["BOLD"],
78
+ "",
79
+ {
80
+ "BOLD": {
81
+ "pattern": (
82
+ "{subject}/func/{subject}_task-rest_bold.nii.gz"
83
+ ),
84
+ "space": "MNINLin6Asym",
85
+ "confounds": {
86
+ "pattern": "{subject}/func/{subject}_confounds.tsv",
87
+ "format": "fmriprep",
88
+ },
89
+ "zip": "zap",
90
+ },
91
+ },
92
+ pytest.raises(RuntimeError, match="not accepted"),
93
+ ),
94
+ (
95
+ ["T1w"],
96
+ "",
97
+ {
98
+ "T1w": {
99
+ "pattern": "{subject}/anat/{subject}*.nii",
100
+ "space": "native",
101
+ },
102
+ },
103
+ pytest.raises(ValueError, match="following a replacement"),
104
+ ),
105
+ (
106
+ ["T1w"],
107
+ "wrong",
108
+ {
109
+ "T1w": {
110
+ "pattern": "{subject}/anat/{subject}_T1w.nii",
111
+ "space": "native",
112
+ },
113
+ },
114
+ pytest.raises(TypeError, match="`replacements` must be a list"),
115
+ ),
116
+ (
117
+ ["T1w"],
118
+ [1],
119
+ {
120
+ "T1w": {
121
+ "pattern": "{subject}/anat/{subject}_T1w.nii",
122
+ "space": "native",
123
+ },
124
+ },
125
+ pytest.raises(
126
+ TypeError, match="`replacements` must be a list of strings"
127
+ ),
128
+ ),
129
+ (
130
+ ["T1w", "BOLD"],
131
+ ["subject", "session"],
132
+ {
133
+ "T1w": {
134
+ "pattern": "{subject}/anat/{subject}_T1w.nii.gz",
135
+ "space": "native",
136
+ },
137
+ "BOLD": {
138
+ "pattern": (
139
+ "{subject}/func/{subject}_task-rest_bold.nii.gz"
140
+ ),
141
+ "space": "MNI152NLin6Asym",
142
+ },
143
+ },
144
+ pytest.raises(ValueError, match="is not part of any pattern"),
145
+ ),
146
+ (
147
+ ["BOLD"],
148
+ ["subject", "session"],
149
+ {
150
+ "T1w": {
151
+ "pattern": "{subject}/anat/_T1w.nii.gz",
152
+ "space": "native",
153
+ },
154
+ "BOLD": {
155
+ "pattern": "{session}/func/_task-rest_bold.nii.gz",
156
+ "space": "MNI152NLin6Asym",
157
+ },
158
+ },
159
+ pytest.raises(ValueError, match="At least one pattern"),
160
+ ),
161
+ (
162
+ ["T1w", "T2w", "BOLD"],
163
+ ["subject"],
164
+ {
165
+ "T1w": {
166
+ "pattern": "{subject}/anat/{subject}_T1w.nii.gz",
167
+ "space": "native",
168
+ },
169
+ "T2w": {
170
+ "pattern": "{subject}/anat/{subject}_T2w.nii.gz",
171
+ "space": "native",
172
+ },
173
+ "BOLD": {
174
+ "pattern": (
175
+ "{subject}/func/{session}/{subject}_task-rest_bold.nii.gz"
176
+ ),
177
+ "space": "MNI152NLin6Asym",
178
+ "confounds": {
179
+ "pattern": "{subject}/func/{subject}_confounds.tsv",
180
+ "format": "fmriprep",
181
+ },
182
+ },
183
+ },
184
+ nullcontext(),
185
+ ),
186
+ ],
187
+ )
188
+ def test_PatternValidationMixin(
189
+ types: Union[str, List[str], List[int]],
190
+ replacements: Union[str, List[str], List[int]],
191
+ patterns: Union[str, Dict[str, Dict[str, str]]],
192
+ expect: ContextManager,
193
+ ) -> None:
194
+ """Test validation.
195
+
196
+ Parameters
197
+ ----------
198
+ types : str, list of int or str
199
+ The parametrized data types to validate.
200
+ replacements : str, list of str or int
201
+ The parametrized pattern replacements to validate.
202
+ patterns : str, dict
203
+ The parametrized patterns to validate against.
204
+ expect : typing.ContextManager
205
+ The parametrized ContextManager object.
206
+
207
+ """
208
+
209
+ class MockDataGrabber(PatternValidationMixin):
210
+ def __init__(
211
+ self,
212
+ types,
213
+ replacements,
214
+ patterns,
215
+ ) -> None:
216
+ self.types = types
217
+ self.replacements = replacements
218
+ self.patterns = patterns
219
+
220
+ def validate(self) -> None:
221
+ self.validate_patterns(
222
+ types=self.types,
223
+ replacements=self.replacements,
224
+ patterns=self.patterns,
225
+ )
226
+
227
+ dg = MockDataGrabber(types, replacements, patterns)
228
+ with expect:
229
+ dg.validate()
230
+
231
+
232
+ # This test is kept separate as bool doesn't support context manager protocol,
233
+ # used in the earlier test
234
+ def test_PatternValidationMixin_partial_pattern_check() -> None:
235
+ """Test validation for partial patterns."""
236
+ with pytest.warns(RuntimeWarning, match="might not work as expected"):
237
+ PatternValidationMixin().validate_patterns(
238
+ types=["BOLD"],
239
+ replacements=["subject"],
240
+ patterns={
241
+ "BOLD": {
242
+ "mask": {
243
+ "pattern": "{subject}/func/{subject}_BOLD.nii.gz",
244
+ "space": "MNI152NLin6Asym",
245
+ },
246
+ },
247
+ }, # type: ignore
248
+ partial_pattern_ok=True,
249
+ )
@@ -1,4 +1,4 @@
1
- """Provide imports for datareader sub-package."""
1
+ """DataReaders for datasets' data loading."""
2
2
 
3
3
  # Authors: Federico Raimondo <f.raimondo@fz-juelich.de>
4
4
  # Leonard Sasse <l.sasse@fz-juelich.de>
@@ -6,3 +6,6 @@
6
6
  # License: AGPL
7
7
 
8
8
  from .default import DefaultDataReader
9
+
10
+
11
+ __all__ = ["DefaultDataReader"]