Skip to content

Commit

Permalink
Merge pull request galaxyproject#17797 from kostrykin/tifffile
Browse files Browse the repository at this point in the history
Add support for floating point TIFF files in verification of image-based tool outputs
  • Loading branch information
mvdbeek authored Mar 20, 2024
2 parents cd16d19 + 45a819e commit 429c63e
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 42 deletions.
27 changes: 23 additions & 4 deletions lib/galaxy/tool_util/verify/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
from PIL import Image
except ImportError:
pass
try:
import tifffile
except ImportError:
pass

from galaxy.tool_util.parser.util import (
DEFAULT_DELTA,
Expand Down Expand Up @@ -496,14 +500,29 @@ def get_image_metric(
raise ValueError(f'No such metric: "{metric_name}"')


def _load_image(filepath: str) -> "numpy.typing.NDArray":
"""
Reads the given image, trying tifffile and Pillow for reading.
"""
# Try reading with tifffile first. It fails if the file is not a TIFF.
try:
arr = tifffile.imread(filepath)

# If tifffile failed, then the file is not a tifffile. In that case, try with Pillow.
except tifffile.TiffFileError:
with Image.open(filepath) as im:
arr = numpy.array(im)

# Return loaded image
return arr


def files_image_diff(file1: str, file2: str, attributes: Optional[Dict[str, Any]] = None) -> None:
"""Check the pixel data of 2 image files for differences."""
attributes = attributes or {}

with Image.open(file1) as im1:
arr1 = numpy.array(im1)
with Image.open(file2) as im2:
arr2 = numpy.array(im2)
arr1 = _load_image(file1)
arr2 = _load_image(file2)

if arr1.dtype != arr2.dtype:
raise AssertionError(f"Image data types did not match ({arr1.dtype}, {arr2.dtype}).")
Expand Down
88 changes: 50 additions & 38 deletions lib/galaxy/tool_util/verify/asserts/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
from PIL import Image
except ImportError:
pass
try:
import tifffile
except ImportError:
pass

if TYPE_CHECKING:
import numpy.typing
Expand Down Expand Up @@ -58,18 +62,17 @@ def assert_has_image_width(
"""
Asserts the specified output is an image and has a width of the specified value.
"""
buf = io.BytesIO(output_bytes)
with Image.open(buf) as im:
_assert_number(
im.size[0],
width,
delta,
min,
max,
negate,
"{expected} width {n}+-{delta}",
"{expected} width to be in [{min}:{max}]",
)
im_arr = _get_image(output_bytes)
_assert_number(
im_arr.shape[1],
width,
delta,
min,
max,
negate,
"{expected} width {n}+-{delta}",
"{expected} width to be in [{min}:{max}]",
)


def assert_has_image_height(
Expand All @@ -83,18 +86,17 @@ def assert_has_image_height(
"""
Asserts the specified output is an image and has a height of the specified value.
"""
buf = io.BytesIO(output_bytes)
with Image.open(buf) as im:
_assert_number(
im.size[1],
height,
delta,
min,
max,
negate,
"{expected} height {n}+-{delta}",
"{expected} height to be in [{min}:{max}]",
)
im_arr = _get_image(output_bytes)
_assert_number(
im_arr.shape[0],
height,
delta,
min,
max,
negate,
"{expected} height {n}+-{delta}",
"{expected} height to be in [{min}:{max}]",
)


def assert_has_image_channels(
Expand All @@ -108,18 +110,18 @@ def assert_has_image_channels(
"""
Asserts the specified output is an image and has the specified number of channels.
"""
buf = io.BytesIO(output_bytes)
with Image.open(buf) as im:
_assert_number(
len(im.getbands()),
channels,
delta,
min,
max,
negate,
"{expected} image channels {n}+-{delta}",
"{expected} image channels to be in [{min}:{max}]",
)
im_arr = _get_image(output_bytes)
n_channels = 1 if im_arr.ndim < 3 else im_arr.shape[2] # we assume here that the image is a 2-D image
_assert_number(
n_channels,
channels,
delta,
min,
max,
negate,
"{expected} image channels {n}+-{delta}",
"{expected} image channels to be in [{min}:{max}]",
)


def _compute_center_of_mass(im_arr: "numpy.typing.NDArray") -> Tuple[float, float]:
Expand All @@ -139,10 +141,20 @@ def _get_image(
) -> "numpy.typing.NDArray":
"""
Returns the output image or a specific channel.
The function tries to read the image using tifffile and Pillow.
"""
buf = io.BytesIO(output_bytes)
with Image.open(buf) as im:
im_arr = numpy.array(im)

# Try reading with tifffile first. It fails if the file is not a TIFF.
try:
im_arr = tifffile.imread(buf)

# If tifffile failed, then the file is not a tifffile. In that case, try with Pillow.
except tifffile.TiffFileError:
buf.seek(0)
with Image.open(buf) as im:
im_arr = numpy.array(im)

# Select the specified channel (if any).
if channel is not None:
Expand Down
1 change: 1 addition & 0 deletions packages/tool_util/setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ extended-assertions =
numpy
pysam
pillow
tifffile

[options.packages.find]
exclude =
Expand Down
Binary file added test-data/im4_float.tif
Binary file not shown.
5 changes: 5 additions & 0 deletions test/functional/tools/image_diff.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@
<param name="in" value="im1_uint8.png" />
<output name="out" value="im1_uint8.png" compare="image_diff" metric="rms" eps="0" />
</test>
<!-- test pair of equal images (float tiff) -->
<test>
<param name="in" value="im4_float.tif" />
<output name="out" value="im4_float.tif" compare="image_diff" />
</test>
<!-- test pair of different images -->
<test>
<param name="in" value="im2_a.png" />
Expand Down
12 changes: 12 additions & 0 deletions test/functional/tools/validation_image.xml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,18 @@
</assert_contents>
</output>
</test>
<!-- Tests with float TIFF images -->
<test>
<param name="input" value="im4_float.tif" />
<output name="output">
<assert_contents>
<has_image_width width="25" />
<has_image_height height="25" />
<has_image_channels channels="1" />
<has_image_center_of_mass center_of_mass="11.75, 11.75" />
</assert_contents>
</output>
</test>
<!-- Tests with label images -->
<test>
<param name="input" value="im2_b.png" />
Expand Down

0 comments on commit 429c63e

Please sign in to comment.