Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ torch = [
"picked_particles_3d_central_slice" = "cryoemservices.services.images_plugins:picked_particles_3d_central_slice"
"tiff_to_apng" = "cryoemservices.services.images_plugins:tiff_to_apng"
"tilt_series_alignment" = "cryoemservices.services.images_plugins:tilt_series_alignment"
"xrm_to_jpeg" = "cryoemservices.services.images_plugins:xrm_to_jpeg"
[project.entry-points."cryoemservices.services.process_recipe.filters"]
ispyb = "cryoemservices.util.process_recipe_tools:ispyb_filter"
[project.entry-points."cryoemservices.wrappers"]
Expand Down
29 changes: 27 additions & 2 deletions src/cryoemservices/services/images_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
import pandas as pd
import PIL.Image
import starfile
import tifffile as tf
import tifffile

@tieneupin tieneupin Jul 3, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Double import? tifffile has been imported as tf right below this.
I'll leave it to you to pick which convention to use for this file. 👍

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was an accident. I've got for tifffile as tf is sometimes tensorflow

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yes, that's fair, they both have the same initials.

from PIL import ImageDraw, ImageEnhance, ImageFilter, ImageFont
from txrm2tiff.main import convert_and_save

from cryoemservices.services.cryolo import grid_bar_histogram
from cryoemservices.util.image_processing.shared import convert_to_rgb
Expand Down Expand Up @@ -686,7 +687,7 @@ def tiff_to_apng(plugin_params: Callable):
img = PIL.Image.open(input_file)

# Determine number of frames in image
with tf.TiffFile(input_file) as tiff_file:
with tifffile.TiffFile(input_file) as tiff_file:
num_frames = len(tiff_file.pages)

# Collect image frames
Expand Down Expand Up @@ -924,3 +925,27 @@ def tilt_series_alignment(plugin_params: Callable):
extra={"image-processing-time": timing},
)
return outfile


def xrm_to_jpeg(plugin_params: Callable):
if not required_parameters(plugin_params, ["xrm_file", "tiff_destination"]):
return False
xrm_path = Path(plugin_params("xrm_file"))
tiff_path = Path(plugin_params("tiff_destination"))
annotate = plugin_params("annotate")
if not xrm_path.is_file():
logger.error(f"File {xrm_path} not found")
return False

convert_and_save(
xrm_path, str(tiff_path).replace("_Annotated", ""), annotate=annotate or False
)
data = tifffile.imread(tiff_path)
if len(data.shape) == 4:
# Take first frame if 3D RGB
data = data[0]
with PIL.Image.fromarray(data) as thumb_im:
thumb_im.thumbnail((1024, 1024))
thumbnail_jpg = tiff_path.parent / (tiff_path.stem + "_thumbnail.jpg")
thumb_im.save(thumbnail_jpg)
return tiff_path
35 changes: 35 additions & 0 deletions tests/services/test_images_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
picked_particles_3d_central_slice,
tiff_to_apng,
tilt_series_alignment,
xrm_to_jpeg,
)
from cryoemservices.util.image_processing.shared import convert_to_rgb

Expand Down Expand Up @@ -103,6 +104,19 @@ def params(key):
return params


def plugin_params_xrm(input_file, output_file, command):
def params(key):
p = {
"parameters": {"images_command", command},
"xrm_file": input_file,
"tiff_destination": output_file,
"annotate": True,
}
return p.get(key)

return params


def test_mrc_to_jpeg_nack_when_file_not_found(tmp_path):
jpeg_path = tmp_path / "new_folder/new_job/new_file.jpeg"
assert not mrc_to_jpeg(plugin_params(jpeg_path, False))
Expand Down Expand Up @@ -808,6 +822,23 @@ def test_tilt_image_alignment_works_2d(mock_imagedraw, tmp_path):
assert ellipse_calls[1][1] == {"width": 20, "outline": "#f5a927"}


@mock.patch("cryoemservices.services.images_plugins.convert_and_save")
def test_xrm_to_jpeg(mock_convert, tmp_path):
input_xrm = tmp_path / "example.xrm"
input_xrm.touch()
output_tiff = tmp_path / "example_Annotated.tiff"
data_2d = np.linspace(0, 255, 100).reshape((10, 10))
im = PIL.Image.fromarray(data_2d)
im.convert("RGB").save(output_tiff)

assert xrm_to_jpeg(plugin_params_xrm(input_xrm, output_tiff, "xrm_to_tiff"))

mock_convert.assert_called_once_with(
input_xrm, f"{tmp_path}/example.tiff", annotate=True
)
assert (tmp_path / "example_Annotated_thumbnail.jpg").is_file()


def test_interfaces_without_keys():
"""Test that file path keys are required"""
assert not mrc_to_jpeg(lambda x: None)
Expand All @@ -818,6 +849,7 @@ def test_interfaces_without_keys():
assert not picked_particles_3d_central_slice(lambda x: None)
assert not picked_particles_3d_apng(lambda x: None)
assert not tilt_series_alignment(lambda x: None)
assert not xrm_to_jpeg(lambda x: None)


def test_interfaces_without_files(tmp_path):
Expand Down Expand Up @@ -853,3 +885,6 @@ def test_interfaces_without_files(tmp_path):
)
)
assert not tilt_series_alignment(plugin_params(tmp_path / "not.mrc", True, 1))
assert not xrm_to_jpeg(
plugin_params_xrm(tmp_path / "not.xrm", tmp_path / "out.tiff", "xrm_to_tiff")
)
3 changes: 2 additions & 1 deletion tests/services/test_images_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def test_plugins_exist():
service.initializing()

# Check the expected images plugins are present
assert len(service.image_functions.keys()) == 10
assert len(service.image_functions.keys()) == 11
assert service.image_functions.get("mrc_central_slice", "")
assert service.image_functions.get("mrc_projection", "")
assert service.image_functions.get("mrc_to_apng", "")
Expand All @@ -35,6 +35,7 @@ def test_plugins_exist():
assert service.image_functions.get("picked_particles_3d_central_slice", "")
assert service.image_functions.get("tiff_to_apng", "")
assert service.image_functions.get("tilt_series_alignment", "")
assert service.image_functions.get("xrm_to_jpeg", "")


@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows")
Expand Down
Loading