napari-stitcher 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,16 @@
1
+ __version__ = "0.0.1"
2
+
3
+ from ._reader import napari_get_reader
4
+ from ._sample_data import make_sample_data
5
+ from ._stitcher_widget import StitcherQWidget
6
+ from ._mosaic_widget import MosaicQWidget
7
+ from ._writer import write_multiple, write_single_image
8
+
9
+ __all__ = (
10
+ "napari_get_reader",
11
+ "write_single_image",
12
+ "write_multiple",
13
+ "make_sample_data",
14
+ "StitcherQWidget",
15
+ "MosaicQWidget",
16
+ )
@@ -0,0 +1,192 @@
1
+ """
2
+ This module is an example of a barebones QWidget plugin for napari
3
+
4
+ It implements the Widget specification.
5
+ see: https://napari.org/stable/plugins/guides.html?#widgets
6
+
7
+ Replace code below according to your needs.
8
+ """
9
+ from typing import TYPE_CHECKING
10
+ import os, tempfile, sys
11
+
12
+ import numpy as np
13
+
14
+ from napari.utils import notifications
15
+
16
+ from magicgui import widgets
17
+ from qtpy.QtWidgets import QVBoxLayout, QWidget
18
+
19
+ from multiview_stitcher import (
20
+ registration,
21
+ fusion,
22
+ spatial_image_utils,
23
+ msi_utils,
24
+ )
25
+
26
+ from napari_stitcher import _reader, viewer_utils, _utils
27
+
28
+ if TYPE_CHECKING:
29
+ import napari
30
+
31
+
32
+ class MosaicQWidget(QWidget):
33
+ # your QWidget.__init__ can optionally request the napari viewer instance
34
+ # in one of two ways:
35
+ # 1. use a parameter called `napari_viewer`, as done here
36
+ # 2. use a type annotation of 'napari.viewer.Viewer' for any parameter
37
+ def __init__(self, napari_viewer):
38
+ super().__init__()
39
+ self.viewer = napari_viewer
40
+ self.viewer.title = "Napari Stitcher"
41
+
42
+ self.setLayout(QVBoxLayout())
43
+
44
+ # mosaic arrangement widgets
45
+ self.overlap = widgets.FloatSlider(
46
+ value=0.1, min=0, max=0.9999, label='Overlap:')
47
+ self.n_col = widgets.SpinBox(
48
+ value=1, min=1, max=100, label='Number of columns:')
49
+ self.n_row = widgets.SpinBox(
50
+ value=1, min=1, max=100, label='Number of rows:')
51
+ self.mosaic_arr = widgets.ComboBox(
52
+ choices=['rows first','columns first','snake by rows','snake by columns'],
53
+ value='snake by columns',
54
+ label='Mosaic arrangement:',
55
+ tooltip='Select the type of grid arrangement. The tile order is taken from the list of layers.')
56
+ self.input_order = widgets.ComboBox(
57
+ choices=['forward','backward'],
58
+ value='forward',
59
+ label='Image order:',
60
+ tooltip='Whether to arrange the tiles in forward or backward order.')
61
+ self.button_arrange_tiles = widgets.Button(text='Arrange tiles')
62
+
63
+ # organize widgets
64
+ self.mosaic_widgets = [
65
+ self.overlap,
66
+ self.n_col,
67
+ self.n_row,
68
+ self.mosaic_arr,
69
+ self.input_order,
70
+ self.button_arrange_tiles,
71
+ ]
72
+
73
+ self.container = widgets.VBox(widgets=\
74
+ self.mosaic_widgets
75
+ )
76
+
77
+ self.container.native.setMinimumWidth = 50
78
+
79
+ self.layout().addWidget(self.container.native)
80
+
81
+ # connect callbacks
82
+ self.button_arrange_tiles.clicked.connect(self.arrange_tiles)
83
+
84
+
85
+ def reset(self):
86
+
87
+ self.msims = {}
88
+ self.input_layers = []
89
+
90
+
91
+ def link_channel_layers(self, layers):
92
+
93
+ # link channel layers
94
+ from napari.experimental import link_layers
95
+
96
+ sims = {l.name: msi_utils.get_sim_from_msim(self.msims[l.name])
97
+ for l in layers}
98
+
99
+ channels = [_utils.get_str_unique_to_ch_from_sim_coords(sim.coords) for sim in sims.values()]
100
+ for ch in channels:
101
+ ch_layers = list(_utils.filter_layers(layers, sims, ch=ch))
102
+
103
+ if len(ch_layers):
104
+ link_layers(ch_layers, ('contrast_limits', 'visible'))
105
+
106
+
107
+ def arrange_tiles(self):
108
+ """
109
+ Arrange tiles in the viewer according to the selected mosaic arrangement.
110
+ """
111
+
112
+ layer_channels = [
113
+ l.name if not '::' in l.name else _utils.get_str_unique_to_ch_from_layer_name(l.name)
114
+ for l in self.viewer.layers]
115
+
116
+ layer_views = [
117
+ _utils.get_str_unique_to_view_from_layer_name(l.name)
118
+ for l in self.viewer.layers]
119
+
120
+ view_order = []
121
+ for lview in layer_views:
122
+ if lview not in view_order:
123
+ view_order.append(lview)
124
+
125
+ if self.input_order.value == 'backward':
126
+ view_order = view_order[::-1]
127
+
128
+ n_channels = len(np.unique(layer_channels))
129
+ n_tiles = len(np.unique(layer_views))
130
+
131
+ if n_tiles != self.n_col.value * self.n_row.value:
132
+ notifications.notification_manager.receive_info(
133
+ 'Warning: Total number of tiles does not match the selected mosaic arrangement.'
134
+ )
135
+ return
136
+
137
+ if n_tiles == 0:
138
+ notifications.notification_manager.receive_info(
139
+ 'No layers loaded.'
140
+ )
141
+ return
142
+
143
+ tile_w = self.viewer.layers[0].extent.world[1,-1] - self.viewer.layers[0].extent.world[0,-1] # tile width
144
+ tile_h = self.viewer.layers[0].extent.world[1,-2] - self.viewer.layers[0].extent.world[0,-2] # tile height
145
+
146
+ tile_indices = _utils.get_tile_indices(
147
+ mosaic_arr=self.mosaic_arr.value,
148
+ n_col=self.n_row.value,
149
+ n_row=self.n_col.value,
150
+ n_tiles=n_tiles
151
+ )
152
+
153
+ if self.input_order.value == 'forward':
154
+ l0_translate = self.viewer.layers[0].translate[-2:]
155
+ else:
156
+ l0_translate = self.viewer.layers[-1].translate[-2:]
157
+
158
+ for l in self.viewer.layers:
159
+ view = _utils.get_str_unique_to_view_from_layer_name(l.name)
160
+ itile = view_order.index(view)
161
+ tile_index = tile_indices[itile]
162
+ # print(f'Layer {l.name} -> tile index: {tile_index}')
163
+ l.translate[-2:] = [
164
+ l0_translate[0] + tile_index[1] * tile_w - tile_index[1] * tile_w * self.overlap.value,
165
+ l0_translate[1] + tile_index[0] * tile_h - tile_index[0] * tile_h * self.overlap.value
166
+ ]
167
+ l.refresh()
168
+
169
+
170
+ def __del__(self):
171
+
172
+ print('Deleting napari-stitcher mosaic widget')
173
+
174
+
175
+ if __name__ == "__main__":
176
+
177
+ import napari
178
+
179
+ viewer = napari.Viewer()
180
+
181
+ wdg = MosaicQWidget(viewer)
182
+ viewer.window.add_dock_widget(wdg)
183
+
184
+ for irow in range(2):
185
+ for icol in range(2):
186
+ for ch in range(2):
187
+ viewer.add_image(np.random.randint(0, 100, [100] * 3), name=f'layer_{irow}_{icol} :: ch{ch}')
188
+
189
+ wdg.n_col.value = 2
190
+ wdg.n_row.value = 2
191
+
192
+ wdg.button_arrange_tiles.clicked()
@@ -0,0 +1,98 @@
1
+ """
2
+ This module is an example of a barebones numpy reader plugin for napari.
3
+
4
+ It implements the Reader specification, but your plugin may choose to
5
+ implement multiple readers or even other plugin contributions. see:
6
+ https://napari.org/stable/plugins/guides.html?#readers
7
+ """
8
+
9
+ from multiview_stitcher import msi_utils
10
+ from multiview_stitcher.io import read_mosaic_image_into_list_of_spatial_xarrays,\
11
+ METADATA_TRANSFORM_KEY
12
+
13
+ from napari_stitcher import viewer_utils
14
+
15
+
16
+ def napari_get_reader(path):
17
+ """A basic implementation of a Reader contribution.
18
+
19
+ Parameters
20
+ ----------
21
+ path : str or list of str
22
+ Path to file, or list of paths.
23
+
24
+ Returns
25
+ -------
26
+ function or None
27
+ If the path is a recognized format, return a function that accepts the
28
+ same path or list of paths, and returns a list of layer data tuples.
29
+ """
30
+ if isinstance(path, list):
31
+ # reader plugins may be handed single path, or a list of paths.
32
+ # if it is a list, it is assumed to be an image stack...
33
+ # so we are only going to look at the first file.
34
+ path = path[0]
35
+
36
+ # if we know we cannot read the file, we immediately return None.
37
+ # otherwise we return the *function* that can read ``path``.
38
+ if path.endswith(".czi"):
39
+ return read_mosaic
40
+ else:
41
+ return None
42
+
43
+
44
+ def read_mosaic(path, scene_index=None):
45
+ """
46
+
47
+ Read in tiles as layers.
48
+
49
+ Take a path or list of paths and return a list of LayerData tuples.
50
+
51
+ Readers are expected to return data as a list of tuples, where each tuple
52
+ is (data, [add_kwargs, [layer_type]]), "add_kwargs" and "layer_type" are
53
+ both optional.
54
+
55
+ Parameters
56
+ ----------
57
+ path : str or list of str
58
+ Path to file, or list of paths.
59
+
60
+ Returns
61
+ -------
62
+ layer_data : list of tuples
63
+ A list of LayerData tuples where each tuple in the list contains
64
+ (data, metadata, layer_type), where data is a numpy array, metadata is
65
+ a dict of keyword arguments for the corresponding viewer.add_* method
66
+ in napari, and layer_type is a lower-case string naming the type of
67
+ layer. Both "meta", and "layer_type" are optional. napari will
68
+ default to layer_type=="image" if not provided
69
+ """
70
+ # handle both a string and a list of strings
71
+ paths = [path] if isinstance(path, str) else path
72
+
73
+ sims = read_mosaic_image_into_list_of_spatial_xarrays(paths[0], scene_index=scene_index)
74
+
75
+ msims = [msi_utils.get_msim_from_sim(sim, scale_factors=[])
76
+ for sim in sims]
77
+
78
+ out_layers = viewer_utils.create_image_layer_tuples_from_msims(
79
+ msims,
80
+ transform_key=METADATA_TRANSFORM_KEY,
81
+ data_as_array=False)
82
+
83
+ return out_layers
84
+
85
+
86
+ if __name__ == "__main__":
87
+
88
+ from multiview_stitcher.sample_data import get_mosaic_sample_data_path
89
+
90
+ filename = get_mosaic_sample_data_path()
91
+
92
+ sims = read_mosaic_image_into_list_of_spatial_xarrays(filename)
93
+
94
+ from multiview_stitcher import msi_utils
95
+
96
+ msim = msi_utils.get_msim_from_sim(sims[0], scale_factors=[])
97
+
98
+ msim_sel = msi_utils.multiscale_sel_coords(msim, {'c':'EGFP'})
@@ -0,0 +1,66 @@
1
+ """
2
+ This module is an example of a barebones sample data provider for napari.
3
+
4
+ It implements the "sample data" specification.
5
+ see: https://napari.org/stable/plugins/guides.html?#sample-data
6
+
7
+ Replace code below according to your needs.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import numpy as np
12
+
13
+ from pathlib import Path
14
+
15
+ from napari_stitcher import viewer_utils
16
+ from napari_stitcher._reader import read_mosaic
17
+
18
+ from multiview_stitcher.sample_data import get_mosaic_sample_data_path
19
+ from multiview_stitcher.io import METADATA_TRANSFORM_KEY
20
+ from multiview_stitcher.sample_data import generate_tiled_dataset
21
+ from multiview_stitcher.msi_utils import get_msim_from_sim
22
+
23
+
24
+ def make_sample_data():
25
+ """Generates an image"""
26
+ # Return list of tuples
27
+ # [(data1, add_image_kwargs1), (data2, add_image_kwargs2)]
28
+ # Check the documentation for more information about the
29
+ # add_image_kwargs
30
+ # https://napari.org/stable/api/napari.Viewer.html#napari.Viewer.add_image
31
+
32
+ sample_path = get_mosaic_sample_data_path()
33
+
34
+ return read_mosaic([sample_path])
35
+
36
+
37
+ def drifting_timelapse_with_stage_shifts_no_overlap_2d():
38
+
39
+ sims = generate_tiled_dataset(
40
+ ndim=2, N_t=20, N_c=1,
41
+ tile_size=30, tiles_x=3, tiles_y=3, tiles_z=1,
42
+ drift_scale=2., shift_scale=2.,
43
+ overlap=0, zoom=8, dtype=np.uint8)
44
+
45
+ msims = [get_msim_from_sim(sim) for sim in sims]
46
+
47
+ layer_tuples = viewer_utils.create_image_layer_tuples_from_msims(
48
+ msims, transform_key=METADATA_TRANSFORM_KEY)
49
+
50
+ return layer_tuples
51
+
52
+
53
+ def timelapse_with_stage_shifts_with_overlap_3d():
54
+
55
+ sims = generate_tiled_dataset(
56
+ ndim=3, N_t=20, N_c=1,
57
+ tile_size=30, tiles_x=3, tiles_y=3, tiles_z=1,
58
+ drift_scale=0., shift_scale=2.,
59
+ overlap=3, zoom=8, dtype=np.uint8)
60
+
61
+ msims = [get_msim_from_sim(sim) for sim in sims]
62
+
63
+ layer_tuples = viewer_utils.create_image_layer_tuples_from_msims(
64
+ msims, transform_key=METADATA_TRANSFORM_KEY)
65
+
66
+ return layer_tuples