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
@@ -4,12 +4,14 @@
4
4
  # License: AGPL
5
5
 
6
6
  import warnings
7
+ from typing import List, Tuple
7
8
 
8
9
  import nibabel
9
10
  import numpy as np
10
11
  import pytest
11
12
  from nilearn._utils import data_gen
12
13
  from nilearn.image import get_data
14
+ from nilearn.maskers import NiftiSpheresMasker
13
15
  from numpy.testing import assert_array_equal
14
16
 
15
17
  from junifer.external.nilearn import JuniferNiftiSpheresMasker
@@ -17,7 +19,7 @@ from junifer.external.nilearn import JuniferNiftiSpheresMasker
17
19
 
18
20
  # New BSD License
19
21
 
20
- # Copyright (c) 2007 - 2022 The nilearn developers.
22
+ # Copyright (c) The nilearn developers.
21
23
  # All rights reserved.
22
24
 
23
25
 
@@ -331,3 +333,76 @@ def test_nifti_spheres_masker_io_shapes() -> None:
331
333
  )
332
334
  test_data = masker.transform(img_4d)
333
335
  assert test_data.shape == (n_volumes, n_regions)
336
+
337
+
338
+ @pytest.mark.parametrize(
339
+ "shape",
340
+ [
341
+ (10, 11, 12),
342
+ (10, 11, 12, 5),
343
+ ],
344
+ )
345
+ @pytest.mark.parametrize(
346
+ "radius, allow_overlap",
347
+ [
348
+ (2.0, True),
349
+ (2.0, False),
350
+ (3.0, True),
351
+ (4.0, True),
352
+ (5.0, True),
353
+ ],
354
+ )
355
+ @pytest.mark.parametrize(
356
+ "coords",
357
+ [
358
+ [(1, 1, 1)],
359
+ [(1, 1, 1), (4, 4, 4)],
360
+ [(1, 1, 1), (4, 4, 4), (10, 10, 10)],
361
+ ],
362
+ )
363
+ def test_junifer_and_nilearn_mean_agg_are_equal(
364
+ shape: Tuple[int, ...],
365
+ radius: float,
366
+ allow_overlap: bool,
367
+ coords: List[Tuple[int, int, int]],
368
+ ) -> None:
369
+ """Test junifer's masker behaves same as nilearn's when agg is mean.
370
+
371
+ Parameters
372
+ ----------
373
+ shape : tuple of int
374
+ The parametrized shape of the input image.
375
+ radius : float
376
+ The parametrized radius of the spheres.
377
+ allow_overlap : bool
378
+ The parametrized option to overlap spheres or not.
379
+ coords : list of tuple of int, int and int
380
+ The parametrized seeds.
381
+
382
+ """
383
+ # Set affine
384
+ affine = np.eye(4)
385
+ # Generate random image
386
+ input_img, mask_img = data_gen.generate_random_img(
387
+ shape=shape,
388
+ affine=affine,
389
+ )
390
+ # Compute junifer's version
391
+ junifer_masker = JuniferNiftiSpheresMasker(
392
+ seeds=coords,
393
+ radius=radius,
394
+ allow_overlap=allow_overlap,
395
+ mask_img=mask_img,
396
+ )
397
+ junifer_output = junifer_masker.fit_transform(input_img)
398
+ # Compute nilearn's version
399
+ nilearn_masker = NiftiSpheresMasker(
400
+ seeds=coords,
401
+ radius=radius,
402
+ allow_overlap=allow_overlap,
403
+ mask_img=mask_img,
404
+ )
405
+ nilearn_output = nilearn_masker.fit_transform(input_img)
406
+ # Checks
407
+ assert junifer_output.shape == nilearn_output.shape
408
+ np.testing.assert_almost_equal(junifer_output, nilearn_output)
@@ -1,4 +1,4 @@
1
- """Provide imports for markers sub-package."""
1
+ """Markers for feature extraction."""
2
2
 
3
3
  # Authors: Federico Raimondo <f.raimondo@fz-juelich.de>
4
4
  # Leonard Sasse <l.sasse@fz-juelich.de>
@@ -23,3 +23,25 @@ from .temporal_snr import (
23
23
  TemporalSNRParcels,
24
24
  TemporalSNRSpheres,
25
25
  )
