Skip to content

Commit 7a46035

Browse files
authored
Merge branch 'main' into issue799
2 parents df97751 + 3428632 commit 7a46035

File tree

9 files changed

+101
-2628
lines changed

9 files changed

+101
-2628
lines changed

.github/workflows/ci.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ jobs:
2727
strategy:
2828
matrix:
2929
python-version:
30-
- "3.8"
3130
- "3.9"
3231
- "3.10"
3332
- "3.11"

CHANGELOG.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,18 @@ Write the date in place of the "Unreleased" in the case a new version is release
55

66
## Unreleased
77

8+
### Added
9+
810
- Add adapters for reading back assets with the image/jpeg and
911
multipart/related;type=image/jpeg mimetypes.
10-
- Add a check for the `openpyxcl` module when importing excel serializer.
12+
- Automatic reshaping of tiff data by the adapter to account for
13+
extra/missing singleton dimension
14+
- Add a check for the `openpyxcl` module when importing excel serializer.
15+
16+
### Changed
17+
18+
- Drop support for Python 3.8, which is reached end of life
19+
upstream on 7 October 2024.
1120

1221
## v0.1.0b10 (2024-10-11)
1322

pyproject.toml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ authors = [
1212
maintainers = [
1313
{ name = "Brookhaven National Laboratory", email = "[email protected]" },
1414
]
15-
requires-python = ">=3.8"
15+
requires-python = ">=3.9"
1616

1717
# All dependencies are optional; it depends on whether you are running
1818
# a client or server (or both) and what data structures you care about.
@@ -23,8 +23,6 @@ classifiers = [
2323
"Development Status :: 4 - Beta",
2424
"License :: OSI Approved :: BSD License",
2525
"Programming Language :: Python :: 3 :: Only",
26-
"Programming Language :: Python :: 3.7",
27-
"Programming Language :: Python :: 3.8",
2826
"Programming Language :: Python :: 3.9",
2927
"Programming Language :: Python :: 3.10",
3028
"Programming Language :: Python :: 3.11",

tiled/_tests/test_tiff.py

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@
1010
from ..client import Context, from_context
1111
from ..client.register import IMG_SEQUENCE_EMPTY_NAME_ROOT, register
1212
from ..server.app import build_app
13+
from ..structures.array import ArrayStructure, BuiltinDtype
1314
from ..utils import ensure_uri
1415

1516
COLOR_SHAPE = (11, 17, 3)
17+
rng = numpy.random.default_rng(12345)
1618

1719

1820
@pytest.fixture(scope="module")
@@ -21,20 +23,27 @@ def client(tmpdir_module):
2123
sequence_directory.mkdir()
2224
filepaths = []
2325
for i in range(3):
24-
data = numpy.random.random((5, 7, 4))
26+
data = rng.integers(0, 255, size=(5, 7, 4), dtype="uint8")
2527
filepath = sequence_directory / f"temp{i:05}.tif"
2628
tf.imwrite(filepath, data)
2729
filepaths.append(filepath)
28-
color_data = numpy.random.randint(0, 255, COLOR_SHAPE, dtype="uint8")
30+
color_data = rng.integers(0, 255, size=COLOR_SHAPE, dtype="uint8")
2931
path = Path(tmpdir_module, "color.tif")
3032
tf.imwrite(path, color_data)
31-
3233
tree = MapAdapter(
3334
{
3435
"color": TiffAdapter(ensure_uri(path)),
3536
"sequence": TiffSequenceAdapter.from_uris(
3637
[ensure_uri(filepath) for filepath in filepaths]
3738
),
39+
"5d_sequence": TiffSequenceAdapter.from_uris(
40+
[ensure_uri(filepath) for filepath in filepaths],
41+
structure=ArrayStructure(
42+
shape=(3, 1, 5, 7, 4),
43+
chunks=((1, 1, 1), (1,), (5,), (7,), (4,)),
44+
data_type=BuiltinDtype.from_numpy_dtype(numpy.dtype("uint8")),
45+
),
46+
),
3847
}
3948
)
4049
app = build_app(tree)
@@ -62,6 +71,27 @@ def test_tiff_sequence(client, slice_input, correct_shape):
6271
assert arr.shape == correct_shape
6372

6473

74+
@pytest.mark.parametrize(
75+
"slice_input, correct_shape",
76+
[
77+
(None, (3, 1, 5, 7, 4)),
78+
(..., (3, 1, 5, 7, 4)),
79+
((), (3, 1, 5, 7, 4)),
80+
(0, (1, 5, 7, 4)),
81+
(slice(0, 3, 2), (2, 1, 5, 7, 4)),
82+
((1, slice(0, 10), slice(0, 3), slice(0, 3)), (1, 3, 3, 4)),
83+
((slice(0, 3), 0, slice(0, 3), slice(0, 3)), (3, 3, 3, 4)),
84+
((..., 0, 0, 0, 0), (3,)),
85+
((0, slice(0, 1), slice(0, 1), slice(0, 2), ...), (1, 1, 2, 4)),
86+
((0, ..., slice(0, 2)), (1, 5, 7, 2)),
87+
((..., slice(0, 1)), (3, 1, 5, 7, 1)),
88+
],
89+
)
90+
def test_forced_reshaping(client, slice_input, correct_shape):
91+
arr = client["5d_sequence"].read(slice=slice_input)
92+
assert arr.shape == correct_shape
93+
94+
6595
@pytest.mark.parametrize("block_input, correct_shape", [((0, 0, 0, 0), (1, 5, 7, 4))])
6696
def test_tiff_sequence_block(client, block_input, correct_shape):
6797
arr = client["sequence"].read_block(block_input)

tiled/adapters/sequence.py

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import builtins
2+
import math
3+
import warnings
24
from abc import abstractmethod
35
from pathlib import Path
46
from typing import Any, List, Optional, Tuple, Union
57

68
import numpy as np
9+
from ndindex import ndindex
710
from numpy._typing import NDArray
811

912
from ..structures.array import ArrayStructure, BuiltinDtype
@@ -13,6 +16,47 @@
1316
from .type_alliases import JSON, NDSlice
1417

1518

19+
def force_reshape(arr: np.array, desired_shape: Tuple[int, ...]) -> np.array:
20+
"""Reshape a numpy array to match the desited shape, if possible.
21+
22+
Parameters
23+
----------
24+
25+
arr : np.array
26+
The original ND array to be reshaped
27+
desired_shape : Tuple[int, ...]
28+
The desired shape of the resulting array
29+
30+
Returns
31+
-------
32+
33+
A view of the original array
34+
"""
35+
36+
if arr.shape == desired_shape:
37+
# Nothing to do here
38+
return arr
39+
40+
if arr.size == math.prod(desired_shape):
41+
if len(arr.shape) != len(desired_shape):
42+
# Missing or extra singleton dimensions
43+
warnings.warn(
44+
f"Forcefully reshaping {arr.shape} to {desired_shape}",
45+
category=RuntimeWarning,
46+
)
47+
return arr.reshape(desired_shape)
48+
else:
49+
# Some dimensions might be swapped or completely wrong
50+
# TODO: needs to be treated more carefully
51+
pass
52+
53+
warnings.warn(
54+
f"Can not reshape array of {arr.shape} to match {desired_shape}; proceeding without changes",
55+
category=RuntimeWarning,
56+
)
57+
return arr
58+
59+
1660
class FileSequenceAdapter:
1761
"""Base adapter class for image (and other file) sequences
1862
@@ -124,12 +168,12 @@ def metadata(self) -> JSON:
124168
def read(self, slice: Optional[NDSlice] = ...) -> NDArray[Any]:
125169
"""Return a numpy array
126170
127-
Receives a sequence of values to select from a collection of image files
128-
that were saved in a folder The input order is defined as: files -->
171+
Receives a sequence of values to select from a collection of data files
172+
that were saved in a folder. The input order is defined as: files -->
129173
vertical slice --> horizontal slice --> color slice --> ... read() can
130174
receive one value or one slice to select all the data from one file or
131175
a sequence of files; or it can receive a tuple (int or slice) to select
132-
a more specific sequence of pixels of a group of images.
176+
a more specific sequence of pixels of a group of images, for example.
133177
134178
Parameters
135179
----------
@@ -165,11 +209,15 @@ def read(self, slice: Optional[NDSlice] = ...) -> NDArray[Any]:
165209
the_rest.insert(0, Ellipsis) # Include any leading dimensions
166210
elif isinstance(left_axis, builtins.slice):
167211
arr = self.read(slice=left_axis)
212+
213+
sliced_shape = ndindex(left_axis).newshape(self.structure().shape)
214+
arr = force_reshape(arr, sliced_shape)
168215
arr = np.atleast_1d(arr[tuple(the_rest)])
169216
else:
170217
raise RuntimeError(f"Unsupported slice type, {type(slice)} in {slice}")
171218

172-
return arr
219+
sliced_shape = ndindex(slice).newshape(self.structure().shape)
220+
return force_reshape(arr, sliced_shape)
173221

174222
def read_block(
175223
self, block: Tuple[int, ...], slice: Optional[NDSlice] = ...

tiled/client/auth.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,7 @@ def sync_clear_token(self, key):
7979
with self._sync_lock:
8080
self.tokens.pop(key, None)
8181
filepath = self.token_directory / key
82-
# filepath.unlink(missing_ok=False) # Python 3.8+
83-
try:
84-
filepath.unlink()
85-
except FileNotFoundError:
86-
pass
82+
filepath.unlink(missing_ok=False)
8783
self.tokens.pop(key, None) # Clear cached value.
8884

8985
def sync_auth_flow(self, request, attempt=0):

0 commit comments

Comments
 (0)