26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255 | def __init__(self, nb: Notebook, background_image: Optional[Union[str, np.ndarray]] = 'dapi',
gene_marker_file: Optional[str] = None):
"""
This is the function to view the results of the pipeline
i.e. the spots found and which genes they were assigned to.
Args:
nb: Notebook containing at least the `ref_spots` page.
background_image: Optional file_name or image that will be plotted as the background image.
If image, z dimension needs to be first i.e. `n_z x n_y x n_x` if 3D or `n_y x n_x` if 2D.
If pass *2D* image for *3D* data, will show same image as background on each z-plane.
gene_marker_file: Path to csv file containing marker and color for each gene. There must be 6 columns
in the csv file with the following headers:
* GeneNames - str, name of gene with first letter capital
* ColorR - float, Rgb color for plotting
* ColorG - float, rGb color for plotting
* ColorB - float, rgB color for plotting
* napari_symbol - str, symbol used to plot in napari
* mpl_symbol - str, equivalent of napari symbol in matplotlib.
If it is not provided, then the default file *coppafish/plot/results_viewer/legend.gene_color.csv*
will be used.
"""
# TODO: flip y axis so origin bottom left
self.nb = nb
if gene_marker_file is None:
gene_marker_file = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'gene_color.csv')
gene_legend_info = pd.read_csv(gene_marker_file)
# indices of genes in notebook to gene_color data - quicker to look up integers than names
# in change_threshold
n_legend_genes = len(gene_legend_info['GeneNames'])
self.legend_gene_symbol = np.asarray(gene_legend_info['mpl_symbol'])
self.legend_gene_no = np.ones(n_legend_genes, dtype=int) * -1
for i in range(n_legend_genes):
# TODO: maybe guard against different cases in this comparison
gene_ind = np.where(self.nb.call_spots.gene_names == gene_legend_info['GeneNames'][i])[0]
if len(gene_ind) > 0:
self.legend_gene_no[i] = gene_ind[0]
# concatenate anchor and omp spots so can use button to switch between them.
self.omp_0_ind = self.nb.ref_spots.tile.size # number of anchor spots
if self.nb.has_page('omp'):
self.n_spots = self.omp_0_ind + self.nb.omp.tile.size # number of anchor + number of omp spots
else:
self.n_spots = self.omp_0_ind
spot_zyx = np.zeros((self.n_spots, 3))
spot_zyx[:self.omp_0_ind] = (self.nb.ref_spots.local_yxz + self.nb.stitch.tile_origin[self.nb.ref_spots.tile]
)[:, [2, 0, 1]]
if self.nb.has_page('omp'):
spot_zyx[self.omp_0_ind:] = (self.nb.omp.local_yxz + self.nb.stitch.tile_origin[self.nb.omp.tile]
)[:, [2, 0, 1]]
if not self.nb.basic_info.is_3d:
spot_zyx = spot_zyx[:, 1:]
# indicate spots shown when plot first opened - omp if exists, else anchor
if self.nb.has_page('omp'):
show_spots = np.zeros(self.n_spots, dtype=bool)
show_spots[self.omp_0_ind:] = quality_threshold(self.nb, 'omp')
else:
show_spots = quality_threshold(self.nb, 'anchor')
# color to plot for all genes in the notebook
gene_color = np.ones((len(self.nb.call_spots.gene_names), 3))
for i in range(n_legend_genes):
if self.legend_gene_no[i] != -1:
gene_color[self.legend_gene_no[i]] = [gene_legend_info.loc[i, 'ColorR'],
gene_legend_info.loc[i, 'ColorG'],
gene_legend_info.loc[i, 'ColorB']]
self.viewer = napari.Viewer()
self.viewer.window.qt_viewer.dockLayerList.setVisible(False)
self.viewer.window.qt_viewer.dockLayerControls.setVisible(False)
# Add background image if given
self.diagnostic_layer_ind = 0
self.image_layer_ind = None
if background_image is not None:
if isinstance(background_image, str):
if background_image.lower() == 'dapi':
file_name = nb.file_names.big_dapi_image
elif background_image.lower() == 'anchor':
file_name = nb.file_names.big_anchor_image
else:
file_name = background_image
if file_name is not None and os.path.isfile(file_name):
background_image = np.load(file_name)
if file_name.endswith('.npz'):
# Assume image is first array if .npz file
background_image = background_image[background_image._files[0]]
else:
background_image = None
warnings.warn(f'No file exists with address =\n{file_name}\nso plotting with no background.')
if background_image is not None:
self.viewer.add_image(background_image)
self.diagnostic_layer_ind = 1
self.image_layer_ind = 0
self.viewer.layers[self.image_layer_ind].contrast_limits_range = [background_image.min(),
background_image.max()]
self.image_contrast_slider = QRangeSlider(Qt.Orientation.Horizontal) # Slider to change score_thresh
self.image_contrast_slider.setRange(background_image.min(), background_image.max())
# Make starting lower bound contrast the 95th percentile value so most appears black
# Use mid_z to quicken up calculation
mid_z = int(background_image.shape[0]/2)
start_contrast = np.percentile(background_image[mid_z], [95, 99.99]).astype(int).tolist()
self.image_contrast_slider.setValue(start_contrast)
self.change_image_contrast()
# When dragging, status will show contrast values.
self.image_contrast_slider.valueChanged.connect(lambda x: self.show_image_contrast(x[0], x[1]))
# On release of slider, genes shown will change
self.image_contrast_slider.sliderReleased.connect(self.change_image_contrast)
# Add legend indicating genes plotted
self.legend = {'fig': None, 'ax': None}
self.legend['fig'], self.legend['ax'], n_gene_label_letters = \
add_legend(gene_legend_info=gene_legend_info, genes=nb.call_spots.gene_names)
# xy is position of each symbol in legend, need to see which gene clicked on.
self.legend['xy'] = np.zeros((len(self.legend['ax'].collections), 2), dtype=float)
self.legend['gene_no'] = np.zeros(len(self.legend['ax'].collections), dtype=int)
# In legend, each gene name label has at most n_gene_label_letters letters so need to crop
# gene_names in notebook when looking for corresponding gene in legend.
gene_names_crop = np.asarray([gene_name[:n_gene_label_letters] for gene_name in nb.call_spots.gene_names])
for i in range(self.legend['xy'].shape[0]):
# Position of label for each gene in legend window
self.legend['xy'][i] = np.asarray(self.legend['ax'].collections[i].get_offsets())
# gene no in notebook that each position in the legend corresponds to
self.legend['gene_no'][i] = \
np.where(gene_names_crop == self.legend['ax'].texts[i].get_text())[0][0]
self.legend['fig'].mpl_connect('button_press_event', self.update_genes)
self.viewer.window.add_dock_widget(self.legend['fig'], area='left', name='Genes')
self.active_genes = np.arange(len(nb.call_spots.gene_names)) # start with all genes shown
if background_image is not None:
# Slider to change background image contrast
self.viewer.window.add_dock_widget(self.image_contrast_slider, area="left", name='Image Contrast')
# Add all spots in layer as transparent white spots.
point_size = 10 # with size=4, spots are too small to see
self.viewer.add_points(spot_zyx, name='Diagnostic', face_color='w', size=point_size + 2, opacity=0,
shown=show_spots)
# Add gene spots with coppafish color code - different layer for each symbol
if self.nb.has_page('omp'):
self.spot_gene_no = np.hstack((self.nb.ref_spots.gene_no, self.nb.omp.gene_no))
else:
self.spot_gene_no = self.nb.ref_spots.gene_no
self.label_prefix = 'Gene Symbol:' # prefix of label for layers showing spots
for s in np.unique(self.legend_gene_symbol):
spots_correct_gene = np.isin(self.spot_gene_no, self.legend_gene_no[self.legend_gene_symbol == s])
if spots_correct_gene.any():
coords_to_plot = spot_zyx[spots_correct_gene]
spotcolor_to_plot = gene_color[self.spot_gene_no[spots_correct_gene]]
symb_to_plot = np.unique(gene_legend_info[self.legend_gene_symbol == s]['napari_symbol'])[0]
self.viewer.add_points(coords_to_plot, face_color=spotcolor_to_plot, symbol=symb_to_plot,
name=f'{self.label_prefix}{s}', size=point_size,
shown=show_spots[spots_correct_gene])
# TODO: showing multiple z-planes at once is possible using out_of_slice_display=True,
# but at the moment cannot use at same time as show.
# When this works, can change n_z shown by changing z-dimension of point_size i.e.
# point_size = [z_size, 10, 10] and z_size changes.
# On next napari, release should be able to change thickness of spots in z-direction i.e. control what
# number of z-planes can be seen at any one time:
# https://github.com/napari/napari/issues/4816#issuecomment-1186600574.
# Then see find_spots viewer for how I added a z-thick slider
self.viewer.layers.selection.active = self.viewer.layers[self.diagnostic_layer_ind]
# so indicates when a spot is selected in viewer status
# It is needed because layer is transparent so can't see when select spot.
self.viewer_status_on_select()
config = self.nb.get_config()['thresholds']
self.score_omp_multiplier = config['score_omp_multiplier']
self.score_thresh_slider = QDoubleRangeSlider(Qt.Orientation.Horizontal) # Slider to change score_thresh
# Scores for anchor/omp are different so reset score range when change method
# Max possible score is that found for ref_spots, as this can be more than 1.
# Max possible omp score is 1.
max_score = np.around(round_any(nb.ref_spots.score.max(), 0.1, 'ceil'), 2)
max_score = float(np.clip(max_score, 1, np.inf))
self.score_range = {'anchor': [config['score_ref'], max_score]}
if self.nb.has_page('omp'):
self.score_range['omp'] = [config['score_omp'], max_score]
self.score_thresh_slider.setValue(self.score_range['omp'])
else:
self.score_thresh_slider.setValue(self.score_range['anchor'])
self.score_thresh_slider.setRange(0, max_score)
# When dragging, status will show thresh.
self.score_thresh_slider.valueChanged.connect(lambda x: self.show_score_thresh(x[0], x[1]))
# On release of slider, genes shown will change
self.score_thresh_slider.sliderReleased.connect(self.update_plot)
self.viewer.window.add_dock_widget(self.score_thresh_slider, area="left", name='Score Range')
# OMP Score Multiplier Slider
self.omp_score_multiplier_slider = QDoubleSlider(Qt.Orientation.Horizontal)
self.omp_score_multiplier_slider.setValue(self.score_omp_multiplier)
self.omp_score_multiplier_slider.setRange(0, 50)
self.omp_score_multiplier_slider.valueChanged.connect(lambda x: self.show_omp_score_multiplier(x))
self.omp_score_multiplier_slider.sliderReleased.connect(self.update_plot)
# intensity is calculated same way for anchor / omp method so do not reset intensity threshold
# when change method.
self.intensity_thresh_slider = QDoubleSlider(Qt.Orientation.Horizontal)
self.intensity_thresh_slider.setRange(0, 1)
intensity_thresh = get_intensity_thresh(nb)
self.intensity_thresh_slider.setValue(intensity_thresh)
# When dragging, status will show thresh.
self.intensity_thresh_slider.valueChanged.connect(lambda x: self.show_intensity_thresh(x))
# On release of slider, genes shown will change
self.intensity_thresh_slider.sliderReleased.connect(self.update_plot)
self.viewer.window.add_dock_widget(self.intensity_thresh_slider, area="left", name='Intensity Threshold')
if self.nb.has_page('omp'):
self.method_buttons = ButtonMethodWindow('OMP') # Buttons to change between Anchor and OMP spots showing.
else:
self.method_buttons = ButtonMethodWindow('Anchor')
self.method_buttons.button_anchor.clicked.connect(self.button_anchor_clicked)
self.method_buttons.button_omp.clicked.connect(self.button_omp_clicked)
if self.nb.has_page('omp'):
self.viewer.window.add_dock_widget(self.omp_score_multiplier_slider, area="left",
name='OMP Score Multiplier')
# Only have button to change method if have omp page too.
self.viewer.window.add_dock_widget(self.method_buttons, area="left", name='Method')
self.key_call_functions()
if self.nb.basic_info.is_3d:
self.viewer.dims.axis_labels = ['z', 'y', 'x']
else:
self.viewer.dims.axis_labels = ['y', 'x']
napari.run()
|