Skip to content

Commit 8ad4b4d

Browse files
authored
Add files via upload
1 parent 47b46b5 commit 8ad4b4d

File tree

2 files changed

+337
-0
lines changed

2 files changed

+337
-0
lines changed

Recipes/TransformImages/Rotate2D.py

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import os
2+
import sys
3+
import ctypes
4+
import shlex
5+
import subprocess
6+
import imagecodecs
7+
import numpy as np
8+
from skimage import transform, img_as_uint, img_as_ubyte
9+
from tifffile import imread, imwrite
10+
from os.path import dirname as up
11+
12+
13+
"""
14+
Rotates a 2D image given the user-defined angle.
15+
Works only for single channels.
16+
17+
Requirements
18+
------------
19+
numpy
20+
scikit-image
21+
imagecodecs
22+
tifffile
23+
24+
Parameters
25+
----------
26+
Input channel:
27+
Input channel to be scaled.
28+
29+
Returns
30+
-------
31+
New channel in original image:
32+
Returns an empty channel.
33+
34+
New image:
35+
Opens Aivia to display the new scaled image.
36+
37+
"""
38+
39+
interpolation_mode = 1 # 0: Nearest-neighbor, 1: Bi-linear , 2: Bi-quadratic, 3: Bi-cubic, 4: Bi-quartic, 5: Bi-quintic
40+
41+
42+
# Get path to the Aivia executable
43+
def getParentDir(curr_dir, level=1):
44+
for i in range(level):
45+
parent_dir = up(curr_dir)
46+
curr_dir = parent_dir
47+
return curr_dir
48+
49+
50+
exeDir = sys.executable
51+
parentDir = getParentDir(exeDir, level=2)
52+
aivia_path = parentDir + '\\Aivia.exe'
53+
54+
55+
# [INPUT Name:inputImagePath Type:string DisplayName:'Input Channel']
56+
# [INPUT Name:resize Type:int DisplayName:'Resize image (0 = No, 1 = Yes)' Default:0 Min:0 Max:1]
57+
# [INPUT Name:rotAngle Type:int DisplayName:'Rotation Angle (-180 to 180)' Default:0 Min:0 Max:180]
58+
# [OUTPUT Name:resultPath Type:string DisplayName:'Rotated channel']
59+
def run(params):
60+
global interpolation_mode
61+
# image_org = params['EntryPoint']
62+
image_location = params['inputImagePath']
63+
result_location = params['resultPath']
64+
zCount = int(params['ZCount'])
65+
tCount = int(params['TCount'])
66+
pixel_cal_tmp = params['Calibration']
67+
pixel_cal = pixel_cal_tmp[6:].split(', ') # Expects calibration with 'XYZT: ' in front
68+
rot_angle = int(params['rotAngle']) * (-1) # 1 axis is inverted from Aivia to python
69+
do_resize = True if params['resize'] == '1' else False
70+
71+
if abs(rot_angle) > 360:
72+
error_mess = f"Error: {rot_angle} value is not appropriate as a rotating angle"
73+
ctypes.windll.user32.MessageBoxW(0, error_mess, 'Error', 0)
74+
sys.exit(error_mess)
75+
76+
# Getting XY and Z calibration values # Expecting only 'Micrometers' in this code
77+
XY_cal = float(pixel_cal[0].split(' ')[0])
78+
Z_cal = float(pixel_cal[2].split(' ')[0])
79+
80+
if not os.path.exists(image_location):
81+
print(f"Error: {image_location} does not exist")
82+
return
83+
84+
if not os.path.exists(aivia_path):
85+
print(f"Error: {aivia_path} does not exist")
86+
return
87+
88+
raw_data = imread(image_location)
89+
dims = raw_data.shape
90+
print('-- Input dimensions (expected (Z), Y, X): ', np.asarray(dims), ' --')
91+
92+
# Checking image is not 2D+t or 3D+t
93+
if len(dims) > 2 or tCount > 1:
94+
error_mess = 'Error: Image should be 2D only.'
95+
ctypes.windll.user32.MessageBoxW(0, error_mess, 'Error', 0)
96+
sys.exit(error_mess)
97+
98+
# Rotation
99+
processed_data = transform.rotate(raw_data, rot_angle, resize=do_resize, order=interpolation_mode)
100+
101+
# Formatting result array
102+
if raw_data.dtype is np.dtype('u2'):
103+
out_data = img_as_uint(processed_data)
104+
else:
105+
out_data = img_as_ubyte(processed_data)
106+
107+
if do_resize:
108+
# Defining axes for output metadata and scale factor variable
109+
axes = 'YX'
110+
meta_info = {'axes': axes, 'spacing': str(Z_cal), 'unit': 'um'} # TODO: change to TZCYX for ImageJ style???
111+
112+
# Formatting voxel calibration values
113+
inverted_XY_cal = 1 / XY_cal
114+
115+
tmp_path = result_location.replace('.tif', '-rotated.tif')
116+
print('Saving image in temp location:\n', tmp_path)
117+
imwrite(tmp_path, out_data, imagej=True, photometric='minisblack', metadata=meta_info,
118+
resolution=(inverted_XY_cal, inverted_XY_cal))
119+
120+
# Run external program
121+
cmdLine = 'start \"\" \"' + aivia_path + '\" \"' + tmp_path + '\"'
122+
123+
args = shlex.split(cmdLine)
124+
subprocess.run(args, shell=True)
125+
126+
mess = 'Rotated image with new dimensions is available in the Image Explorer'
127+
ctypes.windll.user32.MessageBoxW(0, mess, 'Process complete', 0)
128+
129+
else:
130+
imwrite(result_location, out_data)
131+
132+
133+
if __name__ == '__main__':
134+
params = {'inputImagePath': r'D:\PythonCode\_tests\2D-image.tif',
135+
'resultPath': r'D:\PythonCode\_tests\output.tif',
136+
'TCount': 1,
137+
'ZCount': 1,
138+
'Calibration': 'XYZT: 0.46 micrometers, 0.46 micrometers, 0.46 micrometers, 1 Default',
139+
'rotAngle': 15,
140+
'resize': 0}
141+
run(params)
142+
143+
# CHANGELOG
144+
# v1_00: - Including isotropic scaling and proper export to Aivia
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
# -------- Activate virtual environment -------------------------
2+
import os
3+
import ctypes
4+
import sys
5+
from pathlib import Path
6+
7+
def search_activation_path():
8+
for i in range(5):
9+
final_path = str(Path(__file__).parents[i]) + '\\env\\Scripts\\activate_this.py'
10+
if os.path.exists(final_path):
11+
return final_path
12+
return ''
13+
14+
activate_path = search_activation_path()
15+
if os.path.exists(activate_path):
16+
exec(open(activate_path).read(), {'__file__': activate_path})
17+
print(f'Aivia virtual environment activated\nUsing python: {activate_path}')
18+
else:
19+
error_mess = f'Error: {activate_path} was not found.\n\nPlease check that:\n' \
20+
f' 1/ The \'FirstTimeSetup.py\' script was already run in Aivia,\n' \
21+
f' 2/ The current python recipe is in one of the "\\PythonEnvForAivia\\" subfolders.'
22+
ctypes.windll.user32.MessageBoxW(0, error_mess, 'Error', 0)
23+
sys.exit(error_mess)
24+
# ---------------------------------------------------------------
25+
26+
import shlex
27+
import subprocess
28+
import imagecodecs
29+
import numpy as np
30+
from skimage import transform, img_as_uint, img_as_ubyte
31+
from tifffile import imread, imwrite
32+
from os.path import dirname as up
33+
from magicgui import magicgui
34+
35+
36+
"""
37+
Scales the input channel up or down (isotropic factor) and rotates the volume 90 degrees around one axis (not centered).
38+
Works only for 3D (not timelapses) and for single channels.
39+
40+
Requirements
41+
------------
42+
numpy
43+
scikit-image
44+
imagecodecs
45+
tifffile
46+
47+
Parameters
48+
----------
49+
Input channel:
50+
Input channel to be scaled.
51+
52+
Returns
53+
-------
54+
New channel in original image:
55+
Returns an empty channel.
56+
57+
New image:
58+
Opens Aivia to display the new scaled image.
59+
60+
"""
61+
62+
axis_rot_options = {'ClockWise': {'X': (1, 0), 'Y': (0, 2), 'Z': (2, 1)},
63+
'CounterClockWise': {'X': (0, 1), 'Y': (2, 0), 'Z': (1, 2)}}
64+
interpolation_mode = 1 # 0: Nearest-neighbor, 1: Bi-linear , 2: Bi-quadratic, 3: Bi-cubic, 4: Bi-quartic, 5: Bi-quintic
65+
66+
67+
# Get path to the Aivia executable
68+
def getParentDir(curr_dir, level=1):
69+
for i in range(level):
70+
parent_dir = up(curr_dir)
71+
curr_dir = parent_dir
72+
return curr_dir
73+
74+
75+
exeDir = sys.executable
76+
parentDir = getParentDir(exeDir, level=2)
77+
aivia_path = parentDir + '\\Aivia.exe'
78+
79+
80+
# [INPUT Name:inputImagePath Type:string DisplayName:'Input Channel']
81+
# [OUTPUT Name:resultPath Type:string DisplayName:'Duplicate of input']
82+
def run(params):
83+
global axis_rot_options, interpolation_mode
84+
# image_org = params['EntryPoint']
85+
image_location = params['inputImagePath']
86+
result_location = params['resultPath']
87+
zCount = int(params['ZCount'])
88+
tCount = int(params['TCount'])
89+
pixel_cal_tmp = params['Calibration']
90+
pixel_cal = pixel_cal_tmp[6:].split(', ') # Expects calibration with 'XYZT: ' in front
91+
92+
# Getting XY and Z calibration values # Expecting only 'Micrometers' in this code
93+
XY_cal = float(pixel_cal[0].split(' ')[0])
94+
Z_cal = float(pixel_cal[2].split(' ')[0])
95+
Z_ratio = float(Z_cal) / float(XY_cal)
96+
97+
if not os.path.exists(image_location):
98+
print(f"Error: {image_location} does not exist")
99+
return
100+
101+
if not os.path.exists(aivia_path):
102+
print(f"Error: {aivia_path} does not exist")
103+
return
104+
105+
raw_data = imread(image_location)
106+
dims = raw_data.shape
107+
print('-- Input dimensions (expected (Z), Y, X): ', np.asarray(dims), ' --')
108+
109+
# Checking image is not 2D+t or 3D+t
110+
if len(dims) != 3 or (len(dims) == 3 and tCount > 1):
111+
print('Error: Image should be XYZ only.')
112+
return
113+
114+
# Scale image to be isotropic
115+
final_cal = XY_cal
116+
scale_factor_xy = 1
117+
scale_factor_z = 1
118+
if Z_ratio > 1:
119+
scale_factor_z = Z_ratio
120+
elif Z_ratio < 1:
121+
scale_factor_xy = 1 / Z_ratio
122+
final_cal = Z_cal
123+
124+
if abs(Z_ratio - 1) > 0.001:
125+
print('-- Rescaling image as XY and Z calibration are different')
126+
final_scale = (scale_factor_z, scale_factor_xy, scale_factor_xy)
127+
iso_data = transform.rescale(raw_data, final_scale, interpolation_mode)
128+
else:
129+
iso_data = raw_data
130+
131+
# GUI to choose rotation axis
132+
swap_axes_options = axis_rot_options['ClockWise']
133+
@magicgui(axis={"label": "Select the rotation axis:", "widget_type": "RadioButtons",
134+
'choices': swap_axes_options.keys()},
135+
direction={"label": "Select the rotation direction:", "widget_type": "RadioButtons",
136+
'choices': axis_rot_options.keys()},
137+
call_button="Run")
138+
def get_rot_param(axis=list(swap_axes_options.keys())[0], direction=list(axis_rot_options.keys())[0]):
139+
pass
140+
141+
@get_rot_param.called.connect
142+
def close_GUI_callback():
143+
get_rot_param.close()
144+
145+
get_rot_param.show(run=True)
146+
rot_axis = get_rot_param.axis.value
147+
rot_dir = get_rot_param.direction.value
148+
149+
# Rotation
150+
rot_axes = axis_rot_options[rot_dir][rot_axis]
151+
processed_data = np.rot90(iso_data, axes=rot_axes)
152+
153+
# Formatting result array
154+
if raw_data.dtype is np.dtype('u2'):
155+
out_data = img_as_uint(processed_data)
156+
else:
157+
out_data = img_as_ubyte(processed_data)
158+
159+
# Defining axes for output metadata and scale factor variable
160+
axes = 'ZYX'
161+
meta_info = {'axes': axes, 'spacing': str(final_cal), 'unit': 'um'}
162+
163+
# Formatting voxel calibration values
164+
inverted_XY_cal = 1 / final_cal
165+
166+
tmp_path = result_location.replace('.tif', '-rotated.tif')
167+
print('Saving image in temp location:\n', tmp_path)
168+
imwrite(tmp_path, out_data, imagej=True, photometric='minisblack', metadata=meta_info,
169+
resolution=(inverted_XY_cal, inverted_XY_cal))
170+
171+
# Dummy save
172+
# dummy_data = np.zeros(image_data.shape, dtype=image_data.dtype)
173+
# imwrite(result_location, dummy_data)
174+
175+
# Run external program
176+
cmdLine = 'start \"\" \"' + aivia_path + '\" \"' + tmp_path + '\"'
177+
178+
args = shlex.split(cmdLine)
179+
subprocess.run(args, shell=True)
180+
181+
182+
if __name__ == '__main__':
183+
params = {'inputImagePath': r'D:\PythonCode\_tests\3D_Embryo_Fluo-IsotropicCube-8b.aivia.tif',
184+
'resultPath': r'D:\PythonCode\_tests\output.tif',
185+
'TCount': 1,
186+
'ZCount': 192,
187+
'Calibration': 'XYZT: 0.46 micrometers, 0.46 micrometers, 0.46 micrometers, 1 Default'}
188+
run(params)
189+
190+
# CHANGELOG
191+
# v1_00: - Including isotropic scaling and proper export to Aivia
192+
# v1_10: - Adding a GUI to choose rotation axis + virtual environment activation
193+
# v1_11: - New virtual env code for auto-activation

0 commit comments

Comments
 (0)