junifer 0.0.6.dev317__py3-none-any.whl → 0.0.6.dev330__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 CHANGED
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '0.0.6.dev317'
16
- __version_tuple__ = version_tuple = (0, 0, 6, 'dev317')
15
+ __version__ = version = '0.0.6.dev330'
16
+ __version_tuple__ = version_tuple = (0, 0, 6, 'dev330')
@@ -37,6 +37,9 @@ class GnuParallelLocalAdapter(QueueContextAdapter):
37
37
  virtual environment of any kind (default None).
38
38
  verbose : str, optional
39
39
  The level of verbosity (default "info").
40
+ verbose_datalad : str or None, optional
41
+ The level of verbosity for datalad. If None, will be the same
42
+ as ``verbose`` (default None).
40
43
  submit : bool, optional
41
44
  Whether to submit the jobs (default False).
42
45
 
@@ -65,6 +68,7 @@ class GnuParallelLocalAdapter(QueueContextAdapter):
65
68
  pre_collect: Optional[str] = None,
66
69
  env: Optional[dict[str, str]] = None,
67
70
  verbose: str = "info",
71
+ verbose_datalad: Optional[str] = None,
68
72
  submit: bool = False,
69
73
  ) -> None:
70
74
  """Initialize the class."""
@@ -76,6 +80,7 @@ class GnuParallelLocalAdapter(QueueContextAdapter):
76
80
  self._pre_collect = pre_collect
77
81
  self._check_env(env)
78
82
  self._verbose = verbose
83
+ self._verbose_datalad = verbose_datalad
79
84
  self._submit = submit
80
85
 
81
86
  self._log_dir = self._job_dir / "logs"
@@ -155,6 +160,11 @@ class GnuParallelLocalAdapter(QueueContextAdapter):
155
160
 
156
161
  def run(self) -> str:
157
162
  """Return run commands."""
163
+ verbose_args = f"--verbose {self._verbose}"
164
+ if self._verbose_datalad:
165
+ verbose_args = (
166
+ f"{verbose_args} --verbose-datalad {self._verbose_datalad}"
167
+ )
158
168
  return (
159
169
  f"#!/usr/bin/env {self._shell}\n\n"
160
170
  "# This script is auto-generated by junifer.\n\n"
@@ -169,7 +179,7 @@ class GnuParallelLocalAdapter(QueueContextAdapter):
169
179
  f"{self._job_dir.resolve()!s}/{self._executable} "
170
180
  f"{self._arguments} run "
171
181
  f"{self._yaml_config_path.resolve()!s} "
172
- f"--verbose {self._verbose} "
182
+ f"{verbose_args} "
173
183
  f"--element"
174
184
  )
175
185
 
@@ -184,6 +194,11 @@ class GnuParallelLocalAdapter(QueueContextAdapter):
184
194
 
185
195
  def collect(self) -> str:
186
196
  """Return collect commands."""
197
+ verbose_args = f"--verbose {self._verbose}"
198
+ if self._verbose_datalad:
199
+ verbose_args = (
200
+ f"{verbose_args} --verbose-datalad {self._verbose_datalad}"
201
+ )
187
202
  return (
188
203
  f"#!/usr/bin/env {self._shell}\n\n"
189
204
  "# This script is auto-generated by junifer.\n\n"
@@ -193,7 +208,7 @@ class GnuParallelLocalAdapter(QueueContextAdapter):
193
208
  f"{self._job_dir.resolve()!s}/{self._executable} "
194
209
  f"{self._arguments} collect "
195
210
  f"{self._yaml_config_path.resolve()!s} "
196
- f"--verbose {self._verbose}"
211
+ f"{verbose_args}"
197
212
  )
198
213
 
199
214
  def prepare(self) -> None:
@@ -37,6 +37,9 @@ class HTCondorAdapter(QueueContextAdapter):
37
37
  virtual environment of any kind (default None).
38
38
  verbose : str, optional
39
39
  The level of verbosity (default "info").
40
+ verbose_datalad : str or None, optional
41
+ The level of verbosity for datalad. If None, will be the same
42
+ as ``verbose`` (default None).
40
43
  cpus : int, optional
41
44
  The number of CPU cores to use (default 1).
42
45
  mem : str, optional
@@ -83,6 +86,7 @@ class HTCondorAdapter(QueueContextAdapter):
83
86
  pre_collect: Optional[str] = None,
84
87
  env: Optional[dict[str, str]] = None,
85
88
  verbose: str = "info",
89
+ verbose_datalad: Optional[str] = None,
86
90
  cpus: int = 1,
87
91
  mem: str = "8G",
88
92
  disk: str = "1G",
@@ -99,6 +103,7 @@ class HTCondorAdapter(QueueContextAdapter):
99
103
  self._pre_collect = pre_collect
100
104
  self._check_env(env)
101
105
  self._verbose = verbose
106
+ self._verbose_datalad = verbose_datalad
102
107
  self._cpus = cpus
103
108
  self._mem = mem
104
109
  self._disk = disk
@@ -201,10 +206,15 @@ class HTCondorAdapter(QueueContextAdapter):
201
206
 
202
207
  def run(self) -> str:
203
208
  """Return run commands."""
209
+ verbose_args = f"--verbose {self._verbose} "
210
+ if self._verbose_datalad is not None:
211
+ verbose_args = (
212
+ f"{verbose_args} --verbose-datalad {self._verbose_datalad} "
213
+ )
204
214
  junifer_run_args = (
205
215
  "run "
206
216
  f"{self._yaml_config_path.resolve()!s} "
207
- f"--verbose {self._verbose} "
217
+ f"{verbose_args}"
208
218
  "--element $(element)"
209
219
  )
