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) | |