Day 14 串口命令
基于串口通信的自动送片系统实现在前面,我们演示了如何实现手工单片扫描的功能实现。在现实中,这种工作模式是无法提高什么工作效率的,甚至还不如医生自己拿到显微镜下去人工数。只有实现整个流程的自动化才能真正体现出系统的效率。因此,我们需要为扫描仪装上自动上片系统。
自动上片系统的软硬件简介抛去硬件实现的细节不谈,所谓自动上片系统,本质上就是单片机控制的一个机械手,它实现将玻片从片仓中取出放到载物台上,以及从载物台取走玻片放回片仓中的操作——核心就是这两个操作。它有一个很孱弱的单片机,控制步进电机运动,同时和主机通信。通信协议一般是基于串口的二进制消息。一般来说,这种系统的二进制消息定义也十分简单。比如我们使用的上片机,其接口定义如下:
下行消息(上位机->下位机)
123456789字段 长度 值 说明TAG 2 0xEB90 LEN 1 0x00-0xFF 从DIR到CRC的长度DIR 1 取值: 0x00: 下行 0x01: 上行 CMD ...
Day 13 选区对话框
接下来讨论一下在预览图上确认低倍扫描范围的界面实现。和参数确认相比,这个对话框的实现技术要复杂的多,其他细节也麻烦的多。而同时,相对于后面要介绍的在低倍图上确认高倍扫描的界面,又要简单很多,起着一个承前启后的作用。
程序结构调整在开始开发之前,我们发现一个很烦恼的事情,就是我们要测试CellScanner项目中的界面会很麻烦。因为它是一个exe,我们无法直接引用,必须在测试项目中使用源码,从而要修改一堆的路径,还很容易出错。
接下来,我们会再新建一个DLL子项目,把新的界面组件也都放在这里面。这一步应该是轻车熟路了。新的库工程参数如下图所示:
然后,把它添加到CellScanner项目中。
我们最终的目标是把CellScanner中的所有的类都挪过来,在CellScanner中只留下一个main.c就可以了。暂时嘛,先维持现状不变。
创建目录的时候,建议在项目目录下再建立一个目录,把源码都放进去。比如,我们在项目目录下又建立了一个目录cellwidgets,这样,在其他项目中,可以这样使用:#include "cellwidgets/previewchoosedial ...
Day 12 实现简单界面
实现ControlPanelFormControlPanelForm用于显示界面左侧的信息栏,它有两部分组成:上面主要是一个QLabel,用于显示样本的预览图,下方是一个QTableWidget,用于显示扫描进度。是一个很粗糙的界面设计,但是用于演示也够了。
它包含四个slot函数,我们会依次简单看一下:
显示预览图信息函数onSigPreviewCaptured()用于在收到sigPreviewed后显示样本信息。它将收到的cv::Mat转换成QPixmap,并按照这个QLabel的实际大小做缩放,并设置到QLabel上面。
12345678910void ControlPanelForm::onSigPreviewCaptured(cv::Mat prev_mat, cv::Mat label_mat, const QString &qrcode){ _impl->_prevMat = prev_mat.clone(); _impl->_qrcode = qrcode; auto pixmap = ImageTool::MatToQP ...
Day 11 集成到界面
集成ScanTask到界面中我们前面花了好几天来实现后台的处理,却一直看不到程序的界面,接下来我们尝试实现界面相关的功能。首先我们实现单张扫描功能。
我们增加一个slot函数onActSingleScan(),并在ScannerMainWindow的构造函数中将其与QAction actSingleScan关联起来。然后我们给出第一个实现版本:
12345void ScannerMainWindow::onActSingleScan(){ ScanTask task(_impl->_configer, _impl->_scanner); task.doScan({});}
运行程序,我们能够从调试窗口看到了程序运行的调试输出信息,说明程序在运行,但是界面一动不动,被阻塞住了。我们需要把它挪到独立的线程中运行。最简单的做法还是和在连接设备中做的一样,使用QtConcurrent::run()启动线程运行。
一种新手很容易犯的错误写法是:
123456void ScannerMainWindow::onActSingleSc ...
Day 10 高倍扫描 II
集成ImageJointer完成HighImagerJointer单元测试后,我们要将其集成到ScanTask中。
首先创建一个工厂函数,它根据样本类型来决定创建的IImageJointer子类的类别:
1234567891011std::unique_ptr<IImageJointer> AlgorithmFactory::makeImageJointer(int scan_method){ if(scan_method == EScanMethod::eSingleScan) { return std::make_unique<DummyImageJointer>(); } else { return std::make_unique<HighImageJointer>(); }}
在ScanTask::doHighScan()中增加Jointer的创建和启动,并在得到每张图片后将其发送给Jointer处理:
123456 ...
Day 09 高倍扫描
高倍扫描高倍扫描从细胞类别上分为白细胞,红细胞,巨核细胞三类,在细节上有所差别,但是本质上是相同的。我们只讨论白细胞的高倍扫描。
高倍扫描有两种扫描方式,一种是外周血常用的离散方式,即根据提供的坐标,拍摄一张张独立的照片,照片和照片之间没有关联。另一种是连续扫描,和低倍扫描类似,提供的是一个矩形的范围,拍摄连续的照片。和低倍扫描不同的是高倍连续扫描下,相邻的图片之间是有重叠区域的,我们需要将这些图片做拼接和切割,重新生成无重叠的固定大小的图片。
为什么外周血要使用离散扫描方式,而骨髓要使用连续扫描方式?这是一种时间和空间的权衡。在外周血中,白细胞的密度是比较低的,一般需要几个百倍视野的范围内才能有一个白细胞,如果使用连续扫描,会浪费大量的存储空间。相反,一般骨髓样本中的白细胞密度相当高,从几个到几十个,这样我们就不可能拍摄单独的照片了。对于一些特殊的病例,比如急性白血病,白细胞大量增生,我们就应当改用连续扫描,而另一方面,比如对化疗晚期的病人,其体内的白细胞极其稀少,即使是骨髓,一张玻片可能都找不到一百个,此时我们自然需要使用离散扫描的方式。
同样的道理,我们拍摄红细胞的时候必然是连 ...
overload模式详解
前几天跟一个解释代码,他怎么都理解不了我写的使用overload转换QOverload的代码。最后我只能跟他说你就记住我是这么写的,你在上面改就是了。今天有时间,把这个东西详细地写一下。
C++17中的overloadC++17中提供了overload模式,在std::visit的示例中。不注意还真的注意不到。
12345678template <typename... Ts>struct overload : Ts...{ using Ts::operator()...;};template <typename ... Ts>overload(Ts...) -> overload<Ts...>;
按照上面的观点,它应该是作为一个visitor和std::visit配合使用的。
123456789101112131415161718192021222324252627282930void overload_test(){ auto twice = overload{ [](std:: ...
C++20新增的数字比较函数
C++20新增的数字比较函数浏览C++refreence时注意到,C++20开始,增加了一批数字比较的函数
1234567891011121314template <class T, class U>constexpr bool cmp_equal (T t , U u) noexcepttemplate <class T, class U>constexpr bool cmp_not_equal (T t , U u) noexcepttemplate <class T, class U>constexpr bool cmp_less (T t , U u) noexcepttemplate <class T, class U>constexpr bool cmp_greater (T t , U u) noexcepttemplate <class T, class U>constexpr bool cmp_less_equal (T t , U u) noexcepttemplate <class T, class ...
Day 08 打包和低倍扫描
实现PackingWorker设计PackingWorker类接下来是数据打包功能。数据打包功能类PackingWorker,我们封装了对玻片索引数据库的操作和对zip归档文件的操作。这个类的生命周期应该是贯穿玻片扫描的全过程的,它应该是在doPrepare()的时候被创建,在每个扫描活动进行中被操作,用于记录数据库,将图像压缩到数据库等。下面是我们定义的它的初步的接口。可以看到,它的接口是和ScanTask中的signal对应的:
123456789101112131415161718192021222324class FRAMEWORKS_EXPORT PackingWorker : public QObject{ Q_OBJECTpublic: friend class PackingWorkerTester; friend class ScanTaskTester; explicit PackingWorker(const QString& path, const QString& slide_id, IConfiger* co ...