210
220
  log_dir_prefix = (
@@ -246,10 +256,16 @@ class HTCondorAdapter(QueueContextAdapter):
246
256
 
247
257
  def collect(self) -> str:
248
258
  """Return collect commands."""
259
+ verbose_args = f"--verbose {self._verbose} "
260
+ if self._verbose_datalad is not None:
261
+ verbose_args = (
262
+ f"{verbose_args} --verbose-datalad {self._verbose_datalad} "
263
+ )
264
+
249
265
  junifer_collect_args = (
250
266
  "collect "
251
267
  f"{self._yaml_config_path.resolve()!s} "
252
- f"--verbose {self._verbose}"
268
+ f"{verbose_args}"
253
269
  )
254
270
  log_dir_prefix = f"{self._log_dir.resolve()!s}/junifer_collect"
255
271
  fixed = (
junifer/cli/cli.py CHANGED
@@ -45,6 +45,32 @@ __all__ = [
45
45
  ]
46
46
 
47
47
 
48
+ def _validate_optional_verbose(
49
+ ctx: click.Context, param: str, value: Optional[str]
50
+ ):
51
+ """Validate optional verbose option.
52
+
53
+ Parameters
54
+ ----------
55
+ ctx : click.Context
56
+ The context of the command.
57
+ param : str
58
+ The parameter to validate.
59
+ value : str
60
+ The value to validate.
61
+
62
+ Returns
63
+ -------
64
+ str or int or None
65
+ The validated value.
66
+
67
+ """
68
+ if value is None:
69
+ return value
70
+ else:
71
+ return _validate_verbose(ctx, param, value)
72
+
73
+
48
74
  def _validate_verbose(
49
75
  ctx: click.Context, param: str, value: str
50
76
  ) -> Union[str, int]:
@@ -105,8 +131,17 @@ def cli() -> None: # pragma: no cover
105
131
  callback=_validate_verbose,
106
132
  default="info",
107
133
  )
134
+ @click.option(
135
+ "--verbose-datalad",
136
+ type=click.UNPROCESSED,
137
+ callback=_validate_optional_verbose,
138
+ default=None,
139
+ )
108
140
  def run(
109
- filepath: click.Path, element: tuple[str], verbose: Union[str, int]
141
+ filepath: click.Path,
142
+ element: tuple[str],
143
+ verbose: Union[str, int],
144
+ verbose_datalad: Optional[Union[str, int]],
110
145
  ) -> None:
111
146
  """Run feature extraction.
112
147
 
@@ -120,10 +155,12 @@ def run(
120
155
  The element(s) to operate on.
121
156
  verbose : click.Choice
122
157
  The verbosity level: warning, info or debug (default "info").
158
+ verbose_datalad : click.Choice or None
159
+ The verbosity level for datalad: warning, info or debug (default None).
123
160
 
124
161
  """
125
162
  # Setup logging
126
- configure_logging(level=verbose)
163
+ configure_logging(level=verbose, level_datalad=verbose_datalad)
127
164
  # TODO(synchon): add validation
128
165
  # Parse YAML
129
166
  config = parse_yaml(filepath)
@@ -167,7 +204,17 @@ def run(
167
204
  callback=_validate_verbose,
168
205
  default="info",
169
206
  )
170
- def collect(filepath: click.Path, verbose: Union[str, int]) -> None:
207
+ @click.option(
208
+ "--verbose-datalad",
209
+ type=click.UNPROCESSED,
210
+ callback=_validate_optional_verbose,
211
+ default=None,
212
+ )
213
+ def collect(
214
+ filepath: click.Path,
215
+ verbose: Union[str, int],
216
+ verbose_datalad: Union[str, int, None],
217
+ ) -> None:
171
218
  """Collect extracted features.
172
219
 
173
220
  \f
@@ -178,10 +225,12 @@ def collect(filepath: click.Path, verbose: Union[str, int]) -> None:
178
225
  The filepath to the configuration file.
179
226
  verbose : click.Choice
180
227
  The verbosity level: warning, info or debug (default "info").
228
+ verbose_datalad : click.Choice or None
229
+ The verbosity level for datalad: warning, info or debug (default None).
181
230
 
182
231
  """
183
232
  # Setup logging
184
- configure_logging(level=verbose)
233
+ configure_logging(level=verbose, level_datalad=verbose_datalad)
185
234
  # TODO: add validation
186
235
  # Parse YAML
187
236
  config = parse_yaml(filepath)
@@ -208,12 +257,19 @@ def collect(filepath: click.Path, verbose: Union[str, int]) -> None:
208
257
  callback=_validate_verbose,
209
258
  default="info",
210
259
  )
260
+ @click.option(
261
+ "--verbose-datalad",
262
+ type=click.UNPROCESSED,
263
+ callback=_validate_optional_verbose,
264
+ default=None,
265
+ )
211
266
  def queue(
212
267
  filepath: click.Path,
213
268
  element: tuple[str],
214
269
  overwrite: bool,
215
270
  submit: bool,
216
271
  verbose: Union[str, int],
272
+ verbose_datalad: Union[str, int, None],
217
273
  ) -> None:
218
274
  """Queue feature extraction.
219
275
 
@@ -231,6 +287,8 @@ def queue(
231
287
  Whether to submit the job.
232
288
  verbose : click.Choice
233
289
  The verbosity level: warning, info or debug (default "info").
290
+ verbose_datalad : click.Choice or None
291
+ The verbosity level for datalad: warning, info or debug (default None).
234
292
 
235
293
  Raises
236
294
  ------
@@ -239,7 +297,7 @@ def queue(
239
297
 
240
298
  """
241
299
  # Setup logging
242
- configure_logging(level=verbose)
300
+ configure_logging(level=verbose, level_datalad=verbose_datalad)
243
301
  # TODO: add validation
244
302
  # Parse YAML
245
303
  config = parse_yaml(filepath) # type: ignore
@@ -365,9 +423,16 @@ def selftest(subpkg: str) -> None:
365
423
  callback=_validate_verbose,
366
424
  default="info",
367
425
  )
426
+ @click.option(
427
+ "--verbose-datalad",
428
+ type=click.UNPROCESSED,
429
+ callback=_validate_optional_verbose,
430
+ default=None,
431
+ )
368
432
  def reset(
369
433
  filepath: click.Path,
370
434
  verbose: Union[str, int],
435
+ verbose_datalad: Union[str, int, None],
371
436
  ) -> None:
372
437
  """Reset generated assets.
373
438
 
@@ -379,10 +444,12 @@ def reset(
379
444
  The filepath to the configuration file.
380
445
  verbose : click.Choice
381
446
  The verbosity level: warning, info or debug (default "info").
447
+ verbose_datalad : click.Choice or None
448
+ The verbosity level for datalad: warning, info or debug (default None).
382
449
 
383
450
  """
384
451
  # Setup logging
385
- configure_logging(level=verbose)
452
+ configure_logging(level=verbose, level_datalad=verbose_datalad)
386
453
  # Parse YAML
