augmentrum 0.0.1__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.
- augmentrum/__init__.py +23 -0
- augmentrum/augmentation/__init__.py +32 -0
- augmentrum/augmentation/amplitude_scaling.py +170 -0
- augmentrum/augmentation/apodization.py +275 -0
- augmentrum/augmentation/artificial_peaks.py +334 -0
- augmentrum/augmentation/baseline_augmentation.py +544 -0
- augmentrum/augmentation/eddy_current.py +278 -0
- augmentrum/augmentation/gaussian_noise.py +211 -0
- augmentrum/augmentation/line_broadening.py +179 -0
- augmentrum/augmentation/phase_frequency.py +259 -0
- augmentrum/augmentation/residual_water.py +264 -0
- augmentrum/augmentation/spatial_augmentations.py +859 -0
- augmentrum/augmentation/spurious_echoes.py +378 -0
- augmentrum/augmentation/zero_fill.py +163 -0
- augmentrum/core/__init__.py +10 -0
- augmentrum/core/augmentrum.py +1172 -0
- augmentrum/core/base_module.py +423 -0
- augmentrum/core/dataset_utils.py +417 -0
- augmentrum/core/nifti_mrs_plus.py +1079 -0
- augmentrum/core/pipeline.py +358 -0
- augmentrum/dataset/brainbeats.py +537 -0
- augmentrum/dataset/cows.py +302 -0
- augmentrum/dataset/fmrsinpain.py +208 -0
- augmentrum/processing/bicubic_interpolator.py +296 -0
- augmentrum/processing/nifti_raw_processor.py +389 -0
- augmentrum/processing/tricubic_interpolator.py +293 -0
- augmentrum/processing/utils.py +414 -0
- augmentrum/resources/__init__.py +14 -0
- augmentrum/resources/scans/__init__.py +0 -0
- augmentrum/sampling/coil_average_sampler.py +274 -0
- augmentrum/sampling/kspace_reconstructor.py +275 -0
- augmentrum/sampling/kspace_sampling.py +2562 -0
- augmentrum/sampling/subject_splitter.py +131 -0
- augmentrum/utils/__init__.py +10 -0
- augmentrum/utils/philips.py +90 -0
- augmentrum/utils/plotting.py +651 -0
- augmentrum/utils/tensor_ops.py +203 -0
- augmentrum-0.0.1.dist-info/METADATA +189 -0
- augmentrum-0.0.1.dist-info/RECORD +42 -0
- augmentrum-0.0.1.dist-info/WHEEL +5 -0
- augmentrum-0.0.1.dist-info/licenses/LICENSE +202 -0
- augmentrum-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
####################################################################################################
|
|
2
|
+
# dataset_utils.py #
|
|
3
|
+
####################################################################################################
|
|
4
|
+
# #
|
|
5
|
+
# Authors: J. P. Merkofer (j.p.merkofer@tue.nl) #
|
|
6
|
+
# #
|
|
7
|
+
# Created: 2026-02-07 #
|
|
8
|
+
# #
|
|
9
|
+
# Purpose: Backend-agnostic dataset utilities for Augmentrum. #
|
|
10
|
+
# #
|
|
11
|
+
####################################################################################################
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
#*************#
|
|
15
|
+
# imports #
|
|
16
|
+
#*************#
|
|
17
|
+
import random
|
|
18
|
+
import numpy as np
|
|
19
|
+
from typing import List, Optional, Tuple, Dict, Callable, Union
|
|
20
|
+
|
|
21
|
+
# internal
|
|
22
|
+
from augmentrum.core import NIfTI_MRS_Plus, Backend
|
|
23
|
+
from augmentrum.sampling.subject_splitter import SubjectSplitter
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
'create_random_generator',
|
|
28
|
+
'create_fixed_generator',
|
|
29
|
+
'create_deterministic_generator',
|
|
30
|
+
'create_deterministic_indices',
|
|
31
|
+
'convert_batch_to_backend',
|
|
32
|
+
'wrap_generator_for_framework',
|
|
33
|
+
'SubjectSplitter',
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
#**************************************************************************************************#
|
|
38
|
+
# Helper Functions #
|
|
39
|
+
#**************************************************************************************************#
|
|
40
|
+
|
|
41
|
+
def create_deterministic_indices(data: NIfTI_MRS_Plus, n_coils: Tuple, n_averages: Tuple) -> List[Tuple]:
|
|
42
|
+
"""
|
|
43
|
+
Create all possible (subject_idx, coil_idx, average_idx) combinations.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
data: NIfTI_MRS_Plus containing subjects
|
|
47
|
+
n_coils: (min, max) coils to sample
|
|
48
|
+
n_averages: (min, max) averages to sample
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
List of (subject_idx, coil_idx, average_idx) tuples
|
|
52
|
+
"""
|
|
53
|
+
indices = []
|
|
54
|
+
nifti_list = data.list()
|
|
55
|
+
|
|
56
|
+
for subj_idx, nifti in enumerate(nifti_list):
|
|
57
|
+
# Get coil limits
|
|
58
|
+
if 'DIM_COIL' in getattr(nifti, 'dim_tags', []):
|
|
59
|
+
c_max = nifti.shape[nifti.dim_position('DIM_COIL')] - 1
|
|
60
|
+
min_c, max_c = _get_limits(n_coils, c_max)
|
|
61
|
+
else:
|
|
62
|
+
min_c, max_c, c_max = 0, 1, 0
|
|
63
|
+
|
|
64
|
+
# Get average limits
|
|
65
|
+
if 'DIM_DYN' in getattr(nifti, 'dim_tags', []):
|
|
66
|
+
a_max = nifti.shape[nifti.dim_position('DIM_DYN')] - 1
|
|
67
|
+
min_a, max_a = _get_limits(n_averages, a_max)
|
|
68
|
+
else:
|
|
69
|
+
min_a, max_a, a_max = 0, 1, 0
|
|
70
|
+
|
|
71
|
+
# Generate all combinations
|
|
72
|
+
for n_c in range(min_c, max_c + 1):
|
|
73
|
+
for n_a in range(min_a, max_a + 1):
|
|
74
|
+
indices.append((subj_idx, c_max - n_c, a_max - n_a))
|
|
75
|
+
|
|
76
|
+
return indices
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _get_limits(n: Tuple, n_max: int) -> Tuple[int, int]:
|
|
80
|
+
"""Convert (min, max) with possible None to concrete limits."""
|
|
81
|
+
min_n = n_max if n[0] is None else max(0, min(n[0], n_max))
|
|
82
|
+
max_n = n_max if n[1] is None else min(n[1], n_max)
|
|
83
|
+
return min_n, max_n
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
#**************************************************************************************************#
|
|
87
|
+
# Backend-Agnostic Generators #
|
|
88
|
+
#**************************************************************************************************#
|
|
89
|
+
|
|
90
|
+
def _make_batch(data, water, indices, copy=False):
|
|
91
|
+
"""
|
|
92
|
+
Build a (batch_data, batch_water) NIfTI_MRS_Plus pair from subject indices.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
data: NIfTI_MRS_Plus pool of subjects.
|
|
96
|
+
water: Optional NIfTI_MRS_Plus pool of water references.
|
|
97
|
+
indices: List of integer subject indices for this batch.
|
|
98
|
+
copy: If True, copy each NIfTI-MRS object (use for fixed mode
|
|
99
|
+
where the same objects are yielded multiple times).
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
(batch_data, batch_water) as NIfTI_MRS_Plus objects.
|
|
103
|
+
"""
|
|
104
|
+
from augmentrum.core.nifti_mrs_plus import NIfTI_MRS_Plus
|
|
105
|
+
|
|
106
|
+
get = (lambda idx: data[idx].copy()) if copy else (lambda idx: data[idx])
|
|
107
|
+
batch_data = NIfTI_MRS_Plus(
|
|
108
|
+
[get(i) for i in indices], backend=data.backend, volatile=data.volatile,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
batch_water = None
|
|
112
|
+
if water is not None:
|
|
113
|
+
get_w = (lambda idx: water[idx].copy()) if copy else (lambda idx: water[idx])
|
|
114
|
+
batch_water = NIfTI_MRS_Plus(
|
|
115
|
+
[get_w(i) for i in indices], backend=water.backend, volatile=water.volatile,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
return batch_data, batch_water
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def create_random_generator(data: NIfTI_MRS_Plus,
|
|
122
|
+
water: Optional[NIfTI_MRS_Plus],
|
|
123
|
+
pipeline,
|
|
124
|
+
batch_size: int):
|
|
125
|
+
"""
|
|
126
|
+
Infinite random-sampling generator (on-the-fly mode).
|
|
127
|
+
|
|
128
|
+
Each iteration: pick *batch_size* random subjects, sample fresh
|
|
129
|
+
augmentation parameters, apply the pipeline, yield the batch.
|
|
130
|
+
|
|
131
|
+
Yields:
|
|
132
|
+
(batch_data, batch_water) — NIfTI_MRS_Plus objects.
|
|
133
|
+
"""
|
|
134
|
+
n_subjects = len(data)
|
|
135
|
+
|
|
136
|
+
while True:
|
|
137
|
+
indices = [random.randint(0, n_subjects - 1) for _ in range(batch_size)]
|
|
138
|
+
batch_data, batch_water = _make_batch(data, water, indices)
|
|
139
|
+
batch_params = pipeline.sample_batch_parameters(batch_size)
|
|
140
|
+
yield pipeline(batch_data, batch_water, batch_params=batch_params)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def create_fixed_generator(data: NIfTI_MRS_Plus,
|
|
144
|
+
water: Optional[NIfTI_MRS_Plus],
|
|
145
|
+
pipeline,
|
|
146
|
+
batch_size: int,
|
|
147
|
+
shuffle: bool = False):
|
|
148
|
+
"""
|
|
149
|
+
Single-pass generator with fixed augmentation parameters.
|
|
150
|
+
|
|
151
|
+
Parameters are sampled **once** at creation and reused for every batch,
|
|
152
|
+
giving consistent results across epochs.
|
|
153
|
+
|
|
154
|
+
Yields:
|
|
155
|
+
(batch_data, batch_water) — NIfTI_MRS_Plus objects.
|
|
156
|
+
"""
|
|
157
|
+
fixed_params = pipeline.sample_batch_parameters(batch_size)
|
|
158
|
+
|
|
159
|
+
n_subjects = len(data)
|
|
160
|
+
indices = list(range(n_subjects))
|
|
161
|
+
if shuffle:
|
|
162
|
+
random.shuffle(indices)
|
|
163
|
+
|
|
164
|
+
for start in range(0, n_subjects, batch_size):
|
|
165
|
+
batch_idx = indices[start : start + batch_size]
|
|
166
|
+
batch_data, batch_water = _make_batch(data, water, batch_idx, copy=True)
|
|
167
|
+
yield pipeline(batch_data, batch_water, batch_params=fixed_params)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def create_deterministic_generator(data: NIfTI_MRS_Plus,
|
|
171
|
+
water: Optional[NIfTI_MRS_Plus],
|
|
172
|
+
pipeline,
|
|
173
|
+
batch_size: int,
|
|
174
|
+
n_coils: Tuple,
|
|
175
|
+
n_averages: Tuple,
|
|
176
|
+
shuffle: bool = False):
|
|
177
|
+
"""
|
|
178
|
+
Create deterministic generator over all combinations (backend-agnostic).
|
|
179
|
+
|
|
180
|
+
DEPRECATED: Use 'fixed' mode instead for simpler iteration.
|
|
181
|
+
This creates a combinatorial explosion of (subject × coils × averages).
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
data: NIfTI_MRS_Plus with subjects
|
|
185
|
+
water: Optional water reference
|
|
186
|
+
pipeline: Augmentation pipeline to apply
|
|
187
|
+
batch_size: Batch size
|
|
188
|
+
n_coils: (min, max) coils
|
|
189
|
+
n_averages: (min, max) averages
|
|
190
|
+
shuffle: Whether to shuffle indices
|
|
191
|
+
|
|
192
|
+
Yields:
|
|
193
|
+
(batch_data, batch_water) tuples
|
|
194
|
+
"""
|
|
195
|
+
# Create all possible indices
|
|
196
|
+
indices = create_deterministic_indices(data, n_coils, n_averages)
|
|
197
|
+
|
|
198
|
+
if shuffle:
|
|
199
|
+
import random
|
|
200
|
+
random.shuffle(indices)
|
|
201
|
+
|
|
202
|
+
# Yield batches
|
|
203
|
+
for i in range(0, len(indices), batch_size):
|
|
204
|
+
batch_indices = indices[i:i + batch_size]
|
|
205
|
+
batch_data_list, batch_water_list = [], []
|
|
206
|
+
|
|
207
|
+
for subj_idx, coil_idx, avg_idx in batch_indices:
|
|
208
|
+
# Get subject
|
|
209
|
+
subj_data = data[subj_idx]
|
|
210
|
+
subj_water = water[subj_idx] if water is not None else None
|
|
211
|
+
|
|
212
|
+
# Apply pipeline with specific indices
|
|
213
|
+
aug_data, aug_water = pipeline(
|
|
214
|
+
subj_data, subj_water,
|
|
215
|
+
coil_indices=coil_idx,
|
|
216
|
+
average_indices=avg_idx
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
batch_data_list.append(aug_data)
|
|
220
|
+
batch_water_list.append(aug_water)
|
|
221
|
+
|
|
222
|
+
yield batch_data_list, batch_water_list
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
#**************************************************************************************************#
|
|
226
|
+
# Backend Conversion Functions #
|
|
227
|
+
#**************************************************************************************************#
|
|
228
|
+
|
|
229
|
+
def convert_batch_to_backend(batch_data: List, batch_water: List, backend: Backend):
|
|
230
|
+
"""
|
|
231
|
+
Convert batch of NIFTI_MRS objects to target backend format.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
batch_data: List of NIFTI_MRS or data arrays
|
|
235
|
+
batch_water: List of NIFTI_MRS or None
|
|
236
|
+
backend: Target backend
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
(converted_data, converted_water)
|
|
240
|
+
"""
|
|
241
|
+
if backend == Backend.NIFTI_LIST:
|
|
242
|
+
return batch_data, batch_water
|
|
243
|
+
|
|
244
|
+
elif backend == Backend.NUMPY:
|
|
245
|
+
# Convert to numpy arrays
|
|
246
|
+
data_arrays = []
|
|
247
|
+
for item in batch_data:
|
|
248
|
+
if hasattr(item, '__array__'):
|
|
249
|
+
data_arrays.append(np.array(item))
|
|
250
|
+
elif isinstance(item, NIfTI_MRS_Plus):
|
|
251
|
+
data_arrays.append(item.numpy())
|
|
252
|
+
else:
|
|
253
|
+
# Assume it's a NIFTI_MRS
|
|
254
|
+
data_arrays.append(item[:])
|
|
255
|
+
|
|
256
|
+
water_arrays = None
|
|
257
|
+
if batch_water and batch_water[0] is not None:
|
|
258
|
+
water_arrays = []
|
|
259
|
+
for item in batch_water:
|
|
260
|
+
if hasattr(item, '__array__'):
|
|
261
|
+
water_arrays.append(np.array(item))
|
|
262
|
+
elif isinstance(item, NIfTI_MRS_Plus):
|
|
263
|
+
water_arrays.append(item.numpy())
|
|
264
|
+
else:
|
|
265
|
+
water_arrays.append(item[:])
|
|
266
|
+
water_arrays = np.array(water_arrays) if water_arrays else None
|
|
267
|
+
|
|
268
|
+
return np.array(data_arrays), water_arrays
|
|
269
|
+
|
|
270
|
+
elif backend == Backend.PYTORCH:
|
|
271
|
+
import torch
|
|
272
|
+
# Convert to torch tensors
|
|
273
|
+
data_np, water_np = convert_batch_to_backend(batch_data, batch_water, Backend.NUMPY)
|
|
274
|
+
data_torch = torch.from_numpy(data_np) if data_np is not None else None
|
|
275
|
+
water_torch = torch.from_numpy(water_np) if water_np is not None else None
|
|
276
|
+
return data_torch, water_torch
|
|
277
|
+
|
|
278
|
+
elif backend in [Backend.TENSORFLOW, Backend.KERAS]:
|
|
279
|
+
import tensorflow as tf
|
|
280
|
+
# Convert to tensorflow tensors
|
|
281
|
+
data_np, water_np = convert_batch_to_backend(batch_data, batch_water, Backend.NUMPY)
|
|
282
|
+
data_tf = tf.convert_to_tensor(data_np) if data_np is not None else None
|
|
283
|
+
water_tf = tf.convert_to_tensor(water_np) if water_np is not None else None
|
|
284
|
+
return data_tf, water_tf
|
|
285
|
+
|
|
286
|
+
elif backend == Backend.JAX:
|
|
287
|
+
import jax.numpy as jnp
|
|
288
|
+
# Convert to jax arrays
|
|
289
|
+
data_np, water_np = convert_batch_to_backend(batch_data, batch_water, Backend.NUMPY)
|
|
290
|
+
data_jax = jnp.array(data_np) if data_np is not None else None
|
|
291
|
+
water_jax = jnp.array(water_np) if water_np is not None else None
|
|
292
|
+
return data_jax, water_jax
|
|
293
|
+
|
|
294
|
+
else:
|
|
295
|
+
raise ValueError(f"Unknown backend: {backend}")
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
#**************************************************************************************************#
|
|
299
|
+
# Framework DataLoader Wrappers #
|
|
300
|
+
#**************************************************************************************************#
|
|
301
|
+
|
|
302
|
+
def wrap_generator_for_framework(generator: Callable,
|
|
303
|
+
backend: Backend,
|
|
304
|
+
framework: str = None):
|
|
305
|
+
"""
|
|
306
|
+
Wrap a generator for a specific framework (PyTorch DataLoader, tf.data, etc.).
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
generator: Generator (iterator) yielding (data, water) batches
|
|
310
|
+
backend: Target backend for data conversion
|
|
311
|
+
framework: 'pytorch', 'tensorflow', 'keras', 'jax', 'numpy', None (raw)
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
Framework-specific dataloader/dataset/generator
|
|
315
|
+
"""
|
|
316
|
+
# Default framework from backend
|
|
317
|
+
if framework is None:
|
|
318
|
+
backend_name = backend.name.lower()
|
|
319
|
+
# nifti_list backend should default to raw python iteration
|
|
320
|
+
if backend_name == 'nifti_list':
|
|
321
|
+
framework = 'python'
|
|
322
|
+
else:
|
|
323
|
+
framework = backend_name
|
|
324
|
+
|
|
325
|
+
# For nifti_list or raw python: unwrap NIfTI_MRS_Plus to raw NIFTI_MRS lists
|
|
326
|
+
if framework in ['python', 'nifti_list']:
|
|
327
|
+
def unwrapped_gen():
|
|
328
|
+
for batch_data, batch_water in generator:
|
|
329
|
+
# Unwrap NIfTI_MRS_Plus objects to raw NIFTI_MRS lists
|
|
330
|
+
if isinstance(batch_data, list):
|
|
331
|
+
# Batch is a list of NIfTI_MRS_Plus objects, unwrap each
|
|
332
|
+
data_list = []
|
|
333
|
+
for item in batch_data:
|
|
334
|
+
if hasattr(item, 'list'):
|
|
335
|
+
# It's a NIfTI_MRS_Plus, get the internal list
|
|
336
|
+
data_list.extend(item.list())
|
|
337
|
+
else:
|
|
338
|
+
# Already a NIFTI_MRS object
|
|
339
|
+
data_list.append(item)
|
|
340
|
+
batch_data = data_list
|
|
341
|
+
elif hasattr(batch_data, 'list'):
|
|
342
|
+
# Single NIfTI_MRS_Plus object
|
|
343
|
+
batch_data = batch_data.list()
|
|
344
|
+
|
|
345
|
+
if batch_water is not None:
|
|
346
|
+
if isinstance(batch_water, list):
|
|
347
|
+
water_list = []
|
|
348
|
+
for item in batch_water:
|
|
349
|
+
if hasattr(item, 'list'):
|
|
350
|
+
water_list.extend(item.list())
|
|
351
|
+
else:
|
|
352
|
+
water_list.append(item)
|
|
353
|
+
batch_water = water_list
|
|
354
|
+
elif hasattr(batch_water, 'list'):
|
|
355
|
+
batch_water = batch_water.list()
|
|
356
|
+
|
|
357
|
+
yield batch_data, batch_water
|
|
358
|
+
return unwrapped_gen()
|
|
359
|
+
|
|
360
|
+
# For numpy/jax: return generator directly with backend conversion
|
|
361
|
+
if framework in ['numpy', 'jax']:
|
|
362
|
+
def converted_gen():
|
|
363
|
+
for batch_data, batch_water in generator: # generator is already iterator
|
|
364
|
+
yield convert_batch_to_backend(batch_data, batch_water, backend)
|
|
365
|
+
return converted_gen()
|
|
366
|
+
|
|
367
|
+
# For PyTorch: wrap in DataLoader-like iterator
|
|
368
|
+
elif framework == 'pytorch':
|
|
369
|
+
def converted_gen():
|
|
370
|
+
for batch_data, batch_water in generator: # generator is already iterator
|
|
371
|
+
yield convert_batch_to_backend(batch_data, batch_water, Backend.PYTORCH)
|
|
372
|
+
return converted_gen()
|
|
373
|
+
|
|
374
|
+
# For TensorFlow/Keras: wrap in tf.data.Dataset
|
|
375
|
+
elif framework in ['tensorflow', 'keras']:
|
|
376
|
+
try:
|
|
377
|
+
import tensorflow as tf
|
|
378
|
+
|
|
379
|
+
def tf_gen():
|
|
380
|
+
for batch_data, batch_water in generator: # generator is already iterator
|
|
381
|
+
data_tf, water_tf = convert_batch_to_backend(
|
|
382
|
+
batch_data, batch_water, Backend.TENSORFLOW
|
|
383
|
+
)
|
|
384
|
+
yield data_tf, water_tf
|
|
385
|
+
|
|
386
|
+
# Infer output signature from first batch
|
|
387
|
+
# We need to peek at the generator - convert to list first
|
|
388
|
+
batch_list = list(generator)
|
|
389
|
+
if not batch_list:
|
|
390
|
+
raise ValueError("Empty generator")
|
|
391
|
+
|
|
392
|
+
first_data, first_water = batch_list[0]
|
|
393
|
+
data_shape = (None,) + first_data[0][:].shape if hasattr(first_data[0], 'shape') else (None, None)
|
|
394
|
+
|
|
395
|
+
output_signature = (
|
|
396
|
+
tf.TensorSpec(shape=data_shape, dtype=tf.complex64),
|
|
397
|
+
tf.TensorSpec(shape=data_shape, dtype=tf.complex64) if first_water else None
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
# Create generator from list
|
|
401
|
+
def list_gen():
|
|
402
|
+
for batch_data, batch_water in batch_list:
|
|
403
|
+
data_tf, water_tf = convert_batch_to_backend(
|
|
404
|
+
batch_data, batch_water, Backend.TENSORFLOW
|
|
405
|
+
)
|
|
406
|
+
yield data_tf, water_tf
|
|
407
|
+
|
|
408
|
+
return tf.data.Dataset.from_generator(
|
|
409
|
+
list_gen,
|
|
410
|
+
output_signature=output_signature
|
|
411
|
+
)
|
|
412
|
+
except ImportError:
|
|
413
|
+
print("Warning: TensorFlow not installed, returning numpy generator")
|
|
414
|
+
return wrap_generator_for_framework(generator, backend, 'numpy')
|
|
415
|
+
|
|
416
|
+
else:
|
|
417
|
+
raise ValueError(f"Unknown framework: {framework}")
|