Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Store original image downsample in LabeledImageServer #27

Merged
merged 8 commits into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions qubalab/images/image_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,8 @@ def _resize(image: Union[np.ndarray, Image.Image], target_size: tuple[int, int],
pilImage = Image.fromarray(image)
elif np.issubdtype(image.dtype, np.integer):
pilImage = Image.fromarray(image.astype(np.int32), mode='I')
elif np.issubdtype(image.dtype, np.bool_):
pilImage = Image.fromarray(image, "1")
else:
pilImage = Image.fromarray(image.astype(np.float32), mode='F')
pilImage = ImageServer._resize(pilImage, target_size=target_size, resample=resample)
Expand Down
16 changes: 9 additions & 7 deletions qubalab/images/labeled_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ def __init__(
label_map: dict[Classification, int] = None,
downsample: float = None,
multichannel: bool = False,
resize_method = PIL.Image.Resampling.NEAREST,
**kwargs
):
"""
Expand All @@ -42,10 +43,10 @@ def __init__(
integers representing a label (see the label_map parameter). If True, the number of channels will be
equal to the highest label value + 1, and the pixel located at (c, y, x) is a boolean indicating if an annotation
with label c is present on the pixel located at (x, y)
:param resize_method: the resampling method to use when resizing the image for downsampling. Bicubic by default
:param resize_method: the resampling method to use when resizing the image for downsampling. Nearest neighbour by default for labeled images.
:raises ValueError: when a label in label_map is less than or equal to 0
"""
super().__init__(**kwargs)
super().__init__(resize_method=resize_method, **kwargs)

if label_map is not None and any(label <= 0 for label in label_map.values()):
raise ValueError('A label in label_map is less than or equal to 0: ' + str(label_map))
Expand All @@ -70,25 +71,26 @@ def _build_metadata(self) -> ImageMetadata:
self._base_image_metadata.path,
f'{self._base_image_metadata.name} - labels',
(ImageShape(
int(self._base_image_metadata.width / self._downsample),
int(self._base_image_metadata.height / self._downsample),
int(self._base_image_metadata.width),
int(self._base_image_metadata.height),
1,
max(self._feature_index_to_label.values(), default=0)+1 if self._multichannel else 1,
1,
),),
PixelCalibration(
PixelLength(
self._base_image_metadata.pixel_calibration.length_x.length * self._downsample,
self._base_image_metadata.pixel_calibration.length_x.length,
self._base_image_metadata.pixel_calibration.length_x.unit
),
PixelLength(
self._base_image_metadata.pixel_calibration.length_y.length * self._downsample,
self._base_image_metadata.pixel_calibration.length_y.length,
self._base_image_metadata.pixel_calibration.length_y.unit
),
self._base_image_metadata.pixel_calibration.length_z
),
False,
bool if self._multichannel else np.uint32
bool if self._multichannel else np.uint32,
downsamples = [self._downsample]
)

def _read_block(self, level: int, region: Region2D) -> np.ndarray:
Expand Down
113 changes: 101 additions & 12 deletions tests/images/test_labeled_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@

def test_image_width_with_downsample():
downsample = 1.5
expected_width = int(sample_metadata.shape.x / downsample)
expected_width = sample_metadata.shape.x
labeled_server = LabeledImageServer(sample_metadata, [], downsample=downsample)

width = labeled_server.metadata.width
Expand All @@ -49,7 +49,7 @@ def test_image_width_with_downsample():

def test_image_height_with_downsample():
downsample = 1.5
expected_height = int(sample_metadata.shape.y / downsample)
expected_height = sample_metadata.shape.y
labeled_server = LabeledImageServer(sample_metadata, [], downsample=downsample)

height = labeled_server.metadata.height
Expand Down Expand Up @@ -174,7 +174,7 @@ def test_image_n_resolutions():

def test_x_pixel_length_with_downsample():
downsample = 1.5
expected_length_x = sample_metadata.pixel_calibration.length_x.length * downsample
expected_length_x = sample_metadata.pixel_calibration.length_x.length
labeled_server = LabeledImageServer(sample_metadata, [], downsample=downsample)

length_x = labeled_server.metadata.pixel_calibration.length_x.length
Expand Down Expand Up @@ -327,7 +327,7 @@ def test_read_points_in_single_channel_image_without_label_map_with_downsample()
)
labeled_server = LabeledImageServer(sample_metadata, features, multichannel=False, downsample=downsample)

image = labeled_server.read_region(1, Region2D(0, 0, labeled_server.metadata.width, labeled_server.metadata.height))
image = labeled_server.read_region(downsample, Region2D(0, 0, labeled_server.metadata.width, labeled_server.metadata.height))

np.testing.assert_array_equal(image, expected_image)

Expand Down Expand Up @@ -359,7 +359,7 @@ def test_read_line_in_single_channel_image_without_label_map_with_downsample():
)
labeled_server = LabeledImageServer(sample_metadata, features, multichannel=False, downsample=downsample)

image = labeled_server.read_region(1, Region2D(0, 0, labeled_server.metadata.width, labeled_server.metadata.height))
image = labeled_server.read_region(downsample, Region2D(0, 0, labeled_server.metadata.width, labeled_server.metadata.height))

np.testing.assert_array_equal(image, expected_image)

