napari / napari-tiff

A napari reader plugin for tiff images.
BSD 3-Clause "New" or "Revised" License
8 stars 4 forks source link

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

Open GenevieveBuckley opened 6 months ago

GenevieveBuckley commented 6 months ago

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

Adding a writer function will involve:

jni commented 6 months ago

@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 commented 6 months ago

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 commented 2 months ago

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()