387
454
  config = parse_yaml(filepath)
388
455
  # Perform operation
@@ -409,11 +476,18 @@ def reset(
409
476
  callback=_validate_verbose,
410
477
  default="info",
411
478
  )
479
+ @click.option(
480
+ "--verbose-datalad",
481
+ type=click.UNPROCESSED,
482
+ callback=_validate_optional_verbose,
483
+ default=None,
484
+ )
412
485
  def list_elements(
413
486
  filepath: click.Path,
414
487
  element: tuple[str],
415
488
  output_file: Optional[click.Path],
416
489
  verbose: Union[str, int],
490
+ verbose_datalad: Union[str, int, None],
417
491
  ) -> None:
418
492
  """List elements of a dataset.
419
493
 
@@ -430,10 +504,12 @@ def list_elements(
430
504
  stdout is not performed.
431
505
  verbose : click.Choice
432
506
  The verbosity level: warning, info or debug (default "info").
507
+ verbose_datalad : click.Choice or None
508
+ The verbosity level for datalad: warning, info or debug (default None
433
509
 
434
510
  """
435
511
  # Setup logging
436
- configure_logging(level=verbose)
512
+ configure_logging(level=verbose, level_datalad=verbose_datalad)
437
513
  # Parse YAML
438
514
  config = parse_yaml(filepath)
439
515
  # Fetch datagrabber
@@ -50,7 +50,7 @@ class AFNIALFF(metaclass=Singleton):
50
50
  @lru_cache(maxsize=None, typed=True)
