-
Notifications
You must be signed in to change notification settings - Fork 0
/
napari_browser_cz.py
576 lines (443 loc) · 20 KB
/
napari_browser_cz.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
# -*- coding: utf-8 -*-
#################################################################
# File : napari_browser_cz.py
# Version : 0.2.1
# Author : czsrh
# Date : 18.02.2021
# Institution : Carl Zeiss Microscopy GmbH
#
# Disclaimer: This tool is purely experimental. Feel free to
# use it at your own risk. Especially be aware of the fact
# that automated stage movements might damage hardware if
# one starts an experiment and the the system is not setup properly.
# Please check everything in simulation mode first!
#
# Copyright (c) 2021 Carl Zeiss AG, Germany. All Rights Reserved.
#
#################################################################
from PyQt5.QtWidgets import (
QHBoxLayout,
QVBoxLayout,
QFileSystemModel,
QFileDialog,
QTreeView,
QDialogButtonBox,
QWidget,
QTableWidget,
QTableWidgetItem,
QCheckBox,
QAbstractItemView,
QComboBox,
QPushButton,
QLineEdit,
QLabel,
QGridLayout
)
from PyQt5.QtCore import Qt, QDir
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtGui import QFont
import sys
import napari
import numpy as np
from aicspylibczi import CziFile
#from czitools import imgfile_tools as imf
import imgfile_tools as imf
import czifile_tools as czt
from aicsimageio import AICSImage
import dask
import dask.array as da
import zarr
import os
from zencontrol import ZenExperiment, ZenDocuments
from pathlib import Path
class TableWidget(QWidget):
def __init__(self):
super(QWidget, self).__init__()
self.layout = QVBoxLayout(self)
self.mdtable = QTableWidget()
self.layout.addWidget(self.mdtable)
self.mdtable.setShowGrid(True)
self.mdtable.setHorizontalHeaderLabels(['Parameter', 'Value'])
header = self.mdtable.horizontalHeader()
header.setDefaultAlignment(Qt.AlignLeft)
def update_metadata(self, metadata):
# number of rows is set to number of metadata entries
row_count = len(metadata)
col_count = 2
self.mdtable.setColumnCount(col_count)
self.mdtable.setRowCount(row_count)
row = 0
# update the table with the entries from metadata dictionary
for key, value in metadata.items():
newkey = QTableWidgetItem(key)
self.mdtable.setItem(row, 0, newkey)
newvalue = QTableWidgetItem(str(value))
self.mdtable.setItem(row, 1, newvalue)
row += 1
# fit columns to content
self.mdtable.resizeColumnsToContents()
def update_style(self):
# define font size and type
fnt = QFont()
fnt.setPointSize(11)
fnt.setBold(True)
fnt.setFamily('Arial')
# update both header items
item1 = QtWidgets.QTableWidgetItem('Parameter')
item1.setForeground(QtGui.QColor(25, 25, 25))
item1.setFont(fnt)
self.mdtable.setHorizontalHeaderItem(0, item1)
item2 = QtWidgets.QTableWidgetItem('Value')
item2.setForeground(QtGui.QColor(25, 25, 25))
item2.setFont(fnt)
self.mdtable.setHorizontalHeaderItem(1, item2)
class FileTree(QWidget):
def __init__(self, filter=['*.czi'], defaultfolder=r'c:\Zen_Output'):
super(QWidget, self).__init__()
# define filter to allowed file extensions
#filter = ['*.czi', '*.ome.tiff', '*ome.tif' '*.tiff' '*.tif']
# define the style for the FileTree via s style sheet
self.setStyleSheet("""
QTreeView: : item {
background - color: rgb(38, 41, 48);
font-weight: bold;}
QTreeView: : item: : selected {
background - color: rgb(38, 41, 48);
color: rgb(0, 255, 0); }
QTreeView QHeaderView: section {
background - color: rgb(38, 41, 48);
color: rgb(255, 255, 255);}
""")
self.model = QFileSystemModel()
self.model.setRootPath(defaultfolder)
self.model.setFilter(QtCore.QDir.AllDirs | QDir.Files | QtCore.QDir.NoDotAndDotDot)
self.model.setNameFilterDisables(False)
self.model.setNameFilters(filter)
self.tree = QTreeView()
self.tree.setModel(self.model)
self.tree.setRootIndex(self.model.index(defaultfolder))
self.tree.setAnimated(True)
self.tree.setIndentation(20)
self.tree.setSortingEnabled(False)
header = self.tree.header()
header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
windowLayout = QVBoxLayout()
windowLayout.addWidget(self.tree)
self.setLayout(windowLayout)
self.tree.clicked.connect(self.on_treeView_clicked)
def on_treeView_clicked(self, index):
indexItem = self.model.index(index.row(), 0, index.parent())
filename = self.model.fileName(indexItem)
filepath = self.model.filePath(indexItem)
# open the file when clicked
print('Opening ImageFile : ', filepath)
open_image_stack(filepath)
class OptionsWidget(QWidget):
def __init__(self):
super(QWidget, self).__init__()
# Create a grid layout instance
self.grid_opt = QGridLayout()
self.grid_opt.setSpacing(10)
self.setLayout(self.grid_opt)
# add checkbox to use Dask Delayed reader to the grid layout
self.cbox_dask = QCheckBox("Use AICSImageIO Dask Reader", self)
self.cbox_dask.setChecked(True)
self.cbox_dask.setStyleSheet("font:bold;"
"font-size: 10px;"
"width :14px;"
"height :14px;"
)
self.grid_opt.addWidget(self.cbox_dask, 0, 0)
# add checkbox to open CZI after the experiment execution
self.cbox_openczi = QCheckBox("Open CZI after Acquisition", self)
self.cbox_openczi.setChecked(True)
self.cbox_openczi.setStyleSheet("font:bold;"
"font-size: 10px;"
"width :14px;"
"height :14px;"
)
self.grid_opt.addWidget(self.cbox_openczi, 1, 0)
class FileBrowser(QWidget):
def __init__(self, defaultfolder=r'c:\Zen_Output'):
super(QWidget, self).__init__()
self.layout = QHBoxLayout(self)
self.file_dialog = QFileDialog()
self.file_dialog.setWindowFlags(Qt.Widget)
self.file_dialog.setModal(False)
self.file_dialog.setOption(QFileDialog.DontUseNativeDialog)
self.file_dialog.setDirectory(defaultfolder)
# remove open and cancel button from widget
self.buttonBox = self.file_dialog.findChild(QDialogButtonBox, "buttonBox")
self.buttonBox.clear()
# only show the following file types
self.file_dialog.setNameFilter("Images (*.czi *.ome.tiff *ome.tif *.tiff *.tif)")
self.layout.addWidget(self.file_dialog)
self.file_dialog.currentChanged.connect(open_image_stack)
class StartExperiment(QWidget):
def __init__(self, expfiles_short,
savefolder=r'c:\temp',
default_cziname='myimage.czi'):
super(QWidget, self).__init__()
self.expfiles_short = expfiles_short
self.savefolder = savefolder
# Create a grid layout instance
self.grid_exp = QGridLayout()
self.grid_exp.setSpacing(10)
self.setLayout(self.grid_exp)
# add widgets to the grid layout
self.expselect = QComboBox(self)
self.expselect.addItems(self.expfiles_short)
self.expselect.setStyleSheet("font: bold;"
"font-size: 10px;"
)
self.grid_exp.addWidget(self.expselect, 0, 0)
self.startexpbutton = QPushButton('Run Experiment')
self.startexpbutton.setStyleSheet("font: bold;"
# "background-color: red;"
"font-size: 10px;"
# "height: 48px;width: 120px;"
)
self.grid_exp.addWidget(self.startexpbutton, 0, 1)
self.namelabel = QLabel(self)
self.namelabel.setText('Save Experiment result as CZI :')
self.namelabel.setStyleSheet("font: bold;"
"font-size: 10px;"
""
)
self.grid_exp.addWidget(self.namelabel, 1, 0)
self.nameedit = QLineEdit(self)
self.nameedit.setText(default_cziname)
self.nameedit.setFixedWidth(200)
self.nameedit.setStyleSheet("font: bold;"
"font-size: 10px;"
)
self.grid_exp.addWidget(self.nameedit, 1, 1)
# Set the layout on the application's window
self.startexpbutton.clicked.connect(self.on_click)
def on_click(self):
# get name of the selected experiment
current_exp = self.expselect.currentText()
print('Selected ZEN Experiment : ', current_exp)
# get the desired savename
desired_cziname = self.nameedit.text()
# disable the button while the experiment is running
self.startexpbutton.setText('Running ...')
# not nice, but this "redraws" the button
# QtCore.QApplication.processEvents()
QtWidgets.QApplication.processEvents()
# initialize the experiment with parameters
czexp = ZenExperiment(experiment=current_exp,
savefolder=self.savefolder,
cziname=desired_cziname)
# start the actual experiment
self.saved_czifilepath = czexp.startexperiment()
print('Saved CZI : ', self.saved_czifilepath)
# enable the button again when experiment is over
self.startexpbutton.setEnabled(True)
self.startexpbutton.setText('Run Experiment')
# not nice, but this "redraws" the button
QtWidgets.QApplication.processEvents()
# option to use Dask Delayed reader
#use_dask = checkboxes.cbox_dask.isChecked()
#print("Use Dask Reader : ", use_dask)
# open the just acquired CZI and show it inside napari viewer
if self.saved_czifilepath is not None:
open_image_stack(self.saved_czifilepath)
def open_image_stack(filepath):
""" Open a file using AICSImageIO and display it using napari
:param path: filepath of the image
:type path: str
"""
if os.path.isfile(filepath):
# remove existing layers from napari
viewer.layers.select_all()
viewer.layers.remove_selected()
# get the metadata
metadata, add_metadata = imf.get_metadata(filepath)
# add the global metadata and adapt the table display
mdbrowser.update_metadata(metadata)
mdbrowser.update_style()
use_aicsimageio = True
use_pylibczi = False
# decide which tool to use to read the image
if metadata['ImageType'] != 'czi':
use_aicsimageio = True
elif metadata['ImageType'] == 'czi' and metadata['czi_isMosaic'] is False:
use_aicsimageio = True
elif metadata['ImageType'] == 'czi' and metadata['czi_isMosaic'] is True:
use_aicsimageio = False
"""
# check if CZI has T or Z dimension
hasT = False
hasZ = False
if 'T' in metadata['dims_aicspylibczi']:
hasT = True
if 'Z' in metadata['dims_aicspylibczi']:
hasZ = True
"""
# read CZI using aicspylibczi
czi = CziFile(filepath)
# get the required shape for all and single scenes
shape_all, shape_single, same_shape = czt.get_shape_allscenes(czi, metadata)
print('Required_Array Shape for all scenes: ', shape_all)
for sh in shape_single:
print('Required Array Shape for single scenes: ', sh)
if not same_shape:
print('No all scenes have the same shape. Exiting ...')
sys.exit()
if use_aicsimageio:
# get AICSImageIO object
img = AICSImage(filepath)
try:
# check if the Dask Delayed Reader should be used
# if there is a checkbox widget
if not checkboxes.cbox_dask.isChecked():
print('Using AICSImageIO normal ImageReader.')
all_scenes_array = img.get_image_data()
if checkboxes.cbox_dask.isChecked():
print('Using AICSImageIO Dask Delayed ImageReader.')
all_scenes_array = img.get_image_dask_data()
except Exception as e:
# in case there is no checkbox widget
print(e, 'No Checkboxes found. Using AICSImageIO Dask Delayed ImageReader.')
all_scenes_array = img.get_image_dask_data()
if not use_aicsimageio and use_pylibczi is True:
# array_type = 'dask'
array_type = 'zarr'
# array_type = 'numpy'
if array_type == 'zarr':
# define array to store all channels
print('Using aicspylibCZI to read the image (ZARR array).')
all_scenes_array = zarr.create(tuple(shape_all),
dtype=metadata['NumPy.dtype'],
chunks=True)
if array_type == 'numpy':
print('Using aicspylibCZI to read the image (Numpy.Array).')
all_scenes_array = np.empty(shape_all, dtype=metadata['NumPy.dtype'])
if array_type == 'zarr' or array_type == 'numpy':
# loop over all scenes
for s in range(metadata['SizeS']):
# get the CZIscene for the current scene
single_scene = czt.CZIScene(czi, metadata, sceneindex=s)
out = czt.read_czi_scene(czi, single_scene, metadata)
all_scenes_array[s, :, :, :, :, :] = np.squeeze(out, axis=0)
print(all_scenes_array.shape)
elif array_type == 'dask':
def dask_load_sceneimage(czi, s, md):
# get the CZIscene for the current scene
single_scene = czt.CZIScene(czi, md, sceneindex=s)
out = czt.read_czi_scene(czi, single_scene, md)
return out
sp = shape_all[1:]
# create dask stack of lazy image readers
print('Using aicspylibCZI to read the image (Dask.Array) + Delayed Reading.')
lazy_process_image = dask.delayed(dask_load_sceneimage) # lazy reader
lazy_arrays = [lazy_process_image(czi, s, metadata) for s in range(metadata['SizeS'])]
dask_arrays = [
da.from_delayed(lazy_array, shape=sp, dtype=metadata['NumPy.dtype'])
for lazy_array in lazy_arrays
]
# Stack into one large dask.array
all_scenes_array = da.stack(dask_arrays, axis=0)
print(all_scenes_array.shape)
# show the actual image stack
imf.show_napari(viewer, all_scenes_array, metadata,
blending='additive',
gamma=0.85,
add_mdtable=False,
rename_sliders=True)
def get_zenfolders(zen_subfolder='Experiment Setups'):
"""Get the absolute path for a specific ZEN folder.
:param zen_subfolder: Name of a specific subfolder, defaults to 'Experiment Setups'
:type zen_subfolder: str, optional
:return: specific zensubfolder path
:rtype: str
"""
zenfolder = None
zensubfolder = None
# get the user folder
userhome = str(Path.home())
# construct the Zen Document folder and check
zenfolder = os.path.join(userhome, r'Documents\Carl Zeiss\ZEN\Documents')
if os.path.isdir(zenfolder):
zensubfolder = os.path.join(zenfolder, zen_subfolder)
if not os.path.isdir(zenfolder):
print('ZEN Folder: ' + zenfolder + 'not found')
return zensubfolder
###########################################################
show_expselect = False
show_options = False
if __name__ == "__main__":
# make sure this location is correct if you specify this
#workdir = r'C:\Users\m1srh\Documents\Zen_Output'
#workdir = r'C:\Users\m1srh\OneDrive - Carl Zeiss AG\Testdata_Zeiss'
workdir = r'c:\Temp\input'
#workdir = r'e:\tuxedo\zen_output'
if os.path.isdir(workdir):
print('SaveFolder : ', workdir, 'found.')
if not os.path.isdir(workdir):
print('SaveFolder : ', workdir, 'not found.')
# specify directly or try to discover folder automatically
# zenexpfolder = r'c:\Users\testuser\Documents\Carl Zeiss\ZEN\Documents\Experiment Setups'
# zenexpfolder = r'e:\Sebastian\Documents\Carl Zeiss\ZEN\Documents\Experiment Setups'
zenexpfolder = get_zenfolders(zen_subfolder='Experiment Setups')
# check if the ZEN experiment folder was found
expfiles_long = []
expfiles_short = []
if os.path.isdir(zenexpfolder):
print('ZEN Experiment Setups Folder :', zenexpfolder, 'found.')
# get lists with existing experiment files
expdocs = ZenDocuments()
expfiles_long, expfiles_short = expdocs.getfilenames(folder=zenexpfolder,
pattern='*.czexp')
if not os.path.isdir(zenexpfolder):
print('ZEN Experiment Setups Folder :', zenexpfolder, 'not found.')
# default for saving an CZI image after acquisition
default_cziname = 'myimage.czi'
# decide what widget to use - 'tree' or 'dialog'
# when using the FileTree one cannot navigate to higher levels
fileselect = 'dialog'
# filter for file extensions
#filter = ['*.czi', '*.ome.tiff', '*ome.tif' '*.tiff' '*.tif']
filter = ['*.czi']
# start the main application
with napari.gui_qt():
# define the parent directory
print('Image Directory : ', workdir)
# initialize the napari viewer
print('Initializing Napari Viewer ...')
viewer = napari.Viewer()
if fileselect == 'tree':
# add a FileTree widget
filetree = FileTree(filter=filter, defaultfolder=workdir)
fbwidget = viewer.window.add_dock_widget(filetree,
name='filebrowser',
area='right')
if fileselect == 'dialog':
# add a FileDialog widget
filebrowser = FileBrowser(defaultfolder=workdir)
fbwidget = viewer.window.add_dock_widget(filebrowser,
name='filebrowser',
area='right')
# create the widget elements to be added to the napari viewer
# table for the metadata and for options
mdbrowser = TableWidget()
# add the Table widget for the metadata
mdwidget = viewer.window.add_dock_widget(mdbrowser,
name='mdbrowser',
area='right')
if show_options:
checkboxes = OptionsWidget()
# add widget to activate the dask delayed reading
cbwidget = viewer.window.add_dock_widget(checkboxes,
name='checkbox',
area='bottom')
if show_expselect:
# widget to start an experiment in ZEN remotely
expselect = StartExperiment(expfiles_short,
savefolder=workdir,
default_cziname=default_cziname)
# add the Experiment Selector widget
expwidget = viewer.window.add_dock_widget(expselect,
name='expselect',
area='bottom')