Skip to content

Commit 1c58cb3

Browse files
authored
TiffIO: Allow bottom left origin for images (#1428)
* give option to switch origin of images * add test for shifting origin * switch to string for origin * fix test * typo fix * fix axis error * fix the axis * fix test
1 parent db9eca8 commit 1c58cb3

File tree

2 files changed

+90
-35
lines changed

2 files changed

+90
-35
lines changed

neo/io/tiffio.py

Lines changed: 70 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -15,35 +15,53 @@ class TiffIO(BaseIO):
1515
"""
1616
Neo IO module for optical imaging data stored as a folder of TIFF images.
1717
18-
*Usage*:
19-
>>> from neo import io
20-
>>> import quantities as pq
21-
>>> r = io.TiffIO("dir_tiff",spatial_scale=1.0*pq.mm, units='V',
22-
... sampling_rate=1.0*pq.Hz)
23-
>>> block = r.read_block()
24-
read block
25-
creating segment
26-
returning block
27-
>>> block
28-
Block with 1 segments
29-
file_origin: 'test'
30-
# segments (N=1)
31-
0: Segment with 1 imagesequences
32-
annotations: {'tiff_file_names': ['file_tif_1_.tiff',
33-
'file_tif_2.tiff',
34-
'file_tif_3.tiff',
35-
'file_tif_4.tiff',
36-
'file_tif_5.tiff',
37-
'file_tif_6.tiff',
38-
'file_tif_7.tiff',
39-
'file_tif_8.tiff',
40-
'file_tif_9.tiff',
41-
'file_tif_10.tiff',
42-
'file_tif_11.tiff',
43-
'file_tif_12.tiff',
44-
'file_tif_13.tiff',
45-
'file_tif_14.tiff']}
46-
# analogsignals (N=0)
18+
Parameters
19+
----------
20+
directory_path: Path | str | None, default: None
21+
The path to the folder containing tiff images
22+
units: Quantity units | None, default: None
23+
the units for creating the ImageSequence
24+
sampling_rate: Quantity Units | None, default: None
25+
The sampling rate
26+
spatial_scale: Quantity unit | None, default: None
27+
The scale of the images
28+
origin: Literal['top-left'| 'bottom-left'], default: 'top-left'
29+
Whether to use the python default origin for images which is upper left corner ('top-left')
30+
as orgin or to use a bottom left corner as orgin ('bottom-left')
31+
Note that plotting functions like matplotlib.pyplot.imshow expect upper left corner.
32+
**kwargs: dict
33+
The standard neo annotation kwargs
34+
35+
Examples
36+
--------
37+
>>> from neo import io
38+
>>> import quantities as pq
39+
>>> r = io.TiffIO("dir_tiff",spatial_scale=1.0*pq.mm, units='V',
40+
... sampling_rate=1.0*pq.Hz)
41+
>>> block = r.read_block()
42+
read block
43+
creating segment
44+
returning block
45+
>>> block
46+
Block with 1 segments
47+
file_origin: 'test'
48+
# segments (N=1)
49+
0: Segment with 1 imagesequences
50+
annotations: {'tiff_file_names': ['file_tif_1_.tiff',
51+
'file_tif_2.tiff',
52+
'file_tif_3.tiff',
53+
'file_tif_4.tiff',
54+
'file_tif_5.tiff',
55+
'file_tif_6.tiff',
56+
'file_tif_7.tiff',
57+
'file_tif_8.tiff',
58+
'file_tif_9.tiff',
59+
'file_tif_10.tiff',
60+
'file_tif_11.tiff',
61+
'file_tif_12.tiff',
62+
'file_tif_13.tiff',
63+
'file_tif_14.tiff']}
64+
# analogsignals (N=0)
4765
"""
4866

4967
name = "TIFF IO"
@@ -66,13 +84,30 @@ class TiffIO(BaseIO):
6684

6785
mode = "dir"
6886

69-
def __init__(self, directory_path=None, units=None, sampling_rate=None, spatial_scale=None, **kwargs):
70-
import PIL
87+
def __init__(
88+
self,
89+
directory_path=None,
90+
units=None,
91+
sampling_rate=None,
92+
spatial_scale=None,
93+
origin='top-left',
94+
**kwargs,
95+
):
96+
# this block is because people might be confused about the PIL -> pillow change
97+
# between python2 -> python3 (both with namespace PIL)
98+
try:
99+
import PIL
100+
except ImportError:
101+
raise ImportError("To use TiffIO you must first `pip install pillow`")
102+
103+
if origin != 'top-left' and origin != 'bottom-left':
104+
raise ValueError("`origin` must be either `top-left` or `bottom-left`")
71105

72106
BaseIO.__init__(self, directory_path, **kwargs)
73107
self.units = units
74108
self.sampling_rate = sampling_rate
75109
self.spatial_scale = spatial_scale
110+
self.origin = origin
76111

77112
def read_block(self, lazy=False, **kwargs):
78113
import PIL
@@ -98,13 +133,17 @@ def natural_sort(l):
98133
list_data_image = []
99134
for file_name in file_name_list:
100135
data = np.array(PIL.Image.open(self.filename + "/" + file_name)).astype(np.float32)
136+
if self.origin == "bottom-left":
137+
data = np.flip(data, axis=-2)
101138
list_data_image.append(data)
102139
list_data_image = np.array(list_data_image)
103140
if len(list_data_image.shape) == 4:
104141
list_data_image = []
105142
for file_name in file_name_list:
106143
image = PIL.Image.open(self.filename + "/" + file_name).convert("L")
107144
data = np.array(image).astype(np.float32)
145+
if self.origin == "bottom-left":
146+
data = np.flip(data, axis=-2)
108147
list_data_image.append(data)
109148

110149
print("read block")

neo/test/iotest/test_tiffio.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,14 @@ def test_read_group_of_tiff_grayscale(self):
1717
img = []
1818
for picture in range(10):
1919
img.append([])
20-
for y in range(50):
20+
for x in range(50):
2121
img[picture].append([])
22-
for x in range(50):
23-
img[picture][y].append(x)
22+
for y in range(50):
23+
img[picture][x].append(y)
2424
img = np.array(img, dtype=float)
2525
for image in range(10):
26-
Image.fromarray(img[image]).save(directory + "/tiff_exemple" + str(image) + ".tif")
26+
# rotate image by 90 deg so that shifting the origin is meaningful in later test
27+
Image.fromarray(np.rot90(img[image])).save(directory + "/tiff_exemple" + str(image) + ".tif")
2728

2829
ioclass = TiffIO(
2930
directory_path=directory, units="V", sampling_rate=1.0 * pq.Hz, spatial_scale=1.0 * pq.micrometer
@@ -35,6 +36,21 @@ def test_read_group_of_tiff_grayscale(self):
3536
self.assertEqual(blck.segments[0].imagesequences[0].sampling_rate, 1.0 * pq.Hz)
3637
self.assertEqual(blck.segments[0].imagesequences[0].spatial_scale, 1.0 * pq.micrometer)
3738

39+
ioclass_bl_origin = TiffIO(
40+
directory_path=directory,
41+
units="V",
42+
sampling_rate=1.0 * pq.Hz,
43+
spatial_scale=1.0 * pq.micrometer,
44+
origin='bottom-left',
45+
)
46+
blck_bl_origin = ioclass_bl_origin.read_block()
47+
48+
self.assertAlmostEqual(
49+
blck.segments[0].imagesequences[0][0][0,0].magnitude,
50+
blck_bl_origin.segments[0].imagesequences[0][0][49,0].magnitude, # since flipped over y, [0,0] == [49,0]
51+
places=3,
52+
)
53+
3854
# end of directory
3955
shutil.rmtree(directory)
4056

0 commit comments

Comments
 (0)