Spaces:
Sleeping
Sleeping
""" ---------------------------------------- | |
* 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) | |