diff --git a/pyproject.toml b/pyproject.toml index 7a4f824f..3afdfa98 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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"] diff --git a/src/cryoemservices/services/images_plugins.py b/src/cryoemservices/services/images_plugins.py index ea9194f1..194ab9b5 100644 --- a/src/cryoemservices/services/images_plugins.py +++ b/src/cryoemservices/services/images_plugins.py @@ -10,8 +10,9 @@ import pandas as pd import PIL.Image import starfile -import tifffile as tf +import tifffile 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 @@ -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 @@ -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 diff --git a/tests/services/test_images_plugins.py b/tests/services/test_images_plugins.py index 410a1b61..6cfcb8f2 100644 --- a/tests/services/test_images_plugins.py +++ b/tests/services/test_images_plugins.py @@ -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 @@ -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)) @@ -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) @@ -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): @@ -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") + ) diff --git a/tests/services/test_images_service.py b/tests/services/test_images_service.py index 45973e8e..2499193b 100644 --- a/tests/services/test_images_service.py +++ b/tests/services/test_images_service.py @@ -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", "") @@ -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")