Skip to content

Commit f916627

Browse files
committed
REF: Generalize bvals and uptake value iterators into a single iterator
Generalize bvals and uptake value iterators into a single iterator that is able to traverse the values in ascending or descending order depending on whether it is provided a list of b-values (DWI) or uptake values (PET). Follow-up to commit a1310e6.
1 parent d27ba75 commit f916627

File tree

2 files changed

+63
-83
lines changed

2 files changed

+63
-83
lines changed

src/nifreeze/utils/iterators.py

Lines changed: 38 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -206,64 +206,54 @@ def _value_iterator(values: list, ascending: bool, round_decimals: int = 2) -> I
206206
return (index[1] for index in indexed_vals)
207207

208208

209-
def bvalue_iterator(*_, **kwargs) -> Iterator[int]:
210-
"""
211-
Traverse the volumes in a DWI dataset by increasing b-value.
209+
def monotonic_value_iterator(*_, **kwargs) -> Iterator[int]:
210+
candidates = [{key: kwargs[key]} for key in (BVALS_KWARG, UPTAKE_KWARG) if key in kwargs]
211+
if not candidates:
212+
raise TypeError(KWARG_ERROR_MSG.format(kwarg=f"{BVALS_KWARG} or {UPTAKE_KWARG}"))
212213

213-
Parameters
214-
----------
215-
bvals : :obj:`list`
216-
List of b-values corresponding to all orientations of the dataset.
217-
Please note that ``bvals`` is a keyword argument and MUST be provided
218-
to generate the volume sequence.
219-
220-
Yields
221-
------
222-
:obj:`int`
223-
The next index.
214+
feature = next(iter(candidates[0]))
215+
ascending = feature == BVALS_KWARG
216+
values = kwargs.pop(feature)
224217

225-
Examples
226-
--------
227-
>>> list(bvalue_iterator(bvals=[0.0, 0.0, 1000.0, 1000.0, 700.0, 700.0, 2000.0, 2000.0, 0.0]))
228-
[0, 1, 8, 4, 5, 2, 3, 6, 7]
218+
return _value_iterator(values, ascending=ascending, **kwargs)
229219

230-
"""
231-
bvals = kwargs.pop(BVALS_KWARG, None)
232-
if bvals is None:
233-
raise TypeError(KWARG_ERROR_MSG.format(kwarg=BVALS_KWARG))
234-
return _value_iterator(bvals, ascending=True, **kwargs)
235220

221+
monotonic_value_iterator.__doc__ = f"""
222+
Traverse the volumes by increasing b-value in a DWI dataset or by decreasing
223+
uptake value in a PET dataset.
236224
237-
def uptake_iterator(*_, **kwargs) -> Iterator[int]:
238-
"""
239-
Traverse the volumes in a PET dataset by decreasing uptake value.
225+
This function requires ``bvals`` or ``uptake`` be a keyword argument to generate
226+
the volume sequence. The b-values are assumed to all orientations in a DWI
227+
dataset, and uptake uptake values correspond to all volumes in a PET dataset.
240228
241-
This function assumes that each uptake value corresponds to a single volume,
242-
and that this value summarizes the uptake of the volume in a meaningful way,
243-
e.g. a mean value across the entire volume.
229+
It is assumed that each uptake value corresponds to a single volume, and that
230+
this value summarizes the uptake of the volume in a meaningful way, e.g. a mean
231+
value across the entire volume.
244232
245-
Parameters
246-
----------
247-
uptake : :obj:`list`
248-
List of uptake values corresponding to all volumes of the dataset.
249-
Please note that ``uptake`` is a keyword argument and MUST be provided
250-
to generate the volume sequence.
233+
Other Parameters
234+
----------------
235+
{SIZE_KEYS_DOC}
251236
252-
Yields
253-
------
254-
:obj:`int`
255-
The next index.
237+
Notes
238+
-----
239+
Only one of the above keyword arguments may be provided at a time. If ``size``
240+
is given, all other size-related keyword arguments will be ignored. If ``size``
241+
is not provided, the function will attempt to infer the number of volumes from
242+
the length or value of the provided keyword argument. If more than one such
243+
keyword is provided, a :exc:`ValueError` will be raised.
256244
257-
Examples
258-
--------
259-
>>> list(uptake_iterator(uptake=[-1.23, 1.06, 1.02, 1.38, -1.46, -1.12, -1.19, 1.24, 1.05]))
260-
[3, 7, 1, 8, 2, 5, 6, 0, 4]
245+
Yields
246+
------
247+
:obj:`int`
248+
The next index.
261249
262-
"""
263-
uptake = kwargs.pop(UPTAKE_KWARG, None)
264-
if uptake is None:
265-
raise TypeError(KWARG_ERROR_MSG.format(kwarg=UPTAKE_KWARG))
266-
return _value_iterator(uptake, ascending=False, **kwargs)
250+
Examples
251+
--------
252+
>>> list(monotonic_value_iterator(bvals=[0.0, 0.0, 1000.0, 1000.0, 700.0, 700.0, 2000.0, 2000.0, 0.0]))
253+
[0, 1, 8, 4, 5, 2, 3, 6, 7]
254+
>>> list(monotonic_value_iterator(uptake=[-1.23, 1.06, 1.02, 1.38, -1.46, -1.12, -1.19, 1.24, 1.05]))
255+
[3, 7, 1, 8, 2, 5, 6, 0, 4]
256+
"""
267257

