Skip to content

Commit 8efeca5

Browse files
authored
Add files via upload
1 parent 8fecbf3 commit 8efeca5

1 file changed

Lines changed: 193 additions & 0 deletions

File tree

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
import os.path
2+
import ctypes
3+
import shlex
4+
import subprocess
5+
import imagecodecs
6+
import numpy as np
7+
from tifffile import imread, imwrite
8+
from skimage import transform, img_as_uint, img_as_ubyte
9+
import sys
10+
11+
"""
12+
Scales the input channel up or down depending on StarDist reference object diameter.
13+
Option for interpolation is in the code.
14+
Works only for 2D/3D rescaling (not timelapses) but can be applied on a per timepoint basis.
15+
Works for single channels.
16+
17+
Documentation
18+
-------------
19+
https://scikit-image.org/docs/stable/api/skimage.transform.html#skimage.transform.rescale
20+
21+
Requirements
22+
------------
23+
numpy (comes with Aivia installer)
24+
scikit-image (comes with Aivia installer)
25+
imagecodecs (comes with Aivia installer)
26+
tifffile (comes with Aivia installer)
27+
28+
Parameters
29+
----------
30+
Input channel:
31+
Input channel to be scaled.
32+
33+
Returns
34+
-------
35+
New channel in original image:
36+
Returns an empty channel.
37+
38+
New image:
39+
Opens Aivia to display the new scaled image.
40+
41+
"""
42+
43+
ref_stardist_diameter = 15 # in Pixels (empirical value based on 1 segmentation result taking average diameter)
44+
conversion_threshold = 0.2 # difference between the scaling factor and 1, to see if it's worth doing the scaling
45+
46+
interpolation_mode = 1 # 0: Nearest-neighbor, 1: Bi-linear , 2: Bi-quadratic, 3: Bi-cubic, 4: Bi-quartic, 5: Bi-quintic
47+
48+
IJTimeUnit = {'Minutes': 'min', 'Seconds': 's', 'Milliseconds': 'ms', 'Microseconds': 'us'}
49+
50+
51+
# [INPUT Name:inputImagePath Type:string DisplayName:'Input Channel']
52+
# [INPUT Name:performZscaling Type:int DisplayName:'Perform Z scaling (1=Yes)' Default:1 Min:0 Max:1]
53+
# [INPUT Name:typicalObjDiam Type:double DisplayName:'Typical Object Diameter' Default:1.0 Min:0.001 Max:1000.0]
54+
# [OUTPUT Name:resultPath Type:string DisplayName:'Duplicate of input']
55+
def run(params):
56+
image_org = params['EntryPoint']
57+
image_location = params['inputImagePath']
58+
result_location = params['resultPath']
59+
typical_diameter = float(params['typicalObjDiam'])
60+
perform_z_scaling = int(params['performZscaling'])
61+
zCount = int(params['ZCount'])
62+
tCount = int(params['TCount'])
63+
pixel_cal_tmp = params['Calibration']
64+
pixel_cal = pixel_cal_tmp[6:].split(', ') # Expects calibration with 'XYZT: ' in front
65+
aivia_path = params['CallingExecutable']
66+
67+
if typical_diameter == 0.0:
68+
error_mess = 'Error: typical diameter value was not provided.'
69+
ctypes.windll.user32.MessageBoxW(0, error_mess, 'Error', 0)
70+
sys.exit(error_mess)
71+
72+
# Getting XY and Z values # Expecting only 'Micrometers' in this code
73+
XY_cal = float(pixel_cal[0].split(' ')[0])
74+
Z_cal = float(pixel_cal[2].split(' ')[0])
75+
T_cal = float(pixel_cal[3].split(' ')[0])
76+
77+
# Check real calibration
78+
real_XYZ_calibration, real_T_calibration = False, False
79+
if not 'efault' in pixel_cal[0].split(' ')[1]: # calibration ok
80+
real_XYZ_calibration = True
81+
if not 'efault' in pixel_cal[3].split(' ')[1]: # calibration ok
82+
real_T_calibration = True
83+
84+
if not os.path.exists(image_location):
85+
print(f"Error: {image_location} does not exist")
86+
return
87+
88+
image_data = imread(image_location)
89+
dims = image_data.shape
90+
print('-- Input dimensions (expected (T) (Z), Y, X): ', np.asarray(dims), ' --')
91+
92+
# Adjust typical diameter to pixel value
93+
if real_XYZ_calibration:
94+
typical_diameter /= XY_cal
95+
96+
# Calculate scale factor depending on StarDist reference
97+
scale_factor_xy = ref_stardist_diameter / typical_diameter
98+
99+
# Aborting the scaling if useless
100+
if abs(scale_factor_xy - 1) < conversion_threshold:
101+
mess = 'Scaling cancelled: scaling factor ({}) is close ' \
102+
'to 1 so StarDist can be run directly on raw image'.format(scale_factor_xy)
103+
ctypes.windll.user32.MessageBoxW(0, mess, 'Scaling cancelled', 0)
104+
sys.exit(mess)
105+
106+
# Showing scale factor and printing in log for backward scaling (important for multichannel images)
107+
mess = 'Calculated scaling factor to remember for backward conversion: {:.3f}.\n\n' \
108+
'Value is also available in the log (Help menu > Open log) where you can search for ' \
109+
'"Scaling factor for StarDist".'.format(scale_factor_xy)
110+
ctypes.windll.user32.MessageBoxW(0, mess, 'Scaling factor to remember', 0)
111+
print(mess)
112+
113+
# Z scaling factor
114+
scale_factor_z = scale_factor_xy if perform_z_scaling else 1
115+
116+
# Calculating final pixel calibration
117+
final_XY_cal = XY_cal / scale_factor_xy if real_XYZ_calibration else 1
118+
final_Z_cal = Z_cal / scale_factor_z if real_XYZ_calibration else 1
119+
120+
# Defining axes for output metadata and scale factor variable
121+
final_scale = None
122+
axes = ''
123+
if tCount > 1 and zCount > 1: # 3D + T
124+
axes = 'TZYX'
125+
final_scale = (1, scale_factor_z, scale_factor_xy, scale_factor_xy)
126+
127+
elif tCount > 1 and zCount == 1: # 2D + T
128+
axes = 'TYX'
129+
final_scale = (1, scale_factor_xy, scale_factor_xy)
130+
131+
elif tCount == 1 and zCount > 1: # 3D
132+
axes = 'ZYX' # should be 'YXZ'
133+
final_scale = (scale_factor_z, scale_factor_xy, scale_factor_xy)
134+
135+
elif tCount == 1 and zCount == 1: # 2D
136+
axes = 'YX'
137+
final_scale = scale_factor_xy
138+
139+
scaled_img = transform.rescale(image_data, final_scale, interpolation_mode)
140+
141+
# Formatting result array
142+
if image_data.dtype is np.dtype('u2'):
143+
out_data = img_as_uint(scaled_img)
144+
print('img_as_uint')
145+
else:
146+
out_data = img_as_ubyte(scaled_img)
147+
print('img_as_ubyte')
148+
149+
tmp_path = result_location.replace('.tif', '-scaled.tif')
150+
meta_info = {'axes': axes}
151+
if real_XYZ_calibration and zCount > 1:
152+
meta_info.update({'spacing': str(final_Z_cal), 'unit': 'um'})
153+
if real_T_calibration:
154+
meta_info.update({'TimeIncrement': T_cal, 'TimeIncrementUnit': IJTimeUnit[pixel_cal[3].split(' ')[1]]})
155+
156+
# Formatting voxel calibration values
157+
inverted_XY_cal = 1 / final_XY_cal
158+
print(final_XY_cal)
159+
160+
print('Saving image in temp location:\n', tmp_path)
161+
if real_XYZ_calibration:
162+
imwrite(tmp_path, out_data, imagej=True, photometric='minisblack', metadata=meta_info,
163+
resolution=(inverted_XY_cal, inverted_XY_cal))
164+
else:
165+
# To avoid calibration in XYZ
166+
imwrite(tmp_path, out_data, imagej=True, photometric='minisblack', metadata=meta_info)
167+
168+
# Dummy save
169+
dummy_data = np.zeros(image_data.shape, dtype=image_data.dtype)
170+
imwrite(result_location, dummy_data)
171+
172+
# Run external program
173+
cmdLine = 'start \"\" \"' + aivia_path + '\" \"' + tmp_path + '\"'
174+
175+
args = shlex.split(cmdLine)
176+
subprocess.run(args, shell=True)
177+
178+
179+
if __name__ == '__main__':
180+
params = {'inputImagePath': 'D:\\PythonCode\\_tests\\3D-TL-toalign.aivia.tif',
181+
'resultPath': 'D:\\PythonCode\\_tests\\Output.tif',
182+
'TCount': 16,
183+
'ZCount': 41,
184+
'Calibration': 'XYZT: 0.4 Micrometers, 0.4 Micrometers, 1.2 Micrometers, 599.9996 Seconds',
185+
'scaleFactorXY': 2,
186+
'scaleFactorZ': 1,
187+
'scaleDirection': 0,
188+
'EntryPoint': '',
189+
'CallingExecutable': ''}
190+
run(params)
191+
192+
# CHANGELOG
193+
# v1_00: - Comes from ScaleImage_1_30_noGUI_IJstyle.py

0 commit comments

Comments
 (0)