26
+ from .brainprint import BrainPrint
27
+
28
+
29
+ __all__ = [
30
+ "BaseMarker",
31
+ "MarkerCollection",
32
+ "RSSETSMarker",
33
+ "ParcelAggregation",
34
+ "SphereAggregation",
35
+ "FunctionalConnectivityParcels",
36
+ "FunctionalConnectivitySpheres",
37
+ "CrossParcellationFC",
38
+ "EdgeCentricFCParcels",
39
+ "EdgeCentricFCSpheres",
40
+ "ReHoParcels",
41
+ "ReHoSpheres",
42
+ "ALFFParcels",
43
+ "ALFFSpheres",
44
+ "TemporalSNRParcels",
45
+ "TemporalSNRSpheres",
46
+ "BrainPrint",
47
+ ]
junifer/markers/base.py CHANGED
@@ -5,6 +5,7 @@
5
5
  # License: AGPL
6
6
 
7
7
  from abc import ABC, abstractmethod
8
+ from copy import deepcopy
8
9
  from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
9
10
 
10
11
  from ..pipeline import PipelineStepMixin, UpdateMetaMixin
@@ -15,20 +16,28 @@ if TYPE_CHECKING:
15
16
  from junifer.storage import BaseFeatureStorage
16
17
 
17
18
 
19
+ __all__ = ["BaseMarker"]
20
+
21
+
18
22
  class BaseMarker(ABC, PipelineStepMixin, UpdateMetaMixin):
19
23
  """Abstract base class for all markers.
20
24
 
25
+ For every interface that is required, one needs to provide a concrete
26
+ implementation of this abstract class.
27
+
21
28
  Parameters
22
29
  ----------
23
- on : str or list of str
24
- The kind of data to apply the marker to. By default, will work on all
25
- available data.
30
+ on : str or list of str or None, optional
31
+ The data type to apply the marker on. If None,
32
+ will work on all available data types (default None).
26
33
  name : str, optional
27
- The name of the marker. By default, it will use the class name as the
34
+ The name of the marker. If None, will use the class name as the
28
35
  name of the marker (default None).
29
36
 
30
37
  Raises
31
38
  ------
39
+ AttributeError
40
+ If the marker does not have `_MARKER_INOUT_MAPPINGS` attribute.
32
41
  ValueError
33
42
  If required input data type(s) is(are) not found.
34
43
 
@@ -39,6 +48,12 @@ class BaseMarker(ABC, PipelineStepMixin, UpdateMetaMixin):
39
48
  on: Optional[Union[List[str], str]] = None,
40
49
  name: Optional[str] = None,
41
50
  ) -> None:
51
+ # Check for missing mapping attribute
52
+ if not hasattr(self, "_MARKER_INOUT_MAPPINGS"):
53
+ raise_error(
54
+ msg=("Missing `_MARKER_INOUT_MAPPINGS` for the marker"),
55
+ klass=AttributeError,
56
+ )
42
57
  # Use all data types if not provided
43
58
  if on is None:
44
59
  on = self.get_valid_inputs()
@@ -82,7 +97,6 @@ class BaseMarker(ABC, PipelineStepMixin, UpdateMetaMixin):
82
97
  )
83
98
  return [x for x in self._on if x in input]
84
99
 
85
- @abstractmethod
86
100
  def get_valid_inputs(self) -> List[str]:
87
101
  """Get valid data types for input.
88
102
 
@@ -92,30 +106,25 @@ class BaseMarker(ABC, PipelineStepMixin, UpdateMetaMixin):
92
106
  The list of data types that can be used as input for this marker.
93
107
 
94
108
  """
95
- raise_error(
96
- msg="Concrete classes need to implement get_valid_inputs().",
97
- klass=NotImplementedError,
98
- )
109
+ return list(self._MARKER_INOUT_MAPPINGS.keys())
99
110
 
100
- @abstractmethod
101
- def get_output_type(self, input_type: str) -> str:
111
+ def get_output_type(self, input_type: str, output_feature: str) -> str:
102
112
  """Get output type.
103
113
 
104
114
  Parameters
105
115
  ----------