268258

269259
def centralsym_iterator(**kwargs) -> Iterator[int]:

test/test_iterators.py

Lines changed: 25 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,10 @@
3131
KWARG_ERROR_MSG,
3232
UPTAKE_KWARG,
3333
_value_iterator,
34-
bvalue_iterator,
3534
centralsym_iterator,
3635
linear_iterator,
36+
monotonic_value_iterator,
3737
random_iterator,
38-
uptake_iterator,
3938
)
4039

4140

@@ -144,43 +143,34 @@ def test_centralsym_iterator(kwargs, expected):
144143
assert list(centralsym_iterator(**kwargs)) == expected
145144

146145

147-
def test_bvalue_iterator_error():
148-
with pytest.raises(TypeError, match=KWARG_ERROR_MSG.format(kwarg=BVALS_KWARG)):
149-
list(bvalue_iterator())
146+
def test_monotonic_value_iterator_error():
147+
with pytest.raises(
148+
TypeError, match=KWARG_ERROR_MSG.format(kwarg=f"{BVALS_KWARG} or {UPTAKE_KWARG}")
149+
):
150+
list(monotonic_value_iterator())
150151

151-
152-
@pytest.mark.parametrize(
153-
"bvals, expected",
154-
[
155-
([0, 700, 1200], [0, 1, 2]),
156-
([0, 0, 1000, 700], [0, 1, 3, 2]),
157-
([0, 1000, 1500, 700, 2000], [0, 3, 1, 2, 4]),
158-
],
159-
)
160-
def test_bvalue_iterator(bvals, expected):
161-
obtained = list(bvalue_iterator(bvals=bvals))
162-
assert set(obtained) == set(range(len(bvals)))
163-
# Should be ordered by increasing bvalue
164-
sorted_bvals = [bvals[i] for i in obtained]
165-
assert sorted_bvals == sorted(bvals)
166-
167-
168-
def test_uptake_iterator_error():
169-
with pytest.raises(TypeError, match=KWARG_ERROR_MSG.format(kwarg=UPTAKE_KWARG)):
170-
list(uptake_iterator())
152+
with pytest.raises(
153+
TypeError, match=KWARG_ERROR_MSG.format(kwarg=f"{BVALS_KWARG} or {UPTAKE_KWARG}")
154+
):
155+
list(monotonic_value_iterator())
171156

172157

173158
@pytest.mark.parametrize(
174-
"uptake, expected",
159+
"feature, values, expected",
175160
[
176-
([0.3, 0.2, 0.1], [0, 1, 2]),
177-
([0.2, 0.1, 0.3], [2, 1, 0]),
178-
([-1.02, 1.16, -0.56, 0.43], [1, 3, 2, 0]),
161+
("bvals", [0, 700, 1200], [0, 1, 2]),
162+
("bvals", [0, 0, 1000, 700], [0, 1, 3, 2]),
163+
("bvals", [0, 1000, 1500, 700, 2000], [0, 3, 1, 2, 4]),
164+
("uptake", [0.3, 0.2, 0.1], [0, 1, 2]),
165+
("uptake", [0.2, 0.1, 0.3], [2, 1, 0]),
166+
("uptake", [-1.02, 1.16, -0.56, 0.43], [1, 3, 2, 0]),
179167
],
180168
)
181-
def test_uptake_iterator_valid(uptake, expected):
182-
obtained = list(uptake_iterator(uptake=uptake))
183-
assert set(obtained) == set(range(len(uptake)))
184-
# Should be ordered by decreasing uptake
185-
sorted_uptake = [uptake[i] for i in obtained]
186-
assert sorted_uptake == sorted(uptake, reverse=True)
169+
def test_monotonic_value_iterator(feature, values, expected):
170+
obtained = list(monotonic_value_iterator(**{feature: values}))
171+
assert set(obtained) == set(range(len(values)))
172+
# If b-values, should be ordered by increasing value; if uptake values,
173+
# should be ordered by decreasing uptake
174+
sorted_vals = [values[i] for i in obtained]
175+
reverse = True if feature == "uptake" else False
176+
assert sorted_vals == sorted(values, reverse=reverse)

0 commit comments

Comments
 (0)