文章目录
- 一、napari 简介
- 二、napari 安装与更新
- 三、napari【巨巨巨大的一个BUG】
- 四、napari 使用指南
- 4.1、菜单栏(File + View + Plugins + Window + Help)
- 4.2、Window:layer list(参数详解)
- 4.3、Window:layer controls(points layer + shapes layer + labels layer)
- 五、项目实战
- 5.1、查看图像:napari.view_image()
- 5.2、添加图像:viewer.add_image()
- 5.3、添加点云:viewer.add_points()
- 5.4、添加形状:viewer.add_shapes() —— 获取线条坐标(起点和终点)
- 六、在napari中自定义组件,并与PyQt完成交互
一、napari 简介
基于 Python 编写的快速、交互式多维图像查看器
。
🥗napari 官网首页:https://napari.org/0.4.18/index.html#
🥪napari 使用案例:https://napari.org/0.4.18/gallery.html#gallery
二、napari 安装与更新
- 安装napari:
pip install napari
- 更新napari:
pip install --upgrade napari
三、napari【巨巨巨大的一个BUG】
【BUG】
:点击View - Toggle Full Screen将最大化软件界面,且菜单栏和很多按钮都将不可用。【影响】
:此时,想要任何操作都无法退出最大化,即使关闭后重试,卸载后重试都无法达到,没有试过关机后重试。【解决方法】
:Window + Tab切换窗口,菜单栏可以短暂有效且可点击,瞬间点击View - Toggle Full Screen,可解除BUG。
四、napari 使用指南
4.1、菜单栏(File + View + Plugins + Window + Help)
File(文件) | ||
---|---|---|
1 | Open File | 打开文件 |
2 | Opencv File as Stack | 打开文件(适用于大尺度) |
3 | Open Sample + napari builtins(提供了很多的内置样本) | 初学者可以直接导入后研究 |
4 | Preferences | 设置(主题 + 快捷键等等) |
5 | Save Selected Layer(s)(所有帧图像) | 保存选定的单层或多层(指定后缀,修改图像格式) |
6 | Save All Layers(所有帧图像) | 保存所有层(指定后缀,修改图像格式) |
7 | Save Screenshot(单帧图像) | 保存当前窗口内容(不显示界面) |
8 | Save Screenshot with Viewer(单帧图像) | 保存整个视图内容(图像 + 界面) |
- Plugins(插件):安装和卸载插件(也可以自定义)
View(视图) | ||
---|---|---|
1 | Axes | 轴 |
2 | Scale Bar | 刻度条 |
3 | Toggle Full Screen | 切换全屏 |
4 | Toggle Menubar Visibity | 切换菜单可见性 |
5 | Toggle Paly | 切换面板 |
6 | Toggle Layer Tooltips | 切换图层工具提示 |
7 | Toggle Activity Dock | 切换活动区 |
Window(窗口) | ||
---|---|---|
1 | console | 控制面板(命令行窗口) |
2 | layer controls | 图层控制(点层、形状层、标签层) |
3 | layer list | 图层列表 |
Help(帮助) | 直接跳转官网页面 | |
---|---|---|
1 | Getting started | 开始 |
2 | Tutorials | 教程 |
3 | Using Layers Guides | 使用图层指南 |
4 | Examples Gallery | 示例图库 |
5 | Release Notes | 版本说明 |
6 | napari homepage | napari 主页 |
7 | napari Info | napari 信息 |
4.2、Window:layer list(参数详解)
4.3、Window:layer controls(points layer + shapes layer + labels layer)
五、项目实战
5.1、查看图像:napari.view_image()
在napari查看器中显示单个或多个图像,并提供了许多可选参数来自定义图像的显示。
import napari
import tifffile
image_path = 'output_8bit.tif'
marked_image = tifffile.imread(image_path)
viewer = napari.view_image(marked_image, name='image', rgb=False)
napari.run()
# 备注:若不添加napari.run(),将避免程序阻塞问题(一直等待界面关闭)
"""
#########################################################################################################
# 函数说明:napari.view_image(data, channel_axis=None, name=None, colormap=None, blending=None, interpolation=None, gamma=None,
# is_pyramid=None, rgb=None, scale=None, translate=None, contrast_limits=None, rendering=None)
# 输入参数:
# - data:要显示的图像数据。可以是以下格式之一:
# 2D NumPy array:灰度图像数据。
# 3D NumPy array:3D图像数据,例如多张2D图像叠加形成的图像序列。
# 4D NumPy array:4D图像数据,例如多通道彩色图像。
# List of 2D, 3D, or 4D arrays:多个图像数据列表。
# Dask array:支持分块加载的大型图像数据。
# ImageData:来自dask_image.imread()等函数的图像数据对象。
# channel_axis:用于多通道图像的通道轴的索引。默认值为None,表示使用最后一个轴作为通道轴。
# - name:图像的名称,将在napari查看器中显示。
# - colormap:图像的颜色映射。可以是字符串表示的颜色映射名称,或是colormap函数。默认值为None,表示使用默认颜色映射。
# blending:图像的混合模式。可以是字符串表示的混合模式名称,例如"translucent"、"additive"等。默认值为None,表示使用默认混合模式。
# interpolation:图像的插值方法。可以是字符串表示的插值方法名称,例如"nearest"、"bilinear"、"bicubic"等。默认值为None,表示使用默认插值方法。
# gamma:图像的gamma值,用于对图像进行伽马校正。默认值为None,表示不进行伽马校正。
# is_pyramid:布尔值,用于指示是否使用金字塔结构显示图像。默认值为None,表示不使用金字塔结构。
# rgb:布尔值,用于指示输入图像是否为RGB彩色图像。默认值为None,表示根据输入图像数据自动判断。
# scale:图像的缩放因子。可以是单个值,表示在所有轴上应用相同的缩放,也可以是每个轴的缩放因子列表。默认值为None,表示不进行缩放。
# translate:图像的平移量。可以是单个值,表示在所有轴上应用相同的平移,也可以是每个轴的平移量列表。默认值为None,表示不进行平移。
# contrast_limits:图像的对比度限制,用于控制图像显示的亮度范围。可以是单个值,表示在所有轴上应用相同的对比度限制,也可以是每个轴的对比度限制列表。默认值为None,表示不设置对比度限制。
# rendering:图像的渲染模式。可以是字符串表示的渲染模式名称,例如"mip"、"translucent"、"attenuated_mip"等。默认值为None,表示使用默认渲染模式。
#########################################################################################################
"""
5.2、添加图像:viewer.add_image()
将单个或多个图像添加到napari查看器中,并提供了多个可选参数来自定义图像的显示。
import napari
import tifffile
image_path = '561result-1-part.tif'
marked_image = tifffile.imread(image_path)
viewer = napari.Viewer() # 创建napari视图
viewer.add_image(marked_image, name="image", colormap='red') # 添加图像(指定红色)
################################################################################
# 隐藏面板
# viewer.window.qt_viewer.controls.hide() # 隐藏后不可使用该功能(重新打开也不行)
# viewer.window.qt_viewer.layers.hide()
# viewer.window.qt_viewer.controls.close()
# viewer.window.qt_viewer.layers.close()
################################################################################
napari.run() # 显示napari图形界面
# 备注:若不添加napari.run(),将避免程序阻塞问题(一直等待界面关闭)
"""
#########################################################################################################
# 函数说明:viewer.add_image(data, *, name=None, scale=None, translate=None, contrast_limits=None,
# colormap=None, blending=None, visible=True, opacity=1.0, interpolation='bilinear',
# rendering='mip', rgb=None, colormap_range=None)
# 输入参数:
# - data:要添加的图像数据。可以是以下格式之一:
# 2D NumPy array:灰度图像数据。
# 3D NumPy array:3D图像数据,例如多张2D图像叠加形成的图像序列。
# 4D NumPy array:4D图像数据,例如多通道彩色图像。
# - name:图像的名称,将在napari查看器中显示。
# scale:图像的缩放因子。可以是单个值,表示在所有轴上应用相同的缩放,也可以是每个轴的缩放因子列表。
# translate:图像的平移量。可以是单个值,表示在所有轴上应用相同的平移,也可以是每个轴的平移量列表。
# contrast_limits:图像的对比度限制,用于控制图像显示的亮度范围。可以是单个值,表示在所有轴上应用相同的对比度限制,也可以是每个轴的对比度限制列表。
# - colormap:图像的颜色映射。可以是字符串表示的颜色映射名称,或是colormap函数。
# blending:图像的混合模式。可以是字符串表示的混合模式名称,例如"translucent"、"additive"等。
# visible:图像是否可见。布尔值,默认为True。
# opacity:图像的不透明度。默认为1.0,表示完全不透明。
# interpolation:图像的插值方法。可以是字符串表示的插值方法名称,例如"nearest"、"bilinear"、"bicubic"等。
# rendering:图像的渲染模式。可以是字符串表示的渲染模式名称,例如"mip"、"translucent"、"attenuated_mip"等。
# rgb:布尔值,用于指示输入图像是否为RGB彩色图像。
# colormap_range:颜色映射的范围。可以是字符串,例如"auto"或"full",表示自动计算颜色映射范围或使用完整范围。
#########################################################################################################
"""
5.3、添加点云:viewer.add_points()
将点的坐标和可选的其他属性添加到napari查看器中,并提供了多个可选参数来自定义点云的显示。
- viewer.add_points()用于添加点云数据,但不能直接显示。需要先添加viewer.view_image(),然后再显示点云数据。
- 点云数据:由离散点(x, y, z)坐标的集合组成。
import napari
import tifffile
import numpy as np
# (1)通过tifffile加载tif图像
marked_image = tifffile.imread('marked_image.tif')
# (2)获取图像的长宽高
if len(marked_image.shape) == 3: # 灰度3D图像:10x10x10
depth, height, width = marked_image.shape
elif len(marked_image.shape) == 4: # 彩色3D图像:3x10x10x10
depth, height, width, _ = marked_image.shape
# (3)根据图像类型自适应变量值
if marked_image.dtype == np.uint8:
max_gray_value = 255
elif marked_image.dtype == np.uint16:
max_gray_value = 65535
elif marked_image.dtype == np.uint32:
max_gray_value = 4294967295
####################################################################
# (4.1)提取指定像素
indices = np.argwhere(marked_image == max_gray_value)
# (4.2)指定范围内的像素值,获取坐标,并绘制为标记点
# min_gray_value = 50
# max_gray_value = max_gray_value - 1
# indices = np.argwhere((marked_image >= min_gray_value) & (marked_image <= max_gray_value))
####################################################################
viewer = napari.Viewer() # 创建napari查看器
viewer.add_image(marked_image) # 添加图像到napari视图
viewer.add_points(indices[:, [0, 1, 2]], size=2, face_color='red', shading='spherical', edge_width=0) # 添加点云
napari.run() # 显示napari图形界面
# 备注:若不添加napari.run(),将避免程序阻塞问题(一直等待界面关闭)
5.4、添加形状:viewer.add_shapes() —— 获取线条坐标(起点和终点)
注意:shapes层和image_data层是两个独立的层,故线的坐标映射到image_data需要进行高度和宽度限制。
import napari
import cv2
# (1)加载图像
image_path = 'blank.png'
image_data = cv2.imread(image_path)
print("height:", image_data.shape[0], "width:", image_data.shape[1])
# (2)创建napari Viewer
viewer = napari.Viewer()
viewer.add_image(image_data) # 添加图像
# 添加形状(线条 + 线宽 + 颜色)
shapes_layer = viewer.add_shapes(data=None, shape_type='line', edge_width=3, edge_color='red')
shapes_layer.mode = 'add_line' # 直接开始绘制线条
napari.run() # 运行napari
# 备注:若不添加napari.run(),将避免程序阻塞问题(一直等待界面关闭)
# (3)打印坐标
# image_shape = image_data.shape # 获取图像的形状
line_layer = viewer.layers['Shapes'] # 获取绘制的线的图层
line_coordinates1 = [] # 获取绘制的所有线的坐标
line_coordinates2 = [] # 获取绘制的所有线的坐标:删除超出图像的线条
if line_layer.data: # 检查图层数据是否存在
for line in line_layer.data: # 遍历线的坐标
coordinates1 = [line[0, 0], line[0, 1], line[1, 0], line[1, 1]]
line_coordinates1.append(coordinates1)
for line in line_layer.data: # 遍历线的坐标
# 【高度限制】:删除超出图像的线条
if line[0, 0] < 0:
line[0, 0] = 0
elif line[0, 0] > image_data.shape[0]:
line[0, 0] = image_data.shape[0]
if line[1, 0] < 0:
line[1, 0] = 0
elif line[1, 0] > image_data.shape[0]:
line[1, 0] = image_data.shape[0]
# 【宽度限制】:删除超出图像的线条
if line[0, 1] < 0:
line[0, 1] = 0
elif line[0, 1] > image_data.shape[1]:
line[0, 1] = image_data.shape[1]
if line[1, 1] < 0:
line[1, 1] = 0
elif line[1, 1] > image_data.shape[1]:
line[1, 1] = image_data.shape[1]
coordinates2 = [line[0, 0], line[0, 1], line[1, 0], line[1, 1]]
line_coordinates2.append(coordinates2)
# 输出所有坐标(shapes:线坐标)
for idx, coordinates in enumerate(line_coordinates1):
print(f'Line {idx + 1} coordinates1:', coordinates)
print("")
# 输出所有坐标(image:线坐标映射到图像的坐标):高度限制+宽度限制
for idx, coordinates in enumerate(line_coordinates2):
print(f'Line {idx + 1} coordinates2:', coordinates)
六、在napari中自定义组件,并与PyQt完成交互
在napari中自定义组件与PyQt新建组件的方法相同,区别是需要将插件的主窗口添加到napari界面的控制面板中:self.viewer.window.add_dock_widget(widget, area='right') # 添加到napari的右侧
,其中:widget是插件的主窗口。
import tifffile
import napari
import numpy as np
import sys
import os
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, \
QPushButton, QFileDialog, QTextEdit, QSlider, QCheckBox
from PyQt5.QtCore import Qt
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Window")
##########################################################
layout = QVBoxLayout()
button_layout = QHBoxLayout()
self.load_button = QPushButton("Load Image", self)
self.load_button.clicked.connect(self.load_image)
self.image_name_label = QLabel("")
button_layout.addWidget(self.load_button)
button_layout.addWidget(self.image_name_label)
layout.addLayout(button_layout)
##########################################################
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
##########################################################
# 初始化参数
self.image_path = ""
self.image_name = ""
##########################################################
def load_image(self):
file_dialog = QFileDialog()
image_path, _ = file_dialog.getOpenFileName(self, "Select Image", "", "Image Files (*.tif *.png *.jpg *.jpeg)")
if image_path:
self.image_path = image_path
self.image_name = os.path.basename(image_path)
self.image_name_label.setText(self.image_name)
self.image_slices = tifffile.imread(image_path)
self.napari_gray_view() # 调用napari_gray_view方法来显示第一个切片
def napari_gray_view(self):
# (1)napari:创建视图、添加图层、显示视图
self.viewer = napari.Viewer() # 创建napari视图
self.viewer.add_image(self.image_slices, name='image') # 添加napari图层
# napari.run() # 显示napari图形界面
# 备注:若不添加napari.run(),将避免程序阻塞问题(一直等待界面关闭)
#########################################################################
# (2)自定义组件
#########################################################################
# (2.1)创建滑动条
self.slider = QSlider() # 新建滑动条
self.slider.setMinimum(0) # 设置滑动条的最小值
self.slider.setMaximum(np.max(self.image_slices)) # 设置滑动条的最大值
self.slider.setValue(0) # 设置滑动条的初始值
# (2.2)创建标签
self.slider_label = QLabel(str(self.slider.value()))
# (2.3)复选框
self.checkbox = QCheckBox("checkbox") # 复选框
self.slider.setEnabled(False) # 复选框的初始状态:False
self.input_box = QLineEdit() # 新建输入框
self.input_box.setEnabled(True) # 复选框的初始状态:True
self.input_label = QLabel("range: " + str(np.min(self.image_slices)) + "/" + str(np.max(self.image_slices))) # 输入框标签
# (3)创建布局并将滑动条和标签添加到布局中
layout = QVBoxLayout()
layout.addWidget(self.slider)
layout.addWidget(self.slider_label)
layout.addWidget(self.checkbox)
layout.addWidget(self.input_box)
layout.addWidget(self.input_label)
# (4)创建一个QWidget作为插件的主窗口
widget = QWidget()
widget.setLayout(layout)
# (5)连接复选框的状态变化信号与槽函数
self.checkbox.stateChanged.connect(self.onCheckboxStateChanged)
self.slider.valueChanged.connect(self.napari_update_gray) # 根据滑动条的值,显示第一个切片
self.input_box.returnPressed.connect(self.napari_update_gray) # 根据输入框的值,显示第一个切片
# (6)将插件的主窗口添加到napari界面的控制面板中
self.viewer.window.add_dock_widget(widget, area='right') # 添加到napari的右侧
#########################################################################
# (7)napari:显示视图
self.viewer.window.show()
def onCheckboxStateChanged(self, state):
if state == Qt.Checked:
self.slider.setEnabled(True)
self.input_box.setEnabled(False)
else:
self.slider.setEnabled(False)
self.input_box.setEnabled(True)
def napari_update_gray(self):
# (1)获取napari视图中的图层对象,并获取图像数据
napari_image = self.image_slices.data
# (2)获取当前切片滑动条的值
current_slice = int(self.viewer.dims.current_step[0])
# (3)获取当前灰度滑动条的值
self.slider_label.setText(str(self.slider.value()))
if self.checkbox.isChecked():
current_gray = self.slider.value()
else:
current_gray = int(self.input_box.text())
print("current_slice:", current_slice, "current_gray:", current_gray)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())