在grok的帮助下利用plotly打造了一个简单的双通道成像预览函数。这样可以方便集成到数据分析工作流程中,以便实时交互以快速检验情况。
效果如下:
需要注意的是:
- 图像数据会自动转换为 8-bit
- 第一个通道默认使用红色,第二个默认绿色伪彩
- 对比度方面仅支持通过滑块调整显示的最大值,并且按百分比来
- 可以对任意通道或merge子图区域进行同步的zoom in/out
代码如下:
from skimage import io
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import numpy as np
def visualize_channels(ch1, ch2, output_file="channel_visualization.html"):
"""
Visualize two grayscale images (ch1 and ch2) and their RGB merged image in a single row with three subplots.
ch1 uses red colormap, ch2 uses green colormap, merged image uses ch1 as R, ch2 as G, B=0.
Non-8-bit inputs are linearly converted to 8-bit based on dtype.
A single slider controls zmax for all subplots (mapped from 1-100% to 0-255), with synchronized zooming and 1:1 scale.
Subplots are displayed with the same aspect ratio and dimensions as the input images.
Tight layout and no axis background for clean display.
Parameters:
ch1 (np.array): Grayscale image for channel 1 (R in merged image)
ch2 (np.array): Grayscale image for channel 2 (G in merged image)
output_file (str): Path to save the output HTML file
"""
# Convert inputs to 8-bit if not already
def to_uint8(arr):
arr = np.asarray(arr)
if arr.dtype == np.uint8:
return arr
# Linearly scale to 0-255 based on dtype min and max
dtype_info = np.iinfo(arr.dtype) if np.issubdtype(arr.dtype, np.integer) else np.finfo(arr.dtype)
arr_min, arr_max = dtype_info.min, dtype_info.max
arr = (arr - arr_min) / (arr_max - arr_min) * 255
return np.clip(arr, 0, 255).astype(np.uint8)
ch1 = to_uint8(ch1)
ch2 = to_uint8(ch2)
# Get image dimensions
img_height, img_width = ch1.shape
# Calculate aspect ratio (width / height)
aspect_ratio = img_width / img_height
# Create subplot figure with shared x and y axes for synchronized zooming
fig = make_subplots(
rows=1, cols=3,
subplot_titles=("Channel 1 (Red)", "Channel 2 (Green)", "Merged (RGB)"),
shared_xaxes=True, shared_yaxes=True,
horizontal_spacing=0.02 # Tight layout: minimize spacing between subplots
)
# Define custom colormaps starting from black
red_cmap = [[0, "rgb(0,0,0)"], [1, "rgb(255,0,0)"]]
green_cmap = [[0, "rgb(0,0,0)"], [1, "rgb(0,255,0)"]]
# Add heatmaps for ch1 (red) and ch2 (green)
fig.add_trace(
go.Heatmap(z=ch1, colorscale=red_cmap, zmin=0, zmax=255, showscale=False),
row=1, col=1
)
fig.add_trace(
go.Heatmap(z=ch2, colorscale=green_cmap, zmin=0, zmax=255, showscale=False),
row=1, col=2
)
# Create RGB merged image (ch1 as R, ch2 as G, B=0)
rgb = np.stack([ch1, ch2, np.zeros_like(ch1)], axis=-1).astype(np.uint8)
# Add RGB image for merged subplot
fig.add_trace(
go.Image(z=rgb, zmin=[0, 0, 0, 0], zmax=[255, 255, 255, 255], hoverinfo="none"),
row=1, col=3
)
# Update axes for synchronized zooming and matching image aspect ratio
fig.update_xaxes(
matches='x',
scaleanchor='y',
scaleratio=1,
constrain='domain'
)
fig.update_yaxes(
matches='y',
scaleanchor='x',
scaleratio=1,
constrain='domain'
)
# Calculate figure dimensions to match image aspect ratio
# Base width per subplot, adjusted for three subplots and minimal margins
subplot_width = 600 # Base width per subplot in pixels
total_width = subplot_width * 3 + 50 # Account for margins and spacing
total_height = subplot_width / aspect_ratio + 150 # Adjust height for aspect ratio + margins
# Add single slider for zmax adjustment for all subplots
fig.update_layout(
sliders=[
{
"active": 99, # Default to 100% (zmax=255)
"yanchor": "top",
"xanchor": "left",
"currentvalue": {
"font": {"size": 16},
"prefix": "Contrast (zmax %): ",
"visible": True,
"xanchor": "right"
},
"pad": {"b": 10, "t": 50},
"len": 0.9,
"x": 0.05,
"y": 0,
"steps": [
{
"method": "restyle",
"label": str(i),
"value": str(i),
"args": [
{
"zmax": [
int((i-1)/99*255), # ch1 (Heatmap)
int((i-1)/99*255), # ch2 (Heatmap)
[int((i-1)/99*255), int((i-1)/99*255), int((i-1)/99*255)] # RGB (Image)
]
},
[0, 1, 2] # Apply to all three traces
]
} for i in range(1, 101, 1)
]
}
],
# Tight layout: minimize margins
margin=dict(l=10, r=10, t=50, b=100),
# title="Channel Visualization with Contrast Adjustment",
width=total_width,
height=total_height,
showlegend=False
)
# Save to HTML
fig.write_html(output_file, include_plotlyjs="cdn")
return fig
def test():
ch1 = io.imread('ch1.tif', plugin='pil')
ch2 = io.imread('ch2.tif', plugin='pil')
frame1 = ch1[5]
frame2 = ch2[4]
visualize_channels(frame1, frame2)
if __name__=='__main__':
test()
此处评论已关闭