106
116
  input_type : str
107
117
  The data type input to the marker.
118
+ output_feature : str
119
+ The feature output of the marker.
108
120
 
109
121
  Returns
110
122
  -------
111
123
  str
112
- The storage type output by the marker.
124
+ The storage type output of the marker.
113
125
 
114
126
  """
115
- raise_error(
116
- msg="Concrete classes need to implement get_output_type().",
117
- klass=NotImplementedError,
118
- )
127
+ return self._MARKER_INOUT_MAPPINGS[input_type][output_feature]
119
128
 
120
129
  @abstractmethod
121
130
  def compute(self, input: Dict, extra_input: Optional[Dict] = None) -> Dict:
@@ -148,6 +157,7 @@ class BaseMarker(ABC, PipelineStepMixin, UpdateMetaMixin):
148
157
  def store(
149
158
  self,
150
159
  type_: str,
160
+ feature: str,
151
161
  out: Dict[str, Any],
152
162
  storage: "BaseFeatureStorage",
153
163
  ) -> None:
@@ -157,13 +167,15 @@ class BaseMarker(ABC, PipelineStepMixin, UpdateMetaMixin):
157
167
  ----------
158
168
  type_ : str
159
169
  The data type to store.
170
+ feature : str
171
+ The feature to store.
160
172
  out : dict
161
173
  The computed result as a dictionary to store.
162
174
  storage : storage-like
163
175
  The storage class, for example, SQLiteFeatureStorage.
164
176
 
165
177
  """
166
- output_type_ = self.get_output_type(type_)
178
+ output_type_ = self.get_output_type(type_, feature)
167
179
  logger.debug(f"Storing {output_type_} in {storage}")
168
180
  storage.store(kind=output_type_, **out)
169
181
 
@@ -192,22 +204,50 @@ class BaseMarker(ABC, PipelineStepMixin, UpdateMetaMixin):
192
204
  for type_ in self._on:
193
205
  if type_ in input.keys():
194
206
  logger.info(f"Computing {type_}")
207
+ # Get data dict for data type
195
208
  t_input = input[type_]
209
+ # Pass the other data types as extra input, removing
210
+ # the current type
196
211
  extra_input = input.copy()
197
212
  extra_input.pop(type_)
213
+ logger.debug(
214
+ f"Extra data type for feature extraction: "
215
+ f"{extra_input.keys()}"
216
+ )
217
+ # Copy metadata
198
218
  t_meta = t_input["meta"].copy()
199
219
  t_meta["type"] = type_
200
-
220
+ # Compute marker
201
221
  t_out = self.compute(input=t_input, extra_input=extra_input)
202
- t_out["meta"] = t_meta
203
-
204
- self.update_meta(t_out, "marker")
205
-
206
- if storage is not None:
207
- logger.info(f"Storing in {storage}")
208
- self.store(type_=type_, out=t_out, storage=storage)
209
- else:
210
- logger.info("No storage specified, returning dictionary")
211
- out[type_] = t_out
222
+ # Initialize empty dictionary if no storage object is provided
223
+ if storage is None:
224
+ out[type_] = {}
225
+ # Store individual features
226
+ for feature_name, feature_data in t_out.items():
227
+ # Make deep copy of the feature data for manipulation
228
+ feature_data_copy = deepcopy(feature_data)
229
+ # Make deep copy of metadata and add to feature data
230
+ feature_data_copy["meta"] = deepcopy(t_meta)
231
+ # Update metadata for the feature,
232
+ # feature data is not manipulated, only meta
233
+ self.update_meta(feature_data_copy, "marker")
234
+ # Update marker feature's metadata name
235
+ feature_data_copy["meta"]["marker"][
236
+ "name"
237
+ ] += f"_{feature_name}"
238
+
239
+ if storage is not None:
240
+ logger.info(f"Storing in {storage}")
241
+ self.store(
242
+ type_=type_,
243
+ feature=feature_name,
244
+ out=feature_data_copy,
245
+ storage=storage,
246
+ )
247
+ else:
248
+ logger.info(
249
+ "No storage specified, returning dictionary"
250
+ )
251
+ out[type_][feature_name] = feature_data_copy
212
252
 
213
253
  return out