Expand Down Expand Up @@ -391,7 +391,7 @@ def test_read_polygon_in_single_channel_image_without_label_map_with_downsample(
)
labeled_server = LabeledImageServer(sample_metadata, features, multichannel=False, downsample=downsample)

image = labeled_server.read_region(1, Region2D(0, 0, labeled_server.metadata.width, labeled_server.metadata.height))
image = labeled_server.read_region(downsample, Region2D(0, 0, labeled_server.metadata.width, labeled_server.metadata.height))

np.testing.assert_array_equal(image, expected_image)

Expand All @@ -409,10 +409,7 @@ def rands():
(x, y + 1)
)

coords = [rands() for i in range(max_objects)]

n_objects = len(coords)
features = [ImageFeature(geojson.Polygon([coords[i]]), Classification("Some classification")) for i in range(n_objects)]
features = [ImageFeature(geojson.Polygon([rands()])) for i in range(max_objects)]
labeled_server = LabeledImageServer(large_metadata, features, multichannel=False, downsample=downsample)

image = labeled_server.read_region(1, Region2D(0, 0, labeled_server.metadata.width, labeled_server.metadata.height))
Expand All @@ -434,8 +431,41 @@ def test_single_channel_labeled_image_with_region_request():

np.testing.assert_array_equal(image, expected_image)



def test_single_channel_labeled_image_with_starting_downsample():
features = [ImageFeature(geojson.LineString([(6, 5), (9, 5)]))]
# when resizing, we lose the labels with bicubic
expected_image = np.array(
[[[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 1, 1]]]
)
labeled_server = LabeledImageServer(sample_metadata, features, multichannel=False, downsample=1)
downsample = 2
region = Region2D(0, 0, labeled_server.metadata.width, labeled_server.metadata.height)
image = labeled_server.read_region(downsample, region)

np.testing.assert_array_equal(image, expected_image)


def test_single_channel_labeled_image_with_request_downsample():
# we downsample
features = [ImageFeature(geojson.LineString([(6, 5), (9, 5)]))]
expected_image = np.array(
[[[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 1, 1]]]
)
labeled_server = LabeledImageServer(sample_metadata, features, multichannel=False, downsample=1)
region = Region2D(0, 0, labeled_server.metadata.width, labeled_server.metadata.height)
image = labeled_server.read_region(2, region)

np.testing.assert_array_equal(image, expected_image)



def test_multi_channel_labeled_image_with_region_request():
downsample = 1
features = [ImageFeature(geojson.LineString([(7, 5), (9, 5)]))]
expected_image = np.array(
[[[False, False, False, False, False],
Expand All @@ -445,7 +475,66 @@ def test_multi_channel_labeled_image_with_region_request():
[False, False, False, False, False],
[False, False, True, True, True]]]
)
labeled_server = LabeledImageServer(sample_metadata, features, multichannel=True, downsample=downsample)
labeled_server = LabeledImageServer(sample_metadata, features, multichannel=True, downsample=1)
region = Region2D(5, 3, labeled_server.metadata.width-5, labeled_server.metadata.height-3)
image = labeled_server.read_region(1, region)

np.testing.assert_array_equal(image, expected_image)




def test_multi_channel_labeled_image_with_starting_downsample():
# we downsample the feature, then request at the same downsample
features = [ImageFeature(geojson.LineString([(6, 5), (9, 5)]))]
expected_image = np.array(
[[[False, False, False, False, False],
[False, False, False, False, False],
[False, False, False, False, False]],
[[False, False, False, False, False],
[False, False, False, False, False],
[False, False, False, True, True]]]
)
downsample = 2
labeled_server = LabeledImageServer(sample_metadata, features, multichannel=True, downsample=downsample)
region = Region2D(0, 0, sample_metadata.width, sample_metadata.height)
image = labeled_server.read_region(2, region)

np.testing.assert_array_equal(image, expected_image)

def test_multi_channel_labeled_image_with_request_downsample():
features = [ImageFeature(geojson.LineString([(6, 5), (9, 5)]))]
## because we resize the image after reading, we lose the small region
expected_image = np.array(
[[[False, False, False, False, False],
[False, False, False, False, False],
[False, False, False, False, False]],
[[False, False, False, False, False],
[False, False, False, False, False],
[False, False, False, False, False]]]
)
labeled_server = LabeledImageServer(sample_metadata, features, multichannel=True, downsample=1)
downsample = 2
region = Region2D(0, 0, labeled_server.metadata.width, labeled_server.metadata.height)
image = labeled_server.read_region(downsample, region)

np.testing.assert_array_equal(image, expected_image)


def test_multi_channel_labeled_image_with_starting_downsample_upsampled():
# we downsample the feature, then request at a downsample of 1, so upsampled!
# therefore the feature gets much bigger
features = [ImageFeature(geojson.LineString([(5, 5), (9, 5)]))]
expected_image = np.array(
[[[False, False, False, False, False],
[False, False, False, False, False],
[False, False, False, False, False]],

[[False, False, False, False, False],
[False, False, False, False, False],
[False, False, True, True, True]]]
)
labeled_server = LabeledImageServer(sample_metadata, features, multichannel=True, downsample=2)
image = labeled_server.read_region(2)

np.testing.assert_array_equal(image, expected_image)