junifer 0.0.7.dev332__py3-none-any.whl → 0.0.8__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.
- junifer/_version.py +9 -19
- junifer/api/__init__.pyi +17 -2
- junifer/api/functions.py +222 -39
- junifer/api/queue_context/__init__.pyi +11 -3
- junifer/api/queue_context/gnu_parallel_local_adapter.py +81 -120
- junifer/api/queue_context/htcondor_adapter.py +118 -181
- junifer/api/queue_context/queue_context_adapter.py +51 -7
- junifer/api/queue_context/tests/test_gnu_parallel_local_adapter.py +10 -35
- junifer/api/queue_context/tests/test_htcondor_adapter.py +13 -50
- junifer/api/res/fsl/run_fsl_docker.sh +4 -0
- junifer/api/tests/test_functions.py +863 -36
- junifer/cli/__init__.py +1 -7
- junifer/cli/cli.py +41 -22
- junifer/cli/parser.py +23 -147
- junifer/cli/tests/data/gmd_mean.yaml +6 -6
- junifer/cli/tests/data/gmd_mean_htcondor.yaml +6 -5
- junifer/cli/tests/data/partly_cloudy_agg_mean_tian.yml +1 -0
- junifer/cli/tests/test_cli.py +120 -4
- junifer/cli/tests/test_cli_utils.py +23 -6
- junifer/cli/utils.py +14 -9
- junifer/configs/__init__.py +5 -0
- junifer/configs/__init__.pyi +0 -0
- junifer/configs/juseless/datagrabbers/__init__.pyi +4 -2
- junifer/configs/juseless/datagrabbers/aomic_id1000_vbm.py +18 -24
- junifer/configs/juseless/datagrabbers/camcan_vbm.py +20 -27
- junifer/configs/juseless/datagrabbers/ixi_vbm.py +44 -52
- junifer/configs/juseless/datagrabbers/tests/test_ixi_vbm.py +0 -7
- junifer/configs/juseless/datagrabbers/tests/test_ucla.py +2 -19
- junifer/configs/juseless/datagrabbers/ucla.py +111 -104
- junifer/configs/juseless/datagrabbers/ukb_vbm.py +19 -25
- junifer/configs/py.typed +0 -0
- junifer/conftest.py +63 -24
- junifer/data/_dispatch.py +9 -19
- junifer/data/coordinates/_ants_coordinates_warper.py +4 -1
- junifer/data/coordinates/_coordinates.py +51 -6
- junifer/data/coordinates/_fsl_coordinates_warper.py +4 -1
- junifer/data/coordinates/tests/test_coordinates.py +15 -3
- junifer/data/coordinates/tests/test_coordinates_names.py +37 -0
- junifer/data/maps/_ants_maps_warper.py +18 -12
- junifer/data/maps/_fsl_maps_warper.py +7 -3
- junifer/data/maps/_maps.py +12 -8
- junifer/data/maps/tests/test_maps.py +13 -21
- junifer/data/masks/_ants_mask_warper.py +18 -12
- junifer/data/masks/_fsl_mask_warper.py +7 -3
- junifer/data/masks/_masks.py +15 -12
- junifer/data/masks/tests/test_masks.py +20 -17
- junifer/data/parcellations/_ants_parcellation_warper.py +18 -12
- junifer/data/parcellations/_fsl_parcellation_warper.py +7 -5
- junifer/data/parcellations/_parcellations.py +125 -91
- junifer/data/parcellations/tests/test_parcellations.py +122 -1
- junifer/data/template_spaces.py +117 -50
- junifer/data/tests/test_template_spaces.py +10 -7
- junifer/data/utils.py +10 -8
- junifer/datagrabber/__init__.pyi +38 -5
- junifer/datagrabber/aomic/__init__.pyi +8 -1
- junifer/datagrabber/aomic/_types.py +22 -0
- junifer/datagrabber/aomic/id1000.py +193 -183
- junifer/datagrabber/aomic/piop1.py +215 -259
- junifer/datagrabber/aomic/piop2.py +211 -244
- junifer/datagrabber/aomic/tests/test_id1000.py +70 -53
- junifer/datagrabber/aomic/tests/test_piop1.py +98 -102
- junifer/datagrabber/aomic/tests/test_piop2.py +88 -85
- junifer/datagrabber/base.py +253 -79
- junifer/datagrabber/datalad_base.py +127 -76
- junifer/datagrabber/dmcc13_benchmark.py +204 -289
- junifer/datagrabber/hcp1200/__init__.pyi +7 -2
- junifer/datagrabber/hcp1200/datalad_hcp1200.py +61 -40
- junifer/datagrabber/hcp1200/hcp1200.py +151 -164
- junifer/datagrabber/hcp1200/tests/test_hcp1200.py +116 -128
- junifer/datagrabber/multiple.py +183 -63
- junifer/datagrabber/pattern.py +358 -227
- junifer/datagrabber/pattern_datalad.py +84 -124
- junifer/datagrabber/pattern_validation_mixin.py +22 -50
- junifer/datagrabber/tests/test_base.py +17 -4
- junifer/datagrabber/tests/test_datalad_base.py +20 -27
- junifer/datagrabber/tests/test_dmcc13_benchmark.py +110 -122
- junifer/datagrabber/tests/test_multiple.py +293 -31
- junifer/datagrabber/tests/test_pattern.py +577 -18
- junifer/datagrabber/tests/test_pattern_datalad.py +177 -107
- junifer/datagrabber/tests/test_pattern_validation_mixin.py +9 -51
- junifer/datareader/default.py +8 -5
- junifer/datareader/tests/test_default_reader.py +1 -1
- junifer/external/BrainPrint/.codecov.yml +2 -0
- junifer/external/BrainPrint/.codespellignore +2 -0
- junifer/external/BrainPrint/.github/ISSUE_TEMPLATE/bug-report.md +45 -0
- junifer/external/BrainPrint/.github/ISSUE_TEMPLATE/documentation.md +12 -0
- junifer/external/BrainPrint/.github/ISSUE_TEMPLATE/feature-request.md +24 -0
- junifer/external/BrainPrint/.github/ISSUE_TEMPLATE/questions-help-support.md +28 -0
- junifer/external/BrainPrint/.github/dependabot.yml +6 -0
- junifer/external/BrainPrint/.github/workflows/build.yml +53 -0
- junifer/external/BrainPrint/.github/workflows/code-style.yml +44 -0
- junifer/external/BrainPrint/.github/workflows/doc.yml +65 -0
- junifer/external/BrainPrint/.github/workflows/publish.yml +31 -0
- junifer/external/BrainPrint/.github/workflows/pytest.yml +54 -0
- junifer/external/BrainPrint/.gitignore +123 -0
- junifer/external/BrainPrint/LICENSE +21 -0
- junifer/external/BrainPrint/README.md +108 -0
- junifer/external/BrainPrint/brainprint/cli/__init__.py +11 -0
- junifer/external/BrainPrint/brainprint/cli/help_text.py +169 -0
- junifer/external/BrainPrint/brainprint/cli/parser.py +150 -0
- junifer/external/BrainPrint/brainprint/cli/utils.py +14 -0
- junifer/external/BrainPrint/brainprint/commands/__init__.py +0 -0
- junifer/external/BrainPrint/brainprint/commands/sys_info.py +18 -0
- junifer/external/BrainPrint/brainprint/utils/tests/__init__.py +0 -0
- junifer/external/BrainPrint/brainprint/utils/tests/test_config.py +33 -0
- junifer/external/BrainPrint/doc/Makefile +20 -0
- junifer/external/BrainPrint/doc/_static/.gitkeep +0 -0
- junifer/external/BrainPrint/doc/_templates/autosummary/class.rst +10 -0
- junifer/external/BrainPrint/doc/_templates/autosummary/function.rst +8 -0
- junifer/external/BrainPrint/doc/_templates/autosummary/module.rst +6 -0
- junifer/external/BrainPrint/doc/api/index.rst +11 -0
- junifer/external/BrainPrint/doc/conf.py +272 -0
- junifer/external/BrainPrint/doc/index.rst +140 -0
- junifer/external/BrainPrint/doc/links.inc +36 -0
- junifer/external/BrainPrint/doc/make.bat +35 -0
- junifer/external/BrainPrint/doc/references.bib +23 -0
- junifer/external/BrainPrint/pyproject.toml +170 -0
- junifer/external/BrainPrint/setup.py +3 -0
- junifer/external/__init__.py +5 -0
- junifer/external/__init__.pyi +0 -0
- junifer/external/nilearn/junifer_connectivity_measure.py +95 -39
- junifer/external/nilearn/junifer_nifti_spheres_masker.py +226 -67
- junifer/external/nilearn/tests/test_junifer_connectivity_measure.py +39 -27
- junifer/external/nilearn/tests/test_junifer_nifti_spheres_masker.py +231 -21
- junifer/external/py.typed +0 -0
- junifer/markers/__init__.pyi +6 -3
- junifer/markers/base.py +126 -76
- junifer/markers/brainprint.py +30 -39
- junifer/markers/complexity/__init__.py +20 -3
- junifer/markers/complexity/complexity_base.py +40 -41
- junifer/markers/complexity/hurst_exponent.py +22 -35
- junifer/markers/complexity/multiscale_entropy_auc.py +53 -41
- junifer/markers/complexity/perm_entropy.py +22 -36
- junifer/markers/complexity/range_entropy.py +22 -35
- junifer/markers/complexity/range_entropy_auc.py +27 -36
- junifer/markers/complexity/sample_entropy.py +22 -36
- junifer/markers/complexity/tests/test_complexity_registry.py +48 -0
- junifer/markers/complexity/tests/test_hurst_exponent.py +47 -36
- junifer/markers/complexity/tests/test_multiscale_entropy_auc.py +50 -36
- junifer/markers/complexity/tests/test_perm_entropy.py +48 -36
- junifer/markers/complexity/tests/test_range_entropy.py +47 -36
- junifer/markers/complexity/tests/test_range_entropy_auc.py +47 -36
- junifer/markers/complexity/tests/test_sample_entropy.py +48 -36
- junifer/markers/complexity/tests/test_weighted_perm_entropy.py +50 -36
- junifer/markers/complexity/weighted_perm_entropy.py +22 -36
- junifer/markers/ets_rss.py +35 -31
- junifer/markers/falff/__init__.pyi +7 -1
- junifer/markers/falff/_afni_falff.py +22 -9
- junifer/markers/falff/_junifer_falff.py +47 -31
- junifer/markers/falff/falff_base.py +80 -60
- junifer/markers/falff/falff_maps.py +17 -41
- junifer/markers/falff/falff_parcels.py +26 -51
- junifer/markers/falff/falff_spheres.py +34 -61
- junifer/markers/falff/tests/test_falff_maps.py +20 -23
- junifer/markers/falff/tests/test_falff_mask.py +99 -0
- junifer/markers/falff/tests/test_falff_parcels.py +14 -10
- junifer/markers/falff/tests/test_falff_spheres.py +14 -10
- junifer/markers/functional_connectivity/crossparcellation_functional_connectivity.py +32 -34
- junifer/markers/functional_connectivity/edge_functional_connectivity_maps.py +12 -25
- junifer/markers/functional_connectivity/edge_functional_connectivity_parcels.py +19 -34
- junifer/markers/functional_connectivity/edge_functional_connectivity_spheres.py +24 -44
- junifer/markers/functional_connectivity/functional_connectivity_base.py +50 -40
- junifer/markers/functional_connectivity/functional_connectivity_maps.py +12 -25
- junifer/markers/functional_connectivity/functional_connectivity_parcels.py +19 -34
- junifer/markers/functional_connectivity/functional_connectivity_spheres.py +24 -45
- junifer/markers/functional_connectivity/tests/test_crossparcellation_functional_connectivity.py +21 -22
- junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_maps.py +8 -7
- junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_parcels.py +8 -4
- junifer/markers/functional_connectivity/tests/test_edge_functional_connectivity_spheres.py +8 -4
- junifer/markers/functional_connectivity/tests/test_functional_connectivity_maps.py +8 -7
- junifer/markers/functional_connectivity/tests/test_functional_connectivity_parcels.py +9 -5
- junifer/markers/functional_connectivity/tests/test_functional_connectivity_spheres.py +8 -12
- junifer/markers/maps_aggregation.py +77 -55
- junifer/markers/parcel_aggregation.py +91 -70
- junifer/markers/reho/__init__.pyi +7 -1
- junifer/markers/reho/_afni_reho.py +23 -14
- junifer/markers/reho/_junifer_reho.py +112 -123
- junifer/markers/reho/reho_base.py +115 -37
- junifer/markers/reho/reho_maps.py +18 -32
- junifer/markers/reho/reho_parcels.py +26 -41
- junifer/markers/reho/reho_spheres.py +33 -54
- junifer/markers/reho/tests/test_junifer_reho.py +178 -0
- junifer/markers/reho/tests/test_reho_maps.py +27 -25
- junifer/markers/reho/tests/test_reho_parcels.py +28 -22
- junifer/markers/reho/tests/test_reho_spheres.py +16 -12
- junifer/markers/sphere_aggregation.py +88 -72
- junifer/markers/temporal_snr/temporal_snr_base.py +32 -30
- junifer/markers/temporal_snr/temporal_snr_maps.py +9 -18
- junifer/markers/temporal_snr/temporal_snr_parcels.py +19 -27
- junifer/markers/temporal_snr/temporal_snr_spheres.py +27 -41
- junifer/markers/temporal_snr/tests/test_temporal_snr_base.py +2 -2
- junifer/markers/temporal_snr/tests/test_temporal_snr_maps.py +17 -22
- junifer/markers/temporal_snr/tests/test_temporal_snr_parcels.py +11 -3
- junifer/markers/temporal_snr/tests/test_temporal_snr_spheres.py +9 -9
- junifer/markers/tests/test_brainprint.py +10 -9
- junifer/markers/tests/test_ets_rss.py +7 -3
- junifer/markers/tests/test_lost_regions.py +279 -0
- junifer/markers/tests/test_maps_aggregation.py +67 -114
- junifer/markers/tests/test_marker_utils.py +40 -1
- junifer/markers/tests/test_markers_base.py +275 -6
- junifer/markers/tests/test_parcel_aggregation.py +91 -53
- junifer/markers/tests/test_sphere_aggregation.py +53 -47
- junifer/markers/utils.py +267 -5
- junifer/onthefly/__init__.py +2 -3
- junifer/onthefly/__init__.pyi +4 -0
- junifer/onthefly/_brainprint.py +3 -5
- junifer/onthefly/py.typed +0 -0
- junifer/onthefly/read_transform.py +11 -9
- junifer/onthefly/tests/test_read_transform.py +7 -7
- junifer/pipeline/__init__.pyi +4 -2
- junifer/pipeline/marker_collection.py +14 -10
- junifer/pipeline/pipeline_component_registry.py +25 -6
- junifer/pipeline/pipeline_step_mixin.py +5 -12
- junifer/pipeline/tests/test_data_object_dumper.py +10 -10
- junifer/pipeline/tests/test_marker_collection.py +145 -168
- junifer/pipeline/tests/test_pipeline_component_registry.py +52 -8
- junifer/pipeline/tests/test_pipeline_step_mixin.py +12 -26
- junifer/pipeline/tests/test_update_meta_mixin.py +4 -5
- junifer/pipeline/tests/test_workdir_manager.py +39 -0
- junifer/pipeline/update_meta_mixin.py +13 -10
- junifer/pipeline/utils.py +22 -17
- junifer/pipeline/workdir_manager.py +41 -32
- junifer/preprocess/__init__.pyi +9 -4
- junifer/preprocess/_temporal_filter.py +34 -33
- junifer/preprocess/_temporal_slicer.py +17 -30
- junifer/preprocess/base.py +99 -55
- junifer/preprocess/confounds/__init__.pyi +10 -2
- junifer/preprocess/confounds/fmriprep_confound_remover.py +87 -109
- junifer/preprocess/confounds/tests/test_fmriprep_confound_remover.py +64 -45
- junifer/preprocess/smoothing/__init__.pyi +2 -2
- junifer/preprocess/smoothing/_afni_smoothing.py +8 -7
- junifer/preprocess/smoothing/_fsl_smoothing.py +6 -4
- junifer/preprocess/smoothing/_nilearn_smoothing.py +7 -5
- junifer/preprocess/smoothing/smoothing.py +59 -40
- junifer/preprocess/smoothing/tests/test_smoothing.py +28 -8
- junifer/preprocess/tests/test_preprocess_base.py +17 -9
- junifer/preprocess/tests/test_temporal_filter.py +37 -16
- junifer/preprocess/tests/test_temporal_slicer.py +12 -12
- junifer/preprocess/warping/__init__.pyi +2 -2
- junifer/preprocess/warping/_ants_warper.py +56 -40
- junifer/preprocess/warping/_fsl_warper.py +49 -36
- junifer/preprocess/warping/_utils.py +106 -0
- junifer/preprocess/warping/space_warper.py +99 -98
- junifer/preprocess/warping/tests/test_space_warper.py +46 -41
- junifer/preprocess/warping/tests/test_warping_utils.py +163 -0
- junifer/stats.py +46 -34
- junifer/storage/__init__.pyi +8 -4
- junifer/storage/base.py +129 -64
- junifer/storage/hdf5.py +307 -199
- junifer/storage/pandas_base.py +46 -24
- junifer/storage/sqlite.py +99 -62
- junifer/storage/tests/data/collected_h5io_fork.hdf5 +0 -0
- junifer/storage/tests/test_hdf5.py +147 -12
- junifer/storage/tests/test_repeated_storage.py +293 -0
- junifer/storage/tests/test_sqlite.py +9 -20
- junifer/storage/tests/test_storage_base.py +18 -21
- junifer/storage/tests/test_utils.py +83 -22
- junifer/storage/utils.py +58 -38
- junifer/testing/__init__.pyi +2 -2
- junifer/testing/datagrabbers.py +480 -44
- junifer/testing/registry.py +12 -0
- junifer/testing/tests/test_adhdtesting_datagrabber.py +24 -0
- junifer/testing/tests/test_oasisvmbtesting_datagrabber.py +5 -0
- junifer/testing/tests/test_partlycloudytesting_datagrabber.py +52 -0
- junifer/testing/tests/test_spmauditory_datagrabber.py +16 -0
- junifer/testing/tests/test_testing_registry.py +31 -0
- junifer/testing/tests/test_testing_utils.py +35 -0
- junifer/testing/tests/test_uclacnpvbmtesting_datagrabber.py +81 -0
- junifer/testing/utils.py +33 -1
- junifer/tests/test_stats.py +1 -5
- junifer/typing/__init__.pyi +2 -0
- junifer/typing/_typing.py +34 -19
- junifer/typing/py.typed +0 -0
- junifer/utils/__init__.pyi +10 -1
- junifer/utils/_config.py +1 -2
- junifer/utils/helpers.py +68 -2
- junifer/utils/logging.py +31 -7
- junifer/utils/tests/test_helpers.py +36 -1
- {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/METADATA +43 -35
- junifer-0.0.8.dist-info/RECORD +402 -0
- {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/WHEEL +1 -1
- junifer/cli/tests/test_parser.py +0 -280
- junifer/external/h5io/h5io/__init__.py +0 -15
- junifer/external/h5io/h5io/_h5io.py +0 -767
- junifer/external/h5io/h5io/_version.py +0 -1
- junifer/external/h5io/h5io/chunked_array.py +0 -94
- junifer/external/h5io/h5io/chunked_list.py +0 -65
- junifer/markers/complexity/__init__.pyi +0 -17
- junifer-0.0.7.dev332.dist-info/RECORD +0 -353
- {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/entry_points.txt +0 -0
- {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/licenses/AUTHORS.rst +0 -0
- {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/licenses/LICENSE.md +0 -0
- {junifer-0.0.7.dev332.dist-info → junifer-0.0.8.dist-info}/top_level.txt +0 -0
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# Authors: Synchon Mandal <s.mandal@fz-juelich.de>
|
|
4
4
|
# License: AGPL
|
|
5
5
|
|
|
6
|
-
import
|
|
6
|
+
from pathlib import Path
|
|
7
7
|
|
|
8
8
|
import nibabel
|
|
9
9
|
import numpy as np
|
|
@@ -14,6 +14,12 @@ from nilearn.maskers import NiftiSpheresMasker
|
|
|
14
14
|
from numpy.testing import assert_array_equal
|
|
15
15
|
|
|
16
16
|
from junifer.external.nilearn import JuniferNiftiSpheresMasker
|
|
17
|
+
from junifer.external.nilearn.junifer_nifti_spheres_masker import (
|
|
18
|
+
_apply_mask_and_get_affinity,
|
|
19
|
+
_get_sphere_voxels,
|
|
20
|
+
_iter_signals_from_spheres,
|
|
21
|
+
)
|
|
22
|
+
from junifer.testing import config_override
|
|
17
23
|
|
|
18
24
|
|
|
19
25
|
# New BSD License
|
|
@@ -262,13 +268,16 @@ def test_standardization() -> None:
|
|
|
262
268
|
data = np.random.RandomState(42).random_sample((3, 3, 3, 5))
|
|
263
269
|
img = nibabel.Nifti1Image(data, np.eye(4))
|
|
264
270
|
|
|
265
|
-
# test
|
|
266
|
-
masker = JuniferNiftiSpheresMasker(
|
|
271
|
+
# test zscore_sample
|
|
272
|
+
masker = JuniferNiftiSpheresMasker(
|
|
273
|
+
seeds=[(1, 1, 1)], standardize="zscore_sample"
|
|
274
|
+
)
|
|
267
275
|
# Test the fit
|
|
268
276
|
s = masker.fit_transform(img)
|
|
269
277
|
|
|
270
278
|
np.testing.assert_almost_equal(s.mean(), 0)
|
|
271
|
-
|
|
279
|
+
# Uses the sample standard deviation
|
|
280
|
+
np.testing.assert_almost_equal(s.std(ddof=1), 1)
|
|
272
281
|
|
|
273
282
|
# test psc
|
|
274
283
|
masker = JuniferNiftiSpheresMasker(seeds=[(1, 1, 1)], standardize="psc")
|
|
@@ -294,10 +303,10 @@ def test_nifti_spheres_masker_inverse_transform() -> None:
|
|
|
294
303
|
|
|
295
304
|
|
|
296
305
|
def test_nifti_spheres_masker_io_shapes() -> None:
|
|
297
|
-
"""Ensure that masker handles
|
|
306
|
+
"""Ensure that masker handles 3D/4D data appropriately.
|
|
298
307
|
|
|
299
|
-
transform(4D image) --> 2D output
|
|
300
|
-
transform(3D image) -->
|
|
308
|
+
transform(4D image) --> 2D output
|
|
309
|
+
transform(3D image) --> 1D output
|
|
301
310
|
|
|
302
311
|
"""
|
|
303
312
|
n_regions, n_volumes = 2, 5
|
|
@@ -318,20 +327,11 @@ def test_nifti_spheres_masker_io_shapes() -> None:
|
|
|
318
327
|
)
|
|
319
328
|
masker.fit()
|
|
320
329
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
# DeprecationWarning should *not* be raised for 4D inputs
|
|
327
|
-
with warnings.catch_warnings():
|
|
328
|
-
warnings.filterwarnings(
|
|
329
|
-
"error",
|
|
330
|
-
message="Starting in version 0.12",
|
|
331
|
-
category=DeprecationWarning,
|
|
332
|
-
)
|
|
333
|
-
test_data = masker.transform(img_4d)
|
|
334
|
-
assert test_data.shape == (n_volumes, n_regions)
|
|
330
|
+
test_data = masker.transform(img_3d)
|
|
331
|
+
assert test_data.shape == (n_regions,)
|
|
332
|
+
|
|
333
|
+
test_data = masker.transform(img_4d)
|
|
334
|
+
assert test_data.shape == (n_volumes, n_regions)
|
|
335
335
|
|
|
336
336
|
|
|
337
337
|
@pytest.mark.parametrize(
|
|
@@ -405,3 +405,213 @@ def test_junifer_and_nilearn_mean_agg_are_equal(
|
|
|
405
405
|
# Checks
|
|
406
406
|
assert junifer_output.shape == nilearn_output.shape
|
|
407
407
|
np.testing.assert_almost_equal(junifer_output, nilearn_output)
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def _spheres_data(
|
|
411
|
+
tmp_path: Path, dtype: str = "float32", with_nan: bool = True
|
|
412
|
+
) -> tuple[nibabel.Nifti1Image, nibabel.Nifti1Image, list]:
|
|
413
|
+
"""Create a file-backed 4D image, a mask and seeds for the spheres."""
|
|
414
|
+
rng = np.random.default_rng(42)
|
|
415
|
+
affine = np.diag([3.0, 3.0, 3.0, 1.0])
|
|
416
|
+
affine[:3, 3] = -30
|
|
417
|
+
data = rng.normal(100, 10, size=(21, 21, 21, 23))
|
|
418
|
+
if with_nan:
|
|
419
|
+
data[10, 10, 10, 3] = np.nan
|
|
420
|
+
nibabel.save(
|
|
421
|
+
nibabel.Nifti1Image(data.astype(dtype), affine), tmp_path / "data.nii"
|
|
422
|
+
)
|
|
423
|
+
mask = np.zeros((21, 21, 21), dtype="int8")
|
|
424
|
+
mask[4:17, 3:18, 5:16] = 1
|
|
425
|
+
mask_img = nibabel.Nifti1Image(mask, affine)
|
|
426
|
+
# The last seed is outside the image, so its sphere is empty
|
|
427
|
+
seeds = [(0, 0, 0), (12, -9, 6), (-24, 24, 24), (100, 100, 100)]
|
|
428
|
+
return nibabel.load(tmp_path / "data.nii"), mask_img, seeds
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
@pytest.mark.parametrize("chunk_size", [1, 5, 23, 100])
|
|
432
|
+
@pytest.mark.parametrize("masked", [True, False])
|
|
433
|
+
@pytest.mark.parametrize(
|
|
434
|
+
"dtype, with_nan",
|
|
435
|
+
[
|
|
436
|
+
("float32", True),
|
|
437
|
+
("float32", False),
|
|
438
|
+
("float64", True),
|
|
439
|
+
("int16", False),
|
|
440
|
+
],
|
|
441
|
+
)
|
|
442
|
+
def test_iter_signals_from_spheres_chunks(
|
|
443
|
+
tmp_path: Path, chunk_size: int, masked: bool, dtype: str, with_nan: bool
|
|
444
|
+
) -> None:
|
|
445
|
+
"""Test the signals of the spheres read in chunks of volumes.
|
|
446
|
+
|
|
447
|
+
They are the same as the ones extracted from the whole data.
|
|
448
|
+
|
|
449
|
+
Parameters
|
|
450
|
+
----------
|
|
451
|
+
tmp_path : pathlib.Path
|
|
452
|
+
The path to the test directory.
|
|
453
|
+
chunk_size : int
|
|
454
|
+
The parametrized number of volumes to read at once.
|
|
455
|
+
masked : bool
|
|
456
|
+
The parametrized use of a mask.
|
|
457
|
+
dtype : str
|
|
458
|
+
The parametrized data type.
|
|
459
|
+
with_nan : bool
|
|
460
|
+
The parametrized presence of a NaN value in the data.
|
|
461
|
+
|
|
462
|
+
"""
|
|
463
|
+
img, mask_img, seeds = _spheres_data(tmp_path, dtype, with_nan)
|
|
464
|
+
mask_img = mask_img if masked else None
|
|
465
|
+
X, A = _apply_mask_and_get_affinity(
|
|
466
|
+
seeds, img, radius=4.0, allow_overlap=True, mask_img=mask_img
|
|
467
|
+
)
|
|
468
|
+
with config_override("markers.spheres.chunksize", chunk_size):
|
|
469
|
+
signals = list(
|
|
470
|
+
_iter_signals_from_spheres(
|
|
471
|
+
seeds, img, radius=4.0, allow_overlap=True, mask_img=mask_img
|
|
472
|
+
)
|
|
473
|
+
)
|
|
474
|
+
assert len(signals) == len(seeds)
|
|
475
|
+
assert signals[-1].shape == (23, 0)
|
|
476
|
+
for row, t_signals in zip(A.rows, signals, strict=True):
|
|
477
|
+
assert t_signals.dtype == X.dtype
|
|
478
|
+
assert_array_equal(t_signals, X[:, row])
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
def test_JuniferNiftiSpheresMasker_input_unchanged(tmp_path: Path) -> None:
|
|
482
|
+
"""Test the masker does not change the input data.
|
|
483
|
+
|
|
484
|
+
Parameters
|
|
485
|
+
----------
|
|
486
|
+
tmp_path : pathlib.Path
|
|
487
|
+
The path to the test directory.
|
|
488
|
+
|
|
489
|
+
"""
|
|
490
|
+
img, _, seeds = _spheres_data(tmp_path)
|
|
491
|
+
data = np.asarray(img.dataobj).copy()
|
|
492
|
+
in_memory = nibabel.Nifti1Image(data.copy(), img.affine)
|
|
493
|
+
with pytest.warns(RuntimeWarning, match="NaN values"):
|
|
494
|
+
JuniferNiftiSpheresMasker(
|
|
495
|
+
seeds=seeds, radius=4.0, allow_overlap=True
|
|
496
|
+
).fit_transform(in_memory)
|
|
497
|
+
# The NaN is still there
|
|
498
|
+
assert_array_equal(np.asarray(in_memory.dataobj), data)
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
@pytest.mark.parametrize("chunk_size", [0, -1, 1.5, True, "10"])
|
|
502
|
+
def test_JuniferNiftiSpheresMasker_chunk_size_error(
|
|
503
|
+
tmp_path: Path, chunk_size: object
|
|
504
|
+
) -> None:
|
|
505
|
+
"""Test the masker with an invalid number of volumes to read at once.
|
|
506
|
+
|
|
507
|
+
Parameters
|
|
508
|
+
----------
|
|
509
|
+
tmp_path : pathlib.Path
|
|
510
|
+
The path to the test directory.
|
|
511
|
+
chunk_size : object
|
|
512
|
+
The parametrized invalid number of volumes.
|
|
513
|
+
|
|
514
|
+
"""
|
|
515
|
+
img, _, seeds = _spheres_data(tmp_path)
|
|
516
|
+
with config_override("markers.spheres.chunksize", chunk_size):
|
|
517
|
+
with pytest.raises(ValueError, match="must be a positive integer"):
|
|
518
|
+
JuniferNiftiSpheresMasker(seeds=seeds, radius=4.0).fit_transform(
|
|
519
|
+
img
|
|
520
|
+
)
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
def test_JuniferNiftiSpheresMasker_reports() -> None:
|
|
524
|
+
"""Test the reports are disabled by default, as they load the data."""
|
|
525
|
+
assert JuniferNiftiSpheresMasker(seeds=[(1, 1, 1)]).reports is False
|
|
526
|
+
masker = JuniferNiftiSpheresMasker(seeds=[(1, 1, 1)], reports=True)
|
|
527
|
+
assert masker.reports is True
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
def test_get_sphere_voxels_mask_only(tmp_path: Path) -> None:
|
|
531
|
+
"""Test the voxels of the spheres with only a mask, without an image.
|
|
532
|
+
|
|
533
|
+
Parameters
|
|
534
|
+
----------
|
|
535
|
+
tmp_path : pathlib.Path
|
|
536
|
+
The path to the test directory.
|
|
537
|
+
|
|
538
|
+
"""
|
|
539
|
+
img, mask_img, seeds = _spheres_data(tmp_path)
|
|
540
|
+
voxels, A = _get_sphere_voxels(
|
|
541
|
+
seeds, img, radius=4.0, allow_overlap=True, mask_img=mask_img
|
|
542
|
+
)
|
|
543
|
+
mask_voxels, mask_A = _get_sphere_voxels(
|
|
544
|
+
seeds, None, radius=4.0, allow_overlap=True, mask_img=mask_img
|
|
545
|
+
)
|
|
546
|
+
assert_array_equal(mask_voxels, voxels)
|
|
547
|
+
assert mask_A.rows.tolist() == A.rows.tolist()
|
|
548
|
+
# No signals without an image
|
|
549
|
+
X, _ = _apply_mask_and_get_affinity(
|
|
550
|
+
seeds, None, radius=4.0, allow_overlap=True, mask_img=mask_img
|
|
551
|
+
)
|
|
552
|
+
assert X is None
|
|
553
|
+
with pytest.raises(ValueError, match="Either a niimg or a mask_img"):
|
|
554
|
+
_get_sphere_voxels(seeds, None, radius=4.0, allow_overlap=True)
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
@pytest.mark.parametrize("dtype", ["auto", "float64"])
|
|
558
|
+
def test_JuniferNiftiSpheresMasker_dtype(tmp_path: Path, dtype: str) -> None:
|
|
559
|
+
"""Test the data type of the signals is the same as with nilearn.
|
|
560
|
+
|
|
561
|
+
Parameters
|
|
562
|
+
----------
|
|
563
|
+
tmp_path : pathlib.Path
|
|
564
|
+
The path to the test directory.
|
|
565
|
+
dtype : str
|
|
566
|
+
The parametrized data type of the extraction.
|
|
567
|
+
|
|
568
|
+
"""
|
|
569
|
+
img, _, seeds = _spheres_data(tmp_path, "float32", with_nan=False)
|
|
570
|
+
junifer_signals = JuniferNiftiSpheresMasker(
|
|
571
|
+
seeds=seeds[:3], radius=4.0, allow_overlap=True, dtype=dtype
|
|
572
|
+
).fit_transform(img)
|
|
573
|
+
nilearn_signals = NiftiSpheresMasker(
|
|
574
|
+
seeds=seeds[:3], radius=4.0, allow_overlap=True, dtype=dtype
|
|
575
|
+
).fit_transform(img)
|
|
576
|
+
assert junifer_signals.dtype == nilearn_signals.dtype
|
|
577
|
+
np.testing.assert_allclose(junifer_signals, nilearn_signals, rtol=1e-6)
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
@pytest.mark.parametrize("chunk_size", [5, 100])
|
|
581
|
+
@pytest.mark.parametrize("masked", [True, False])
|
|
582
|
+
@pytest.mark.parametrize("with_nan", [True, False])
|
|
583
|
+
def test_junifer_and_nilearn_spheres_are_equal(
|
|
584
|
+
tmp_path: Path, chunk_size: int, masked: bool, with_nan: bool
|
|
585
|
+
) -> None:
|
|
586
|
+
"""Test the signals of the spheres are the same as with nilearn.
|
|
587
|
+
|
|
588
|
+
The signals are read in chunks of volumes from a file-backed image.
|
|
589
|
+
|
|
590
|
+
Parameters
|
|
591
|
+
----------
|
|
592
|
+
tmp_path : pathlib.Path
|
|
593
|
+
The path to the test directory.
|
|
594
|
+
chunk_size : int
|
|
595
|
+
The parametrized number of volumes to read at once.
|
|
596
|
+
masked : bool
|
|
597
|
+
The parametrized use of a mask.
|
|
598
|
+
with_nan : bool
|
|
599
|
+
The parametrized presence of a NaN value in the data.
|
|
600
|
+
|
|
601
|
+
"""
|
|
602
|
+
img, mask_img, seeds = _spheres_data(tmp_path, with_nan=with_nan)
|
|
603
|
+
# Only the seeds with voxels in the mask, as nilearn fails for empty
|
|
604
|
+
# spheres
|
|
605
|
+
params = {
|
|
606
|
+
"seeds": seeds[:2],
|
|
607
|
+
"radius": 4.0,
|
|
608
|
+
"allow_overlap": True,
|
|
609
|
+
"mask_img": mask_img if masked else None,
|
|
610
|
+
}
|
|
611
|
+
with config_override("markers.spheres.chunksize", chunk_size):
|
|
612
|
+
junifer_signals = JuniferNiftiSpheresMasker(**params).fit_transform(
|
|
613
|
+
img
|
|
614
|
+
)
|
|
615
|
+
nilearn_signals = NiftiSpheresMasker(**params).fit_transform(img)
|
|
616
|
+
assert junifer_signals.dtype == nilearn_signals.dtype
|
|
617
|
+
np.testing.assert_allclose(junifer_signals, nilearn_signals, rtol=1e-6)
|
|
File without changes
|
junifer/markers/__init__.pyi
CHANGED
|
@@ -11,9 +11,11 @@ __all__ = [
|
|
|
11
11
|
"EdgeCentricFCMaps",
|
|
12
12
|
"EdgeCentricFCParcels",
|
|
13
13
|
"EdgeCentricFCSpheres",
|
|
14
|
+
"ReHoImpl",
|
|
14
15
|
"ReHoMaps",
|
|
15
16
|
"ReHoParcels",
|
|
16
17
|
"ReHoSpheres",
|
|
18
|
+
"ALFFImpl",
|
|
17
19
|
"ALFFMaps",
|
|
18
20
|
"ALFFParcels",
|
|
19
21
|
"ALFFSpheres",
|
|
@@ -21,9 +23,10 @@ __all__ = [
|
|
|
21
23
|
"TemporalSNRParcels",
|
|
22
24
|
"TemporalSNRSpheres",
|
|
23
25
|
"BrainPrint",
|
|
26
|
+
"logger",
|
|
24
27
|
]
|
|
25
28
|
|
|
26
|
-
from .base import BaseMarker
|
|
29
|
+
from .base import BaseMarker, logger
|
|
27
30
|
from .ets_rss import RSSETSMarker
|
|
28
31
|
from .maps_aggregation import MapsAggregation
|
|
29
32
|
from .parcel_aggregation import ParcelAggregation
|
|
@@ -37,8 +40,8 @@ from .functional_connectivity import (
|
|
|
37
40
|
EdgeCentricFCParcels,
|
|
38
41
|
EdgeCentricFCSpheres,
|
|
39
42
|
)
|
|
40
|
-
from .reho import ReHoMaps, ReHoParcels, ReHoSpheres
|
|
41
|
-
from .falff import ALFFMaps, ALFFParcels, ALFFSpheres
|
|
43
|
+
from .reho import ReHoImpl, ReHoMaps, ReHoParcels, ReHoSpheres
|
|
44
|
+
from .falff import ALFFImpl, ALFFMaps, ALFFParcels, ALFFSpheres
|
|
42
45
|
from .temporal_snr import (
|
|
43
46
|
TemporalSNRMaps,
|
|
44
47
|
TemporalSNRParcels,
|
junifer/markers/base.py
CHANGED
|
@@ -6,17 +6,27 @@
|
|
|
6
6
|
|
|
7
7
|
from abc import ABC, abstractmethod
|
|
8
8
|
from copy import deepcopy
|
|
9
|
-
from typing import Any, ClassVar
|
|
9
|
+
from typing import Annotated, Any, ClassVar
|
|
10
10
|
|
|
11
|
+
import structlog
|
|
12
|
+
from pydantic import BaseModel, BeforeValidator, ConfigDict
|
|
13
|
+
|
|
14
|
+
from ..datagrabber import DataType
|
|
11
15
|
from ..pipeline import PipelineStepMixin, UpdateMetaMixin
|
|
16
|
+
from ..storage import StorageType
|
|
12
17
|
from ..typing import MarkerInOutMappings, StorageLike
|
|
13
|
-
from ..utils import
|
|
18
|
+
from ..utils import ensure_list_or_none, raise_error
|
|
19
|
+
from .utils import _changed_paths, _fingerprint
|
|
14
20
|
|
|
15
21
|
|
|
16
22
|
__all__ = ["BaseMarker"]
|
|
17
23
|
|
|
18
24
|
|
|
19
|
-
|
|
25
|
+
_log = structlog.get_logger("junifer")
|
|
26
|
+
logger = _log.bind(pkg="markers", step="marker")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class BaseMarker(BaseModel, ABC, PipelineStepMixin, UpdateMetaMixin):
|
|
20
30
|
"""Abstract base class for marker.
|
|
21
31
|
|
|
22
32
|
For every marker, one needs to provide a concrete
|
|
@@ -24,12 +34,17 @@ class BaseMarker(ABC, PipelineStepMixin, UpdateMetaMixin):
|
|
|
24
34
|
|
|
25
35
|
Parameters
|
|
26
36
|
----------
|
|
27
|
-
on :
|
|
28
|
-
The data type to apply the marker on.
|
|
29
|
-
will work on all available data types
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
name of the marker
|
|
37
|
+
on : :enum:`.DataType` or list of variants or None, optional
|
|
38
|
+
The data type(s) to apply the marker on.
|
|
39
|
+
If None, will work on all available data types.
|
|
40
|
+
Check :enum:`.DataType` for valid values (default None).
|
|
41
|
+
name : str or None, optional
|
|
42
|
+
The name of the marker.
|
|
43
|
+
If None, will use the class name (default None).
|
|
44
|
+
|
|
45
|
+
Attributes
|
|
46
|
+
----------
|
|
47
|
+
valid_inputs
|
|
33
48
|
|
|
34
49
|
Raises
|
|
35
50
|
------
|
|
@@ -42,11 +57,15 @@ class BaseMarker(ABC, PipelineStepMixin, UpdateMetaMixin):
|
|
|
42
57
|
|
|
43
58
|
_MARKER_INOUT_MAPPINGS: ClassVar[MarkerInOutMappings]
|
|
44
59
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
60
|
+
model_config = ConfigDict(extra="forbid", use_enum_values=True)
|
|
61
|
+
|
|
62
|
+
on: Annotated[
|
|
63
|
+
DataType | list[DataType] | None,
|
|
64
|
+
BeforeValidator(ensure_list_or_none),
|
|
65
|
+
] = None
|
|
66
|
+
name: str | None = None
|
|
67
|
+
|
|
68
|
+
def model_post_init(self, context: Any): # noqa: D102
|
|
50
69
|
# Check for missing mapping attribute
|
|
51
70
|
if not hasattr(self, "_MARKER_INOUT_MAPPINGS"):
|
|
52
71
|
raise_error(
|
|
@@ -54,18 +73,51 @@ class BaseMarker(ABC, PipelineStepMixin, UpdateMetaMixin):
|
|
|
54
73
|
klass=AttributeError,
|
|
55
74
|
)
|
|
56
75
|
# Use all data types if not provided
|
|
57
|
-
if on is None:
|
|
58
|
-
on = self.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
on = [
|
|
76
|
+
if self.on is None:
|
|
77
|
+
self.on = self.valid_inputs
|
|
78
|
+
else:
|
|
79
|
+
# Convert to correct data type
|
|
80
|
+
self.on = [
|
|
81
|
+
DataType(t) if isinstance(t, str) else t for t in self.on
|
|
82
|
+
]
|
|
83
|
+
# Check if required input data types are provided
|
|
84
|
+
if any(x not in self.valid_inputs for x in self.on):
|
|
85
|
+
wrong_on = [
|
|
86
|
+
x.value for x in self.on if x not in self.valid_inputs
|
|
87
|
+
]
|
|
88
|
+
raise_error(
|
|
89
|
+
f"{self.__class__.__name__} cannot be computed on "
|
|
90
|
+
f"{wrong_on}"
|
|
91
|
+
)
|
|
92
|
+
# Run extra validation for markers and fail early if needed
|
|
93
|
+
self.validate_marker_params()
|
|
62
94
|
# Set default name if not provided
|
|
63
|
-
self.name = self.__class__.__name__ if name is None else name
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
95
|
+
self.name = self.__class__.__name__ if self.name is None else self.name
|
|
96
|
+
logger.info(
|
|
97
|
+
f"Parameters: {self.model_dump(mode='json')}", component=self.name
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def valid_inputs(self) -> list[DataType]:
|
|
102
|
+
"""Valid data types to operate on.
|
|
103
|
+
|
|
104
|
+
Returns
|
|
105
|
+
-------
|
|
106
|
+
list of :enum:`.DataType`
|
|
107
|
+
The list of data types that can be used as input for this marker.
|
|
108
|
+
|
|
109
|
+
"""
|
|
110
|
+
return [
|
|
111
|
+
DataType(x) if isinstance(x, str) else x
|
|
112
|
+
for x in self._MARKER_INOUT_MAPPINGS.keys()
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
def validate_marker_params(self) -> None:
|
|
116
|
+
"""Run extra logical validation for marker.
|
|
117
|
+
|
|
118
|
+
Subclasses can override to provide validation.
|
|
119
|
+
"""
|
|
120
|
+
pass
|
|
69
121
|
|
|
70
122
|
def validate_input(self, input: list[str]) -> list[str]:
|
|
71
123
|
"""Validate input.
|
|
@@ -88,45 +140,36 @@ class BaseMarker(ABC, PipelineStepMixin, UpdateMetaMixin):
|
|
|
88
140
|
If the input does not have the required data.
|
|
89
141
|
|
|
90
142
|
"""
|
|
91
|
-
if not any(x in input for x in self.
|
|
143
|
+
if not any(x in input for x in self.on):
|
|
92
144
|
raise_error(
|
|
93
|
-
"Input does not have the required data
|
|
94
|
-
f"\t Input: {input}"
|
|
95
|
-
f"\t Required (any of): {self.
|
|
145
|
+
"Input does not have the required data.\n"
|
|
146
|
+
f"\t Input: {input}\n"
|
|
147
|
+
f"\t Required (any of): {[t.value for t in self.on]}"
|
|
96
148
|
)
|
|
97
|
-
return [x for x in self.
|
|
149
|
+
return [x.value for x in self.on if x in input]
|
|
98
150
|
|
|
99
|
-
def
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
-------
|
|
104
|
-
list of str
|
|
105
|
-
The list of data types that can be used as input for this marker.
|
|
106
|
-
|
|
107
|
-
"""
|
|
108
|
-
return list(self._MARKER_INOUT_MAPPINGS.keys())
|
|
109
|
-
|
|
110
|
-
def storage_type(self, input_type: str, output_feature: str) -> str:
|
|
111
|
-
"""Get storage type for a feature.
|
|
151
|
+
def storage_type(
|
|
152
|
+
self, input_type: DataType, output_feature: str
|
|
153
|
+
) -> StorageType:
|
|
154
|
+
"""Get :enum:`.StorageType` for a feature.
|
|
112
155
|
|
|
113
156
|
Parameters
|
|
114
157
|
----------
|
|
115
|
-
input_type :
|
|
158
|
+
input_type : :enum:`.DataType`
|
|
116
159
|
The data type input to the marker.
|
|
117
160
|
output_feature : str
|
|
118
161
|
The feature output of the marker.
|
|
119
162
|
|
|
120
163
|
Returns
|
|
121
164
|
-------
|
|
122
|
-
|
|
165
|
+
:enum:`.StorageType`
|
|
123
166
|
The storage type output of the marker.
|
|
124
167
|
|
|
125
168
|
"""
|
|
126
169
|
return self._MARKER_INOUT_MAPPINGS[input_type][output_feature]
|
|
127
170
|
|
|
128
171
|
@abstractmethod
|
|
129
|
-
def compute(self, input: dict, extra_input:
|
|
172
|
+
def compute(self, input: dict, extra_input: dict | None = None) -> dict:
|
|
130
173
|
"""Compute.
|
|
131
174
|
|
|
132
175
|
Parameters
|
|
@@ -155,33 +198,33 @@ class BaseMarker(ABC, PipelineStepMixin, UpdateMetaMixin):
|
|
|
155
198
|
|
|
156
199
|
def store(
|
|
157
200
|
self,
|
|
158
|
-
|
|
201
|
+
data_type: DataType,
|
|
159
202
|
feature: str,
|
|
160
|
-
|
|
203
|
+
output: dict[str, Any],
|
|
161
204
|
storage: StorageLike,
|
|
162
205
|
) -> None:
|
|
163
206
|
"""Store.
|
|
164
207
|
|
|
165
208
|
Parameters
|
|
166
209
|
----------
|
|
167
|
-
|
|
210
|
+
data_type : :enum:`.DataType`
|
|
168
211
|
The data type to store.
|
|
169
212
|
feature : str
|
|
170
213
|
The feature to store.
|
|
171
|
-
|
|
214
|
+
output : dict
|
|
172
215
|
The computed result as a dictionary to store.
|
|
173
216
|
storage : storage-like
|
|
174
|
-
The storage class, for example,
|
|
217
|
+
The storage class, for example, HDF5FeatureStorage.
|
|
175
218
|
|
|
176
219
|
"""
|
|
177
|
-
|
|
178
|
-
logger.debug(f"Storing {
|
|
179
|
-
storage.store(kind=
|
|
220
|
+
s_type = self.storage_type(data_type, feature)
|
|
221
|
+
logger.debug(f"Storing {s_type} in {storage}")
|
|
222
|
+
storage.store(kind=s_type, **output)
|
|
180
223
|
|
|
181
224
|
def _fit_transform(
|
|
182
225
|
self,
|
|
183
226
|
input: dict[str, dict],
|
|
184
|
-
storage:
|
|
227
|
+
storage: StorageLike | None = None,
|
|
185
228
|
) -> dict:
|
|
186
229
|
"""Fit and transform.
|
|
187
230
|
|
|
@@ -190,63 +233,70 @@ class BaseMarker(ABC, PipelineStepMixin, UpdateMetaMixin):
|
|
|
190
233
|
input : dict
|
|
191
234
|
The Junifer Data object.
|
|
192
235
|
storage : storage-like, optional
|
|
193
|
-
The storage class, for example,
|
|
236
|
+
The storage class, for example, HDF5FeatureStorage.
|
|
194
237
|
|
|
195
238
|
Returns
|
|
196
239
|
-------
|
|
197
240
|
dict
|
|
198
|
-
The processed output as a dictionary. If
|
|
241
|
+
The processed output as a dictionary. If ``storage`` is provided,
|
|
199
242
|
empty dictionary is returned.
|
|
200
243
|
|
|
201
244
|
"""
|
|
202
245
|
out = {}
|
|
203
|
-
for
|
|
204
|
-
if
|
|
205
|
-
logger.info(f"Computing {
|
|
246
|
+
for t in self.on:
|
|
247
|
+
if t in input.keys():
|
|
248
|
+
logger.info(f"Computing {t}")
|
|
206
249
|
# Get data dict for data type
|
|
207
|
-
t_input = input[
|
|
250
|
+
t_input = input[t]
|
|
208
251
|
# Pass the other data types as extra input, removing
|
|
209
252
|
# the current type
|
|
210
253
|
extra_input = input.copy()
|
|
211
|
-
extra_input.pop(
|
|
254
|
+
extra_input.pop(t)
|
|
212
255
|
logger.debug(
|
|
213
256
|
f"Extra data type for feature extraction: "
|
|
214
257
|
f"{extra_input.keys()}"
|
|
215
258
|
)
|
|
216
259
|
# Copy metadata
|
|
217
260
|
t_meta = t_input["meta"].copy()
|
|
218
|
-
t_meta["type"] =
|
|
219
|
-
# Compute marker
|
|
261
|
+
t_meta["type"] = t.value
|
|
262
|
+
# Compute marker, checking it does not change the data, as
|
|
263
|
+
# it is shared with the markers computed afterwards
|
|
264
|
+
fingerprint = _fingerprint(input)
|
|
220
265
|
t_out = self.compute(input=t_input, extra_input=extra_input)
|
|
266
|
+
changed = _changed_paths(fingerprint, _fingerprint(input))
|
|
267
|
+
if changed:
|
|
268
|
+
raise_error(
|
|
269
|
+
f"Marker {self.name} changed its input data, which is "
|
|
270
|
+
f"shared with the other markers: {', '.join(changed)}",
|
|
271
|
+
klass=RuntimeError,
|
|
272
|
+
)
|
|
221
273
|
# Initialize empty dictionary if no storage object is provided
|
|
222
274
|
if storage is None:
|
|
223
|
-
out[
|
|
275
|
+
out[t] = {}
|
|
224
276
|
# Store individual features
|
|
225
|
-
for
|
|
277
|
+
for f_name, f_data in t_out.items():
|
|
226
278
|
# Make deep copy of the feature data for manipulation
|
|
227
|
-
|
|
279
|
+
f_data_copy = deepcopy(f_data)
|
|
228
280
|
# Make deep copy of metadata and add to feature data
|
|
229
|
-
|
|
281
|
+
f_data_copy["meta"] = deepcopy(t_meta)
|
|
230
282
|
# Update metadata for the feature,
|
|
231
283
|
# feature data is not manipulated, only meta
|
|
232
|
-
self.update_meta(
|
|
284
|
+
self.update_meta(f_data_copy, "marker")
|
|
233
285
|
# Update marker feature's metadata name
|
|
234
|
-
|
|
235
|
-
f"_{feature_name}"
|
|
236
|
-
)
|
|
286
|
+
f_data_copy["meta"]["marker"]["name"] += f"_{f_name}"
|
|
237
287
|
|
|
238
288
|
if storage is not None:
|
|
239
289
|
logger.info(f"Storing in {storage}")
|
|
240
290
|
self.store(
|
|
241
|
-
|
|
242
|
-
feature=
|
|
243
|
-
|
|
291
|
+
data_type=t,
|
|
292
|
+
feature=f_name,
|
|
293
|
+
output=f_data_copy,
|
|
244
294
|
storage=storage,
|
|
245
295
|
)
|
|
246
296
|
else:
|
|
247
297
|
logger.info(
|
|
248
298
|
"No storage specified, returning dictionary"
|
|
249
299
|
)
|
|
250
|
-
out[
|
|
300
|
+
out[t][f_name] = f_data_copy
|
|
251
301
|
|
|
252
302
|
return out
|