Spaces:
Sleeping
Sleeping
File size: 10,931 Bytes
b899ae9 1922800 b899ae9 f6173bc b899ae9 1922800 b899ae9 1922800 b899ae9 1922800 b899ae9 1922800 b899ae9 f6173bc b899ae9 f6173bc b899ae9 f6173bc b899ae9 f6173bc b899ae9 f6173bc b899ae9 f6173bc b899ae9 |
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 |
""" ----------------------------------------
* Creation Time : Tue Jun 13 14:18:20 2023
* Author : Charles N. Christensen
* Github : github.com/charlesnchr
----------------------------------------"""
import streamlit as st
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import numpy as np
from skimage import io
import tifffile as tiff
import os
# Function to draw a rectangle given four points
def draw_rectangle(points, ax):
# Compute the centroid of the points
centroid = np.mean(points, axis=0)
# Compute the width and height
width = np.linalg.norm(points[1] - points[0])
height = np.linalg.norm(points[2] - points[1])
# Compute the angle
angle = np.arctan2(points[1, 1] - points[0, 1], points[1, 0] - points[0, 0])
# Create the rectangle
rect = patches.Rectangle(
centroid,
width,
height,
angle=np.degrees(angle),
linewidth=1,
edgecolor="gray",
facecolor="none",
transform=ax.transData,
)
# Add the rectangle
ax.add_patch(rect)
ax.set_aspect("equal")
def draw_rectangle_from_coords(ax, ridx, cidx):
l = np.sqrt(2) / 2 # unit length
o = 8 # origin (x,y)
shift = 1 if ridx % 2 == 0 else 0
x1 = o - 2 * l * cidx + shift * l
y1 = o - l * ridx
x2 = o - l - 2 * l * cidx + shift * l
y2 = o + l - l * ridx
x3 = o - 2 * l * cidx + shift * l
y3 = o + 2 * l - l * ridx
x4 = o + l - 2 * l * cidx + shift * l
y4 = o + l - l * ridx
# apply shift based on row and column
# Parse points
points = np.array([x1, y1, x2, y2, x3, y3, x4, y4]).reshape(-1, 2)
draw_rectangle(points, ax)
# annotate ridx and cidx in the center of the rectangle
ax.annotate(
f"{ridx},{cidx}",
xy=(x2, (y1 + y3) / 2),
horizontalalignment="center",
verticalalignment="center",
color="orange",
)
# return min max x and y
return (
min(x1, x2, x3, x4),
max(x1, x2, x3, x4),
min(y1, y2, y3, y4),
max(y1, y2, y3, y4),
)
def plot_patch(patch):
fig, ax = plt.subplots()
bounds_list = []
# iterate through patch and render if pixel is 1
for row in range(patch.shape[0]):
for col in range(patch.shape[1]):
if patch[row, col] > 100:
bounds = draw_rectangle_from_coords(ax, row, col)
print(bounds)
bounds_list.append(bounds)
# Get the min and max x and y values
xmin = min([b[0] for b in bounds_list])
xmax = max([b[1] for b in bounds_list])
ymin = min([b[2] for b in bounds_list])
ymax = max([b[3] for b in bounds_list])
# Set the limits of the plot
plt.xlim(xmin - 2, xmax + 1)
plt.ylim(ymin - 1, ymax + 1)
return fig, xmin, xmax, ymin, ymax
def DMDPixelTransform(input_img, dmdMapping, xoffset=0, yoffset=0):
# Initialize an array of zeros with same size as the input image
transformed_img = np.zeros_like(input_img)
# Get the dimensions of the input image
rows, cols = input_img.shape
# Iterate over the pixels of the input image
for i in range(rows):
for j in range(cols):
# Calculate the new coordinates for the pixel
ip = i + yoffset
jp = j + xoffset
# Apply the dmdMapping transformation if set
if dmdMapping > 0:
transformed_i = jp + ip - 2
transformed_j = (jp - ip + 4) // 2
else:
transformed_i = ip
transformed_j = jp
# If the new coordinates are within the bounds of the image, copy the pixel value
if 0 <= transformed_i < rows and 0 <= transformed_j < cols:
transformed_img[transformed_i, transformed_j] = input_img[i, j]
# Return the transformed image
return transformed_img
# Streamlit app
st.title("ONI DMD emulator")
# add author info and context
st.markdown("Charles N. Christensen, ONI — 2023/06/14")
tabs = st.tabs(
[
"Basic rendering",
"Pregenerated pattern with padding",
"Manual pattern definition",
]
)
with tabs[0]:
st.header("Render DMD layout")
# Create a new figure
fig, ax = plt.subplots()
# streamlit input for number of columns and rows
cols = st.number_input("Number of columns", min_value=1, max_value=20, value=5)
rows = st.number_input("Number of rows", min_value=1, max_value=20, value=5)
bounds_list = []
for col in range(cols):
for row in range(rows):
bounds = draw_rectangle_from_coords(ax, row, col)
bounds_list.append(bounds)
# Get the min and max x and y values
xmin = min([b[0] for b in bounds_list])
xmax = max([b[1] for b in bounds_list])
ymin = min([b[2] for b in bounds_list])
ymax = max([b[3] for b in bounds_list])
# Set the limits of the plot
plt.xlim(xmin - 2, xmax + 1)
plt.ylim(ymin - 1, ymax + 1)
fig.set_size_inches(cols + 0.5, rows + 0.5)
st.pyplot(fig)
with tabs[1]:
st.subheader("Upload pregenerated pattern or use default")
# upload pattern image
pattern = st.file_uploader("Upload pattern image", type=["tif", "tiff"])
# fallback
if pattern is None:
option = st.selectbox(
"Select pattern",
(
"patterns_spotSize_2_Nspots_5_dmdMapping_1.tif",
"patterns_spotSize_5_Nspots_20_dmdMapping_1.tif",
"patterns_spotSize_1_Nspots_5_dmdMapping_1.tif",
"patterns_pixelsize_ratio_1_k2_200_func_square_wave_one_third_dmdMapping_1.tif",
"patterns_pixelsize_ratio_1_k2_200_func_square_wave_one_third_dmdMapping_0.tif",
"patterns_pixelsize_ratio_1_k2_80_func_square_wave_one_third_dmdMapping_1.tif",
"patterns_pixelsize_ratio_1_k2_80_func_square_wave_one_third_dmdMapping_0.tif",
"patterns_pixelsize_ratio_1_k2_20_func_square_wave_one_third_dmdMapping_1.tif",
"patterns_pixelsize_ratio_1.8_k2_200_func_square_wave_one_third.tif",
"patterns_pixelsize_ratio_1.8_k2_150_func_square_wave_one_third.tif",
"patterns_pixelsize_ratio_1.8_k2_110_func_square_wave_one_third.tif",
"patterns_pixelsize_ratio_1.6_k2_80.tif",
),
)
cur_dir = os.path.dirname(os.path.abspath(__file__))
def_pattern = f"{cur_dir}/patterns/{option}"
img = io.imread(def_pattern)
st.markdown("""**No pattern uploaded**: Loading selected default image.""")
st.markdown(
"Note that `pixelsize_ratio > 1` and `_dmdMapping_0` indicate an assumption of ortholinear grid (no DMD layout correction). The `_dmdMapping_1` patterns are corrected for DMD."
)
else:
print("loading image", pattern)
img = tiff.imread(pattern)
st.subheader("Parameters")
cols = st.columns(2)
# streamlit number input for padding
with cols[0]:
x_padding = st.number_input(
"Vertical padding", min_value=0, max_value=10, value=0
)
with cols[1]:
y_padding = st.number_input(
"Horizontal padding", min_value=0, max_value=10, value=0
)
# streamlit number input for global offset
cols = st.columns(2)
with cols[0]:
x_offset_img = st.number_input(
"Vertical image offset (change to test near edges of DMD)",
min_value=0,
max_value=30,
value=10,
)
with cols[1]:
y_offset_img = st.number_input(
"Horizontal image offset (change to test near edges of DMD)",
min_value=0,
max_value=30,
value=20,
)
cols = st.columns(2)
with cols[0]:
rows_img = st.number_input(
"Number of rows", min_value=5, max_value=100, value=20
)
with cols[1]:
cols_img = st.number_input(
"Number of columns", min_value=5, max_value=100, value=20
)
cols = st.columns(2)
with cols[0]:
frame_idx = st.number_input(
"Plot frame index start", min_value=0, max_value=400, value=0
)
with cols[1]:
frame_count = st.number_input(
"Plot frame index range", min_value=1, max_value=5, value=2
)
global_bounds = None
# plot patches in two frames
for i in range(frame_idx, frame_idx + frame_count):
patch = img[
i,
y_offset_img : y_offset_img + rows_img,
x_offset_img : x_offset_img + cols_img,
]
# add 1pixel padding in top to patch
patch = np.pad(
patch, ((x_padding, 0), (y_padding, 0)), "constant", constant_values=0
)
fig, xmin, xmax, ymin, ymax = plot_patch(patch)
if global_bounds is None:
global_bounds = xmin, xmax, ymin, ymax
xmin, xmax, ymin, ymax = global_bounds
plt.xlim(xmin - 2, xmax + 1)
plt.ylim(ymin - 2, ymax + 1)
fig.set_size_inches(xmax - xmin + 4, ymax - ymin + 4)
st.subheader(f"Frame {i}")
st.pyplot(fig)
with tabs[2]:
# 5x5 array of st checkboxes
st.subheader("Select pixels to turn on in tilted coordinate system")
grid = np.zeros((5, 5))
for i in range(5):
cols = st.columns(5)
for j in range(5):
with cols[j]:
# set value based on i,j = 0,0 1,0 0,1 1,1
value = False
if i == 1 or i == 2:
if j == 1 or j == 2:
value = True
grid[i, j] = st.checkbox(f"{i},{j}", value=value, key=f"{i},{j}")
grid = 255 * grid
# streamlit number input for global offset
x_offset = st.number_input(
"Vertical global offset (change to test near edges of DMD)",
min_value=0,
max_value=30,
value=10,
)
y_offset = st.number_input(
"Horizontal global offset (change to test near edges of DMD)",
min_value=0,
max_value=30,
value=20,
)
global_bounds = None
# plot with offsets of 1px in x and y
for xoffset in range(0, 2):
for yoffset in range(0, 2):
patch = np.zeros((40, 40))
patch[x_offset : x_offset + 5, y_offset : y_offset + 5] = grid
patch = DMDPixelTransform(patch, 1, xoffset, yoffset)
fig, xmin, xmax, ymin, ymax = plot_patch(patch)
if global_bounds is None:
global_bounds = xmin, xmax, ymin, ymax
xmin, xmax, ymin, ymax = global_bounds
plt.xlim(xmin - 2, xmax + 1)
plt.ylim(ymin - 2, ymax + 1)
fig.set_size_inches(xmax - xmin + 4, ymax - ymin + 4)
st.subheader(f"Frame with offset x={xoffset} and y={yoffset}")
st.pyplot(fig)
|