-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbasic_batch.py
More file actions
226 lines (186 loc) · 8.76 KB
/
Copy pathbasic_batch.py
File metadata and controls
226 lines (186 loc) · 8.76 KB
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
import numpy as np
from PIL import Image
from pathlib import Path
import tiffile as tf
import time
import cv2
import napari
import os
import PySimpleGUI as sg
from datetime import datetime
from apeer_ometiff_library import io, processing, omexmlClass
import zarr
import xmltodict
import webbrowser
# 16>8
def map_uint16_to_uint8(img, lower_bound=None, upper_bound=None):
'''
Map a 16-bit image trough a lookup table to convert it to 8-bit.
Parameters
----------
img: numpy.ndarray[np.uint16]
image that should be mapped
lower_bound: int, optional
lower bound of the range that should be mapped to ``[0, 255]``,
value must be in the range ``[0, 65535]`` and smaller than `upper_bound`
(defaults to ``numpy.min(img)``)
upper_bound: int, optional
upper bound of the range that should be mapped to ``[0, 255]``,
value must be in the range ``[0, 65535]`` and larger than `lower_bound`
(defaults to ``numpy.max(img)``)
Returns
-------
numpy.ndarray[uint8]
'''
if lower_bound is None:
lower_bound = np.min(img)
if upper_bound is None:
upper_bound = np.max(img)
if lower_bound >= upper_bound:
raise ValueError(
'"lower_bound" must be smaller than "upper_bound"')
lut = np.concatenate([
np.zeros(lower_bound, dtype=np.uint16),
np.linspace(0, 255, upper_bound - lower_bound).astype(np.uint16),
np.ones(2**16 - upper_bound, dtype=np.uint16) * 255
])
return lut[img].astype(np.uint8)
# text popup
def popup_text(filename, text):
layout = [
[sg.Multiline(text, size=(80, 25)),],
]
win = sg.Window('Metadata', layout, modal=True, finalize=True)
while True:
event, values = win.read()
if event == sg.WINDOW_CLOSED:
break
win.close()
# Theme color
sg.theme('LightGrey1')
# flayout sizes
fs_w = 20
fs_h = 1
# symbols
SYMBOL_UP = '▲'
SYMBOL_DOWN = '▼'
# collapse function
def collapse(layout, key):
"""
Helper function that creates a Column that can be later made hidden, thus appearing "collapsed"
:param layout: The layout for the section
:param key: Key used to make this seciton visible / invisible
:return: A pinned column that can be placed directly into your layout
:rtype: sg.pin
"""
return sg.pin(sg.Column(layout, key=key))
# extra meta data window
optional_meta = [
[sg.Text('Name', size =(fs_w, fs_h)), sg.InputText(default_text=None,key='-Name-')],
[sg.Text('Description', size =(fs_w, fs_h)), sg.InputText(default_text=None,key='-Description-')],
[sg.Text('Acquisition date', size =(fs_w, fs_h)), sg.InputText(key='-AcquisitionDate-')],
#default_text=datetime.today().strftime('%Y-%m-%d-%H:%M:%S')
]
# layout
layout = [
[sg.Text('Choose a folder:')],
[sg.Input(key='-folder path-',enable_events=True),sg.FolderBrowse(target='-folder path-',initial_folder=os.getcwd())],
[sg.HorizontalSeparator()],
[sg.Text('Enter pyramidal OME-TIFF parameters:')],
[sg.Text('Downsample factor:', size =(fs_w, fs_h)), sg.InputText(default_text='2',key='-Downsample-')],
[sg.Text('Number of levels:', size =(fs_w, fs_h)), sg.InputText(default_text='5',key='-levels-')],
[sg.Text('Tile size (multiple of 16):', size =(fs_w, fs_h)), sg.InputText(default_text='256',key='-tile size-')],
[sg.Text('Compression:',size =(fs_w, fs_h)),sg.Combo(['Uncompressed','jpeg'],default_value='Uncompressed',key='-Compress-')],
[sg.HorizontalSeparator()],
[sg.Text(SYMBOL_DOWN, enable_events=True, key='-open extra-'),sg.Text('Additional OME-TIFF metadata', enable_events=True, k='-open extra text-')],[collapse(optional_meta, '-extra-')],
[sg.Submit(), sg.Cancel()],
[sg.Text('nanotomy.org', click_submits=True,enable_events=True,key='-nanotomy-',font=("Helvetica", 10), text_color='black')]
]
# initialize extra section state
opened_extra = False
# get user entered values
window = sg.Window('RAW to OME-TIFF BATCH', layout)
while True:
event, values = window.read()
if event == sg.WINDOW_CLOSED or event == 'Cancel':
break
elif event == '-nanotomy-':
webbrowser.open('http://nanotomy.org')
elif event.startswith('-open extra-'):
opened_extra = not opened_extra
window['-open extra-'].update(SYMBOL_DOWN if opened_extra else SYMBOL_UP)
window['-extra-'].update(visible=opened_extra)
elif event == 'Submit':
batch_folder_path = values['-folder path-']
for image_name in os.listdir(batch_folder_path):
if not image_name.startswith('.'):
# time the conversion and start it
start_time_1 = time.time()
image_folder_path = os.path.join(batch_folder_path,image_name)
image_path = os.path.join(image_folder_path,image_name+'.raw')
xml_path = os.path.join(image_folder_path,image_name+'.xml')
output_path = os.path.join(image_folder_path,image_name+'.ome.tiff')
# input file (RAW) and output
input = image_path
output = output_path
# Parse XML for metadata
with open(xml_path) as fd:
dic = xmltodict.parse(fd.read())
auto_width = int(dic['RawExport']['Width'])
auto_height = int(dic['RawExport']['Height'])
auto_pixelsize = float(dic['RawExport']['PixelSize']['Value'])
auto_pixelsizeunit = dic['RawExport']['PixelSize']['Unit']
auto_bitdepth = int(dic['RawExport']['BitPerSample'])
# read raw file
ROWS = auto_height
COLS = auto_width
fin = open(input)
# Loading the input image
if auto_bitdepth == 8:
image = np.fromfile(fin, dtype = np.uint8, count = ROWS*COLS)
elif auto_bitdepth == 16:
image = np.fromfile(fin, dtype = np.uint16, count = ROWS*COLS)
# Conversion from 1D to 2D array
image.shape = (image.size // COLS, COLS)
end_time_1 = time.time()
##################################### OME TIFF #########################################
# parameters
downsample_factor = int(values['-Downsample-']) # downsample factor of pyramid levels
lvl = int(values['-levels-']) # pyramid levels
tile_size = int(values['-tile size-']) # Multiples of 16
compression = None if values['-Compress-'] == 'Uncompressed' else values['-Compress-']
# get pixel size
pixunit = auto_pixelsizeunit
unit_factor = 1e-6 if pixunit == 'µm' else 1e-9 if pixunit == 'nm' else 1
pixel_size = auto_pixelsize*unit_factor
pixel_size_x = pixel_size
pixel_size_y = pixel_size
pixel_size_z = pixel_size
# Write tiff file
start_time_2 = time.time()
with tf.TiffWriter(output, bigtiff=True) as tif:
# use tiles and JPEG compression.
options = {'tile': (tile_size, tile_size),
'compress': compression,
'metadata':{'PhysicalSizeX': pixel_size_x*1e9, 'PhysicalSizeXUnit': 'nm',
'PhysicalSizeY': pixel_size_y*1e9, 'PhysicalSizeYUnit': 'nm',
'axes': 'YX','Description':values['-Description-'],
'AcquisitionDate':values['-AcquisitionDate-'],
'Name':values['-Name-']}}
# save the base image (the original resolution)
tif.write(image, subifds=lvl, **options)
# iteratively generate and save the pyramid levels to the SubIFDs
image2 = image
for _ in range(lvl):
image2 = cv2.resize(
image2,
(image2.shape[1] // downsample_factor, image2.shape[0] // downsample_factor),
interpolation=cv2.INTER_LINEAR
)
tif.write(image2, **options)
# end timer and print info
end_time_2 = time.time()
size_gb = os.path.getsize(output)*10e-10
print('Image %s was converted in %02d seconds. File size: %.2f GB.' % (image_name,end_time_2-start_time_1,size_gb))
#sg.popup('Image %s in %02d seconds. File size: %.2f GB.' % (image_name,end_time_2-start_time_1,size_gb))
window.close()