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

Add a writer function to the napari-tiff plugin #12

Open
GenevieveBuckley opened this issue Feb 14, 2024 · 3 comments
Open

Add a writer function to the napari-tiff plugin #12

GenevieveBuckley opened this issue Feb 14, 2024 · 3 comments
Labels
enhancement New feature or request good first issue Good for newcomers help wanted Extra attention is needed

Comments

@GenevieveBuckley
Copy link
Collaborator

It'd be nice if this plugin could write tiff images, as well as read them.

Adding a writer function will involve:

  • Creating a new napari_tiff/napari_tiff_writer.py file, similar to this example
  • Updating the napari.yaml file to include the writer plugin information (see here and here, you need to update napari.yaml in two places)
  • Adding a test for the new writer function
@GenevieveBuckley GenevieveBuckley added enhancement New feature or request help wanted Extra attention is needed good first issue Good for newcomers labels Feb 14, 2024
@jni
Copy link
Member

jni commented Feb 14, 2024

@Czaki has an IJ tiff writer here:

https://github.com/4DNucleome/PartSeg/blob/be76bc5b456246f87b270195cdbd74518f329bd3/package/PartSegImage/image_writer.py

but has the following issues (Zulip discussion) in making it a real napari writer plugin:

  1. lack of units in napari metadata,
  2. I only write minimum set of metadata, it will be nice to improve it (for example save colormap etc)
  3. lack of axes order in napari metadata where both Imagej-tiff and ome-tiff require to declare them.

But that code will be a good reference once we have the necessary metadata on layers.

@imagesc-bot
Copy link

This issue has been mentioned on Image.sc Forum. There might be relevant details there:

https://forum.image.sc/t/napari-ome-tiff-reader/91918/15

@GenevieveBuckley
Copy link
Collaborator Author

GenevieveBuckley commented Jun 7, 2024

Relevant zulip discussion: https://napari.zulipchat.com/#narrow/stream/212875-general/topic/How.20do.20I.20save.20in.20the.20ImageJ.20TIFF-format

David has come up with this solution, for preserving the image metadata (scales, etc.)

import napari
import tifffile
from qtpy.QtWidgets import QFileDialog, QPushButton, QVBoxLayout, QWidget, QListWidget, QListWidgetItem
import numpy as np

class SaveLayerWidget(QWidget):
    def __init__(self, viewer):
        super().__init__()
        self.viewer = viewer

        # Layout and buttons
        layout = QVBoxLayout()

        self.layer_list = QListWidget()
        self.layer_list.setSelectionMode(QListWidget.MultiSelection)
        layout.addWidget(self.layer_list)

        save_button = QPushButton('Save Selected Layers')
        save_button.clicked.connect(self.save_selected_layers)
        layout.addWidget(save_button)

        self.setLayout(layout)
        self.populate_layer_list()

    def populate_layer_list(self):
        self.layer_list.clear()
        for layer in self.viewer.layers:
            item = QListWidgetItem(layer.name)
            item.setData(0x0100, layer)
            self.layer_list.addItem(item)

    def save_selected_layers(self):
        selected_items = self.layer_list.selectedItems()
        if not selected_items:
            print("No layers selected.")
            return

        dialog = QFileDialog()
        save_dir = dialog.getExistingDirectory(caption="Select Directory to Save Layers")
        if not save_dir:
            print("No directory selected.")
            return

        for item in selected_items:
            layer = item.data(0x0100)
            if isinstance(layer, napari.layers.Image):
                self.save_layer(layer, save_dir)
            else:
                print(f"Layer {layer.name} is not an image layer. Skipping.")

    def save_layer(self, layer, save_dir):
        try:
            image_data = layer.data
            metadata = layer.metadata or {}
            scale_metadata = layer.scale

            if len(scale_metadata) == 3:
                z_scale, y_scale, x_scale = scale_metadata
            elif len(scale_metadata) == 2:
                z_scale = 1
                y_scale, x_scale = scale_metadata
            else:
                raise ValueError('Unexpected scale dimensions.')

            ij_metadata = {
                'axes': 'ZYX',
                'spacing': z_scale,
                'unit': 'micron',
                'Info': 'Exported from Napari using tifffile.',
                'Properties': {'License': 'Public domain'},
            }

            resolution = (1.0 / x_scale, 1.0 / y_scale)
            filename = f"{save_dir}/{layer.name}.tif"

            tifffile.imwrite(
                filename,
                image_data.astype(np.float32),
                imagej=True,
                resolution=resolution,
                metadata=ij_metadata
            )
            print(f"Image layer {layer.name} saved with metadata to {filename}")

        except Exception as e:
            print(f"An error occurred: {e}")

# Hook into Napari
def main():
    viewer = napari.current_viewer()
    save_layer_widget = SaveLayerWidget(viewer)
    viewer.window.add_dock_widget(save_layer_widget, area='right')

if __name__ == "__main__":
    main()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request good first issue Good for newcomers help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

3 participants