51
51
  def compute(
52
52
  self,
53
- data: "Nifti1Image",
53
+ input_path: Path,
54
54
  highpass: float,
55
55
  lowpass: float,
56
56
  tr: Optional[float],
@@ -59,8 +59,8 @@ class AFNIALFF(metaclass=Singleton):
59
59
 
60
60
  Parameters
61
61
  ----------
62
- data : 4D Niimg-like object
63
- Images to process.
62
+ input_path : pathlib.Path
63
+ Path to the input data.
64
64
  highpass : positive float
65
65
  Highpass cutoff frequency.
66
66
  lowpass : positive float
@@ -82,19 +82,17 @@ class AFNIALFF(metaclass=Singleton):
82
82
  """
83
83
  logger.debug("Creating cache for ALFF computation via AFNI")
84
84
 
85
- # Create component-scoped tempdir
86
- tempdir = WorkDirManager().get_tempdir(prefix="afni_alff+falff")
87
-
88
- # Save target data to a component-scoped tempfile
89
- nifti_in_file_path = tempdir / "input.nii" # needs to be .nii
90
- nib.save(data, nifti_in_file_path)
85
+ # Create element-scoped tempdir
86
+ element_tempdir = WorkDirManager().get_element_tempdir(
87
+ prefix="afni_lff"
88
+ )
91
89
 
92
90
  # Set 3dRSFC command
93
- alff_falff_out_path_prefix = tempdir / "alff_falff"
91
+ lff_out_path_prefix = element_tempdir / "output"
94
92
  bp_cmd = [
95
93
  "3dRSFC",
96
- f"-prefix {alff_falff_out_path_prefix.resolve()}",
97
- f"-input {nifti_in_file_path.resolve()}",
94
+ f"-prefix {lff_out_path_prefix.resolve()}",
95
+ f"-input {input_path.resolve()}",
98
96
  f"-band {highpass} {lowpass}",
99
97
  "-no_rsfa -nosat -nodetrend",
100
98
  ]
@@ -104,49 +102,48 @@ class AFNIALFF(metaclass=Singleton):
104
102
  # Call 3dRSFC
105
103
  run_ext_cmd(name="3dRSFC", cmd=bp_cmd)
106
104
 
107
- # Create element-scoped tempdir so that the ALFF and fALFF maps are
108
- # available later as nibabel stores file path reference for
109
- # loading on computation
110
- element_tempdir = WorkDirManager().get_element_tempdir(
111
- prefix="afni_alff_falff"
112
- )
113
-
105
+ # Read header to get output suffix
106
+ niimg = nib.load(input_path)
107
+ header = niimg.header
108
+ sform_code = header.get_sform(coded=True)[1]
109
+ if sform_code == 4:
110
+ output_suffix = "tlrc"
111
+ else:
112
+ output_suffix = "orig"
113
+ # Set params suffix
114
114
  params_suffix = f"_{highpass}_{lowpass}_{tr}"
115
115
 
116
116
  # Convert alff afni to nifti
117
- alff_afni_to_nifti_out_path = (
118
- element_tempdir / f"alff{params_suffix}_output.nii"
117
+ alff_nifti_out_path = (
118
+ element_tempdir / f"output_alff{params_suffix}.nii"
119
119
  ) # needs to be .nii
120
120
  convert_alff_cmd = [
121
121
  "3dAFNItoNIFTI",
122
- f"-prefix {alff_afni_to_nifti_out_path.resolve()}",
123
- f"{alff_falff_out_path_prefix}_ALFF+orig.BRIK",
122
+ f"-prefix {alff_nifti_out_path.resolve()}",
123
+ f"{lff_out_path_prefix}_ALFF+{output_suffix}.BRIK",
124
124
  ]
125
125
  # Call 3dAFNItoNIFTI
126
126
  run_ext_cmd(name="3dAFNItoNIFTI", cmd=convert_alff_cmd)
127
127
 
128
128
  # Convert falff afni to nifti
129
- falff_afni_to_nifti_out_path = (
130
- element_tempdir / f"falff{params_suffix}_output.nii"
129
+ falff_nifti_out_path = (
130
+ element_tempdir / f"output_falff{params_suffix}.nii"
131
131
  ) # needs to be .nii
132
132
  convert_falff_cmd = [
133
133
  "3dAFNItoNIFTI",
134
- f"-prefix {falff_afni_to_nifti_out_path.resolve()}",
135
- f"{alff_falff_out_path_prefix}_fALFF+orig.BRIK",
134
+ f"-prefix {falff_nifti_out_path.resolve()}",
135
+ f"{lff_out_path_prefix}_fALFF+{output_suffix}.BRIK",
136
136
  ]
137
137
  # Call 3dAFNItoNIFTI
138
138
  run_ext_cmd(name="3dAFNItoNIFTI", cmd=convert_falff_cmd)
139
139
 
140
140
  # Load nifti
141
- alff_data = nib.load(alff_afni_to_nifti_out_path)
142
- falff_data = nib.load(falff_afni_to_nifti_out_path)
143
-
144
- # Delete tempdir
145
- WorkDirManager().delete_tempdir(tempdir)
141
+ alff_data = nib.load(alff_nifti_out_path)
142
+ falff_data = nib.load(falff_nifti_out_path)
146
143
 
147
144
  return (
148
145
  alff_data,
149
146
  falff_data,
150
- alff_afni_to_nifti_out_path,
151
- falff_afni_to_nifti_out_path,
152
- ) # type: ignore
147
+ alff_nifti_out_path,
148
+ falff_nifti_out_path,
149
+ )
@@ -47,7 +47,7 @@ class JuniferALFF(metaclass=Singleton):
47
47
  @lru_cache(maxsize=None, typed=True)
48
48
  def compute(
49
49
  self,
50
- data: "Nifti1Image",
50
+ input_path: Path,
51
51
  highpass: float,
52
52
  lowpass: float,
53
53
  tr: Optional[float],
@@ -56,8 +56,8 @@ class JuniferALFF(metaclass=Singleton):
56
56
 
57
57
  Parameters
58
58
  ----------
59
- data : 4D Niimg-like object
60
- Images to process.
59
+ input_path : pathlib.Path
60
+ Path to the input data.
61
61
  highpass : positive float
62
62
  Highpass cutoff frequency.
63
63
  lowpass : positive float
@@ -80,9 +80,10 @@ class JuniferALFF(metaclass=Singleton):
80
80
  logger.debug("Creating cache for ALFF computation via junifer")
81
81
 
82
82
  # Get scan data
83
- niimg_data = data.get_fdata().copy()
83
+ niimg = nib.load(input_path)
84
+ niimg_data = niimg.get_fdata().copy()
84
85
  if tr is None:
85
- tr = float(data.header["pixdim"][4]) # type: ignore
86
+ tr = float(niimg.header["pixdim"][4]) # type: ignore
86
87
  logger.info(f"`tr` not provided, using `tr` from header: {tr}")
87
88
 
88
89
  # Bandpass the data within the lowpass and highpass cutoff freqs
@@ -120,19 +121,17 @@ class JuniferALFF(metaclass=Singleton):
120
121
  # Calculate ALFF
121
122
  alff = numerator / np.sqrt(niimg_data.shape[-1])
122
123
  alff_data = nimg.new_img_like(
123
- ref_niimg=data,
124
+ ref_niimg=niimg,
124
125
  data=alff,
125
126
  )
126
127
  falff_data = nimg.new_img_like(
127
- ref_niimg=data,
128
+ ref_niimg=niimg,
128
129
  data=falff,
129
130
  )
130
131
 
131
- # Create element-scoped tempdir so that the ALFF and fALFF maps are
132
- # available later as nibabel stores file path reference for
133
- # loading on computation
132
+ # Create element-scoped tempdir
134
133
  element_tempdir = WorkDirManager().get_element_tempdir(
135
- prefix="junifer_alff+falff"
134
+ prefix="junifer_lff"
136
135
  )
137
136
  output_alff_path = element_tempdir / "output_alff.nii.gz"
138
137
  output_falff_path = element_tempdir / "output_falff.nii.gz"
@@ -146,7 +146,7 @@ class ALFFBase(BaseMarker):
146
146
  estimator = JuniferALFF()
147
147
  # Compute ALFF + fALFF
148
148
  alff, falff, alff_path, falff_path = estimator.compute( # type: ignore
149
- data=input_data["data"],
149
+ input_path=input_data["path"],
150
150
  highpass=self.highpass,
151
151
  lowpass=self.lowpass,
152
152
  tr=self.tr,
@@ -69,7 +69,9 @@ def test_ALFFSpheres(caplog: pytest.LogCaptureFixture, tmp_path: Path) -> None:
69
69
  # Fit transform marker on data
70
70
  output = marker.fit_transform(element_data)
71
71
 
72
- assert "Creating cache" in caplog.text
72
+ # Tests for ALFFParcels run before this with the same data and that
73
+ # should create the cache
74
+ assert "Calculating ALFF and fALFF" in caplog.text
73
75
 
74
76
  # Get BOLD output
75
77
  assert "BOLD" in output
@@ -50,7 +50,7 @@ class AFNIReHo(metaclass=Singleton):
50
50
  @lru_cache(maxsize=None, typed=True)
51
51
  def compute(
52
52
  self,
53
- data: "Nifti1Image",
53
+ input_path: Path,
54
54
  nneigh: int = 27,
55
55
  neigh_rad: Optional[float] = None,
56
56
  neigh_x: Optional[float] = None,
@@ -65,8 +65,8 @@ class AFNIReHo(metaclass=Singleton):
65
65
 
66
66
  Parameters
67
67
  ----------
68
- data : 4D Niimg-like object
69
- Images to process.
68
+ input_path : pathlib.Path
69
+ Path to the input data.
70
70
  nneigh : {7, 19, 27}, optional
71
71
  Number of voxels in the neighbourhood, inclusive. Can be:
72
72
 
@@ -128,19 +128,17 @@ class AFNIReHo(metaclass=Singleton):
128
128
  """
129
129
  logger.debug("Creating cache for ReHo computation via AFNI")
130
130
 
131
- # Create component-scoped tempdir
132
- tempdir = WorkDirManager().get_tempdir(prefix="afni_reho")
133
-
134
- # Save target data to a component-scoped tempfile
135
- nifti_in_file_path = tempdir / "input.nii" # needs to be .nii
136
- nib.save(data, nifti_in_file_path)
131
+ # Create element-scoped tempdir
132
+ element_tempdir = WorkDirManager().get_element_tempdir(
133
+ prefix="afni_reho"
134
+ )
137
135
 
138
136
  # Set 3dReHo command
139
- reho_out_path_prefix = tempdir / "reho"
137
+ reho_out_path_prefix = element_tempdir / "output"
140
138
  reho_cmd = [
141
139
  "3dReHo",
142
140
  f"-prefix {reho_out_path_prefix.resolve()}",
143
- f"-inset {nifti_in_file_path.resolve()}",
141
+ f"-inset {input_path.resolve()}",
144
142
  ]
145
143
  # Check ellipsoidal / cuboidal volume arguments
146
144
  if neigh_rad:
@@ -164,28 +162,28 @@ class AFNIReHo(metaclass=Singleton):
164
162
  # Call 3dReHo
165
163
  run_ext_cmd(name="3dReHo", cmd=reho_cmd)
166
164
 
167
- # Create element-scoped tempdir so that the ReHo map is
168
- # available later as nibabel stores file path reference for
169
- # loading on computation
170
- element_tempdir = WorkDirManager().get_element_tempdir(
171
- prefix="afni_reho"
172
- )
165
+ # Read header to get output suffix
166
+ niimg = nib.load(input_path)
167
+ header = niimg.header
168
+ sform_code = header.get_sform(coded=True)[1]
169
+ if sform_code == 4:
170
+ output_suffix = "tlrc"
171
+ else:
172
+ output_suffix = "orig"
173
+
173
174
  # Convert afni to nifti
174
- reho_afni_to_nifti_out_path = (
175
+ reho_nifti_out_path = (
175
176
  element_tempdir / "output.nii" # needs to be .nii
176
177
  )
177
178
  convert_cmd = [
178
179
  "3dAFNItoNIFTI",
179
- f"-prefix {reho_afni_to_nifti_out_path.resolve()}",
180
- f"{reho_out_path_prefix}+orig.BRIK",
180
+ f"-prefix {reho_nifti_out_path.resolve()}",
181
+ f"{reho_out_path_prefix}+{output_suffix}.BRIK",
181
182
  ]
182
183
  # Call 3dAFNItoNIFTI
183
184
  run_ext_cmd(name="3dAFNItoNIFTI", cmd=convert_cmd)
184
185
 
185
186
  # Load nifti
186
- output_data = nib.load(reho_afni_to_nifti_out_path)
187
-
188
- # Delete tempdir
189
- WorkDirManager().delete_tempdir(tempdir)
187
+ output_data = nib.load(reho_nifti_out_path)
190
188
 
191
- return output_data, reho_afni_to_nifti_out_path # type: ignore
189
+ return output_data, reho_nifti_out_path
@@ -48,15 +48,15 @@ class JuniferReHo(metaclass=Singleton):
48
48
  @lru_cache(maxsize=None, typed=True)
49
49
  def compute(
50
50
  self,
51
- data: "Nifti1Image",
51
+ input_path: Path,
52
52
  nneigh: int = 27,
53
53
  ) -> tuple["Nifti1Image", Path]:
54
54
  """Compute ReHo map.
55
55
 
56
56
  Parameters
57
57
  ----------
58
- data : 4D Niimg-like object
59
- Images to process.
58
+ input_path : pathlib.Path
59
+ Path to the input data.
60
60
  nneigh : {7, 19, 27, 125}, optional
61
61
  Number of voxels in the neighbourhood, inclusive. Can be:
62
62
 
@@ -89,7 +89,8 @@ class JuniferReHo(metaclass=Singleton):
89
89
  logger.debug("Creating cache for ReHo computation via junifer")
90
90
 
91
91
  # Get scan data
92
- niimg_data = data.get_fdata()
92
+ niimg = nib.load(input_path)
93
+ niimg_data = niimg.get_fdata().copy()
93
94
  # Get scan dimensions
94
95
  n_x, n_y, n_z, _ = niimg_data.shape
95
96
 
@@ -119,7 +120,7 @@ class JuniferReHo(metaclass=Singleton):
119
120
  # after #299 is merged
120
121
  # Calculate whole brain mask
121
122
  mni152_whole_brain_mask = nmask.compute_brain_mask(
122
- target_img=data,
123
+ target_img=niimg,
123
124
  threshold=0.5,
124
125
  mask_type="whole-brain",
125
126
  )
@@ -227,7 +228,7 @@ class JuniferReHo(metaclass=Singleton):
227
228
 
228
229
  # Create new image like target image
229
230
  output_data = nimg.new_img_like(
230
- ref_niimg=data,
231
+ ref_niimg=niimg,
231
232
  data=reho_map,
232
233
  copy_header=False,
233
234
  )
@@ -125,7 +125,7 @@ class ReHoBase(BaseMarker):
125
125
  estimator = JuniferReHo()
126
126
  # Compute reho
127
127
  reho_map, reho_map_path = estimator.compute( # type: ignore
128
- data=input_data["data"],
128
+ input_path=input_data["path"],
129
129
  **reho_params,
130
130
  )
131
131
 
junifer/utils/logging.py CHANGED
@@ -4,6 +4,7 @@
4
4
  # Synchon Mandal <s.mandal@fz-juelich.de>
5
5
  # License: AGPL
6
6
 
7
+ import os
7
8
  import sys
8
9
 
9
10
 
@@ -16,7 +17,7 @@ import logging
16
17
  import warnings
17
18
  from pathlib import Path
18
19
  from subprocess import PIPE, Popen, TimeoutExpired
19
- from typing import NoReturn, Optional, Union
20
+ from typing import ClassVar, NoReturn, Optional, Union
20
21
  from warnings import warn
21
22
 
22
23
  import datalad
@@ -80,6 +81,59 @@ class WrapStdOut(logging.StreamHandler):
80
81
  raise AttributeError(f"'file' object has not attribute '{name}'")
81
82
 
82
83
 
84
+ class ColorFormatter(logging.Formatter):
85
+ """Color formatter for logging messages.
86
+
87
+ Parameters
88
+ ----------
89
+ fmt : str
90
+ The format string for the logging message.
91
+ datefmt : str, optional
92
+ The format string for the date.
93
+
94
+ """
95
+
96
+ BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)
97
+
98
+ COLORS: ClassVar[dict[str, int]] = {
99
+ "WARNING": YELLOW,
100
+ "INFO": GREEN,
101
+ "DEBUG": BLUE,
102
+ "CRITICAL": MAGENTA,
103
+ "ERROR": RED,
104
+ }
105
+
106
+ RESET_SEQ: str = "\033[0m"
107
+ COLOR_SEQ: str = "\033[1;%dm"
108
+ BOLD_SEQ: str = "\033[1m"
109
+
110
+ def __init__(self, fmt: str, datefmt: Optional[str] = None) -> None:
111
+ """Initialize the ColorFormatter."""
112
+ logging.Formatter.__init__(self, fmt, datefmt)
113
+
114
+ def format(self, record: logging.LogRecord) -> str:
115
+ """Format the log record.
116
+
117
+ Parameters
118
+ ----------
119
+ record : logging.LogRecord
120
+ The log record to format.
121
+
122
+ Returns
123
+ -------
124
+ str
125
+ The formatted log record.
126
+
127
+ """
128
+ levelname = record.levelname
129
+ if levelname in self.COLORS:
130
+ levelname_color = (
131
+ self.COLOR_SEQ % (30 + self.COLORS[levelname]) + levelname
132
+ )
133
+ record.levelname = levelname_color + self.RESET_SEQ
134
+ return logging.Formatter.format(self, record)
135
+
136
+
83
137
  def _get_git_head(path: Path) -> str:
84
138
  """Aux function to read HEAD from git.
85
139
 
@@ -237,11 +291,51 @@ def log_versions(tbox_path: Optional[Path] = None) -> None:
237
291
  pass
238
292
 
239
293
 
294
+ def _can_use_color(handler: logging.Handler) -> bool:
295
+ """Check if color can be used in the logging output.
296
+
297
+ Parameters
298
+ ----------
299
+ handler : logging.Handler
300
+ The logging handler to check for color support.
301
+
302
+ Returns
303
+ -------
304
+ bool
305
+ Whether color can be used in the logging output.
306
+
307
+ """
308
+ if isinstance(handler, logging.FileHandler):
309
+ # Do not use colors in file handlers
310
+ return False
311
+ else:
312
+ stream = handler.stream
313
+ if hasattr(stream, "isatty") and stream.isatty():
314
+ valid_terms = [
315
+ "xterm-256color",
316
+ "xterm-kitty",
317
+ "xterm-color",
318
+ ]
319
+ this_term = os.getenv("TERM", None)
320
+ if this_term is not None:
321
+ if this_term in valid_terms:
322
+ return True
323
+ if this_term.endswith("256color") or this_term.endswith("256"):
324
+ return True
325
+ if this_term == "dumb" and os.getenv("CI", False):
326
+ return True
327
+ if os.getenv("COLORTERM", False):
328
+ return True
329
+ # No TTY, no color
330
+ return False
331
+
332
+
240
333
  def configure_logging(
241
334
  level: Union[int, str] = "WARNING",
242
335
  fname: Optional[Union[str, Path]] = None,
243
336
  overwrite: Optional[bool] = None,
244
337
  output_format=None,
338
+ level_datalad: Union[int, str, None] = None,
245
339
  ) -> None:
246
340
  """Configure the logging functionality.
247
341
 
@@ -264,6 +358,10 @@ def configure_logging(
264
358
  e.g., ``"%(asctime)s - %(levelname)s - %(message)s"``.
265
359
  If None, default string format is used
266
360
  (default ``"%(asctime)s - %(name)s - %(levelname)s - %(message)s"``).
361
+ level_datalad : int or {"DEBUG", "INFO", "WARNING", "ERROR"}, optional
362
+ The level of the messages to print for datalad. If string, it will be
363
+ interpreted as elements of logging. If None, it will be set as the
364
+ ``level`` parameter (default None).
267
365
 
268
366
  """
269
367
  _close_handlers(logger) # close relevant logger handlers
@@ -297,11 +395,22 @@ def configure_logging(
297
395
  # "%(asctime)s [%(levelname)s] %(message)s "
298
396
  # "(%(filename)s:%(lineno)s)"
299
397
  # )
300
- formatter = logging.Formatter(fmt=output_format)
398
+ if _can_use_color(lh):
399
+ formatter = ColorFormatter(fmt=output_format)
400
+ else:
401
+ formatter = logging.Formatter(fmt=output_format)
301
402
 
302
403
  lh.setFormatter(formatter) # set formatter
303
404
  logger.setLevel(level) # set level
304
- datalad.log.lgr.setLevel(level) # set level for datalad
405
+
406
+ # Set datalad logging level accordingly
407
+ if level_datalad is not None:
408
+ if isinstance(level_datalad, str):
409
+ level_datalad = _logging_types[level_datalad]
410
+ else:
411
+ level_datalad = level
412
+ datalad.log.lgr.setLevel(level_datalad) # set level for datalad
413
+
305
414
  logger.addHandler(lh) # set handler
306
415
  log_versions() # log versions of installed packages
307
416
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: junifer
3
- Version: 0.0.6.dev317
3
+ Version: 0.0.6.dev330
4
4
  Summary: JUelich NeuroImaging FEature extractoR
5
5
  Author-email: Fede Raimondo <f.raimondo@fz-juelich.de>, Synchon Mandal <s.mandal@fz-juelich.de>
6
6
  Maintainer-email: Fede Raimondo <f.raimondo@fz-juelich.de>, Synchon Mandal <s.mandal@fz-juelich.de>
@@ -1,6 +1,6 @@
1
1
  junifer/__init__.py,sha256=2McgH1yNue6Z1V26-uN_mfMjbTcx4CLhym-DMBl5xA4,266
2
2
  junifer/__init__.pyi,sha256=SsTvgq2Dod6UqJN96GH1lCphH6hJQQurEJHGNhHjGUI,508
3
- junifer/_version.py,sha256=ysdlII3ZUlFBiHIzSCvasFtc1CMEpYMsiJEwLoFxjKQ,428
3
+ junifer/_version.py,sha256=ib7p2sRvl9n6dHPTqNlqVwUPPOKf5EKIksHMk7EQqno,428
4
4
  junifer/conftest.py,sha256=PWYkkRDU8ly2lYwv7VBKMHje4et6HX7Yey3Md_I2KbA,613
5
5
  junifer/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  junifer/stats.py,sha256=e9aaagMGtgpRfW3Wdpz9ocpnYld1IWylCDcjFUgX9Mk,6225
@@ -11,8 +11,8 @@ junifer/api/functions.py,sha256=Tq1LKoxJuHHylcCFJ6SrPoPriIsnTLdbQ5K6w8gdTE8,1369
11
11
  junifer/api/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
12
  junifer/api/queue_context/__init__.py,sha256=glr8x4aMm4EvVrHywDIlugdNlwD1RzqV2FTDNPqYQZ4,204
13
13
  junifer/api/queue_context/__init__.pyi,sha256=LoDQFGZ9wCDmgx5a1_nhKo4zOSvqViXZ8V882DksF7U,246
14
- junifer/api/queue_context/gnu_parallel_local_adapter.py,sha256=HBsH9ql0AnU87KfiUEpQ7rCakbFEf4dlz8JzYdlqEpg,9542
15
- junifer/api/queue_context/htcondor_adapter.py,sha256=MihS26FJX92_0ysoJm-INP1YUoRcnU_S_WVCgbkoD08,13214
14
+ junifer/api/queue_context/gnu_parallel_local_adapter.py,sha256=hFeLoqqHiB7JXE5cwkVDRX00tjKyK60xAxvE1uO7OOk,10178
15
+ junifer/api/queue_context/htcondor_adapter.py,sha256=9TXtPhBsDdGFgkIhtsySFCi4QUjerUXMq5kMHpxNg90,13878
16
16
  junifer/api/queue_context/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
17
  junifer/api/queue_context/queue_context_adapter.py,sha256=a6UE8xavDfuaZbkWYsayVs6l-rwIrbpFSpqSyHsEeYY,1577
18
18
  junifer/api/queue_context/tests/test_gnu_parallel_local_adapter.py,sha256=Nv_0axIW4SOE7-TyQXd_nM_0A_kDiFAgrkQcmQafW_s,6585
@@ -43,7 +43,7 @@ junifer/api/res/fsl/std2imgcoord,sha256=-X5wRH6XMl0yqnTACJX6MFhO8DFOEWg42MHRxGvi
43
43
  junifer/api/tests/test_functions.py,sha256=PPkjs_FqjZZgsEa3a81ulzPxLskVh5HmnaG8TaNCwR0,18084
44
44
  junifer/cli/__init__.py,sha256=DS3kZKHeVDxt6d1MLBerZ2fcAwrEBHee5JOBhOLajUI,197
45
45
  junifer/cli/__init__.pyi,sha256=PiV4znUnzSeuSSJGz-RT8N21PiMqoSMwYcypi7nt2Js,40
46
- junifer/cli/cli.py,sha256=Q0l7dBclLrM0P5MU2G0MA7W8u23VtqIQdwaeoOy-_ws,13519
46
+ junifer/cli/cli.py,sha256=LvtkFMvnxuoviAhs0bki6P4GMda6ZvNsJi4_QF00va8,15691
47
47
  junifer/cli/parser.py,sha256=ouVnk4NrOmVMNnQDMsqAjt2BaOT05bNJq0LVRsIakXI,8358
48
48
  junifer/cli/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
49
  junifer/cli/utils.py,sha256=AbPQC0Kl-tHMNKiPxp_01gLAGD3IGoLbsq3rXyPMM-c,3116
@@ -208,14 +208,14 @@ junifer/markers/complexity/tests/test_sample_entropy.py,sha256=rfbiguVq7CUwYIvYB
208
208
  junifer/markers/complexity/tests/test_weighted_perm_entropy.py,sha256=yDWKEaUbxrnrG6J2NlktLfwSBre5OuXd63kEof7t8PM,2373
209
209
  junifer/markers/falff/__init__.py,sha256=qxdx_3FsVrn7h3gtbocK0ZmvqZwPQZGKuVkPm31ejNM,217
210
210
  junifer/markers/falff/__init__.pyi,sha256=X-q2zBjUX0imQ37yN2Cg5gKfDvq8sh_9y2hRH4g5ufY,120
211
- junifer/markers/falff/_afni_falff.py,sha256=D_hhT-rfWXeIe-Pwh4pK2O7N9Q07VyMOUHeAz6Ooudo,4518
212
- junifer/markers/falff/_junifer_falff.py,sha256=TnZ6T8u0f0YitJ7KRk4gMcC1tv6UW8m4VVcvcjGRsL0,4450
213
- junifer/markers/falff/falff_base.py,sha256=XZcBfXA74tWrW--uCALkRLl1tB7toR3_HfzPJUblz8g,4909
211
+ junifer/markers/falff/_afni_falff.py,sha256=PYkSOFMyaHoGYDvmBjKLW1ALyWBe7yI36JBqZ71ji2c,4223
212
+ junifer/markers/falff/_junifer_falff.py,sha256=1PsavcopVjPtfmPZsnNi5ynl2GTfnCx9qjuKDb7YejE,4347
213
+ junifer/markers/falff/falff_base.py,sha256=WtJTMRn_Vmv9RZaaeLeyZpCsQc8QgB3UqIPWsUI3Lh4,4915
214
214
  junifer/markers/falff/falff_parcels.py,sha256=sSb6QLaJKpL0GCTRWW3RnpOZCoy1f9lDLgJ0I_W_LlM,6017
215
215
  junifer/markers/falff/falff_spheres.py,sha256=GrakJYPB01y9BNBXM8WzWaae0mC-S06txiycvfBGcj0,6656
216
216
  junifer/markers/falff/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
217
217
  junifer/markers/falff/tests/test_falff_parcels.py,sha256=Z3n1i8dkYbdXgouUjfIif9yLv5MubBEdrtAA-a6kRcc,4349
218
- junifer/markers/falff/tests/test_falff_spheres.py,sha256=-VLEvFaF8CMCN_7FLYCSfP7MMjy-gm1Zgu13je5Pku8,4373
218
+ junifer/markers/falff/tests/test_falff_spheres.py,sha256=PGsxFjMxsH8HxIHVtdcQcX8suFa18ma_12Almknzn88,4503
219
219
  junifer/markers/functional_connectivity/__init__.py,sha256=dGTn69eS7a3rylMQh_wKlO28UmYGjsoDEGu4q5sgQFA,230
220
220
  junifer/markers/functional_connectivity/__init__.pyi,sha256=qfw6WVyE65u-5NZNi0xPa8zZVtkRfFvwyl4jHH2Xl00,539
221
221
  junifer/markers/functional_connectivity/crossparcellation_functional_connectivity.py,sha256=uLdVGywmL7qrzloh1YBL4g4tPiamA47MgHF2DQH0JTU,5733
@@ -233,10 +233,10 @@ junifer/markers/functional_connectivity/tests/test_functional_connectivity_parce
233
233
  junifer/markers/functional_connectivity/tests/test_functional_connectivity_spheres.py,sha256=A9OtFdndiSGOcPHH-QLPh6qoiD03A6KjM_emwxAlPg0,4145
234
234
  junifer/markers/reho/__init__.py,sha256=WZf4A0XaRThjl8SlFOhvTLUfhTHp5koLxZgowsgTSAE,211
235
235
  junifer/markers/reho/__init__.pyi,sha256=_aFb-Ry_EP2OMU6xRL4GlfuDpSl_egHllL-fz7vXjcE,118
236
- junifer/markers/reho/_afni_reho.py,sha256=YBqNYipZO8EFM4Jmek_A36zr9n4nTR0RVDPpxjJKLxM,6466
237
- junifer/markers/reho/_junifer_reho.py,sha256=86oBH8UtWsHJJGz2-uRXAjdGNHvaSV6Xu1v7-9AJqLs,9340
236
+ junifer/markers/reho/_afni_reho.py,sha256=SOWR5y9AYKfw1wj2Z4Wy7ckMUVTmeS378bayvPPVqqo,6225
237
+ junifer/markers/reho/_junifer_reho.py,sha256=14ObaRa2-0JzcoYLJyhnx4bgPCpTdciDXmrn9-gPv20,9387
238
238
  junifer/markers/reho/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
239
- junifer/markers/reho/reho_base.py,sha256=Amji7hNtkT9D-teJ2tTzDsVHKRXETDwhICDKa8ZGehY,4071
239
+ junifer/markers/reho/reho_base.py,sha256=Q88TbhIM4rQWdeQPLwwxwZ9DrR8l09orD1rdTkSYDtc,4077
240
240
  junifer/markers/reho/reho_parcels.py,sha256=UE1ia3uqbmTcZMc_FI625xVPLxBYvwpfrcvhekopbkI,6392
241
241
  junifer/markers/reho/reho_spheres.py,sha256=FCC2qncC85Kd82hg-MOu4T7NAKEkXHUaCcwC9taau9Y,6996
242
242
  junifer/markers/reho/tests/test_reho_parcels.py,sha256=bRtDi91qRcRYaRqqQjuSU6NuNz-KwLVCoTYo-e5VmsI,4075
@@ -334,17 +334,17 @@ junifer/utils/_config.py,sha256=cfxyv1bfklID2atQseu6y3J7mZrCXPwnGEfBSImG9CM,3054
334
334
  junifer/utils/_yaml.py,sha256=jpTroTI2rajECj0RXGCXaOwLpad858WzI7Jg-eXJ_jU,336
335
335
  junifer/utils/fs.py,sha256=M3CKBLh4gPS6s9giyopgb1hHMXzLb6k3cung2wHVBjs,492
336
336
  junifer/utils/helpers.py,sha256=QcfdHPhrYKTf6o5eSOIvDxqmIAxlp9SqmCEdR10jbIY,2033
337
- junifer/utils/logging.py,sha256=DRWcKwez56Mfh5PyLQSaKgL0Cc_Om8qfO8zGHTHB5Vo,9769
337
+ junifer/utils/logging.py,sha256=8rWEtZPohugiB7niF9yvybi79w3Sj5xcaImJhsuXxks,12998
338
338
  junifer/utils/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
339
339
  junifer/utils/singleton.py,sha256=iATJMdzsSVE9akTI4DDycubhAl98t0EdO17gwgOjAYA,1189
340
340
  junifer/utils/tests/test_config.py,sha256=7ltIXuwb_W4Mv_1dxQWyiyM10XgUAfsWKV6D_iE-XU0,1540
341
341
  junifer/utils/tests/test_fs.py,sha256=WQS7cKlKEZ742CIuiOYYpueeAhY9PqlastfDVpVVtvE,923
342
342
  junifer/utils/tests/test_helpers.py,sha256=k5qqfxK8dFyuewTJyR1Qn6-nFaYNuVr0ysc18bfPjyU,929
343
343
  junifer/utils/tests/test_logging.py,sha256=duO4ou365hxwa_kwihFtKPLaL6LC5XHiyhOijrrngbA,8009
344
- junifer-0.0.6.dev317.dist-info/AUTHORS.rst,sha256=rmULKpchpSol4ExWFdm-qu4fkpSZPYqIESVJBZtGb6E,163
345
- junifer-0.0.6.dev317.dist-info/LICENSE.md,sha256=MqCnOBu8uXsEOzRZWh9EBVfVz-kE9NkXcLCrtGXo2yU,34354
346
- junifer-0.0.6.dev317.dist-info/METADATA,sha256=aQiIDdr_Ov-v0da-VOALNQiJe1FIGM-Xz32tTTqlW00,8429
347
- junifer-0.0.6.dev317.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
348
- junifer-0.0.6.dev317.dist-info/entry_points.txt,sha256=6O8ru0BP-SP7YMUZiizFNoaZ2HvJpadO2G7nKk4PwjI,48
349
- junifer-0.0.6.dev317.dist-info/top_level.txt,sha256=4bAq1R2QFQ4b3hohjys2JBvxrl0GKk5LNFzYvz9VGcA,8
350
- junifer-0.0.6.dev317.dist-info/RECORD,,
344
+ junifer-0.0.6.dev330.dist-info/AUTHORS.rst,sha256=rmULKpchpSol4ExWFdm-qu4fkpSZPYqIESVJBZtGb6E,163
345
+ junifer-0.0.6.dev330.dist-info/LICENSE.md,sha256=MqCnOBu8uXsEOzRZWh9EBVfVz-kE9NkXcLCrtGXo2yU,34354
346
+ junifer-0.0.6.dev330.dist-info/METADATA,sha256=LkxGgT6R7p-H0fZTaFoWi71TBNqAnRpuXVa9w6DUhZ4,8429
347
+ junifer-0.0.6.dev330.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
348
+ junifer-0.0.6.dev330.dist-info/entry_points.txt,sha256=6O8ru0BP-SP7YMUZiizFNoaZ2HvJpadO2G7nKk4PwjI,48
349
+ junifer-0.0.6.dev330.dist-info/top_level.txt,sha256=4bAq1R2QFQ4b3hohjys2JBvxrl0GKk5LNFzYvz9VGcA,8
350
+ junifer-0.0.6.dev330.dist-info/RECORD,,