• Python生物医学专业案例 - 细胞计数


    在上公共的编程基础课时,我们经常受到学生的质疑: 我们学这玩艺儿有什么用? 学生的疑问来自于“他没有从课程中得到通过程序设计来解决本专业问题的体验”。重庆大学的教学团队设计了很多与各专业紧密相关的程序设计案例,我们会陆续分享出来,供大家参考。

    本文引用自作者编写的下述图书; 本文允许以个人学习、教学等目的引用、讲授或转载,但需要注明原作者"海洋饼干叔
    叔";本文不允许以纸质及电子出版为目的进行抄摘或改编。
    1.《Python编程基础及应用》,陈波,刘慧君,高等教育出版社。免费授课视频 Python编程基础及应用
    2.《Python编程基础及应用实验教程》, 陈波,熊心志,张全和,刘慧君,赵恒军,高等教育出版社Python编程基础及应用实验教程
    3. 《简明C及C++语言教程》,陈波,待出版书稿。免费授课视频

    (在原始案例中,代码中间留空让学生填写,这里的代码是完整的)
    在这里插入图片描述
    一幅细胞照片经过二值化以后可以视为像素值为0或1的矩阵,如图21-1所示。在该矩阵中,值为1为元素表示该处是细胞或细胞的一部分,该元素的上、下、左、右的相邻元素如果也是1,则相邻元素与该元素位于同一个细胞内;矩阵中值为0的元素表示该处无细胞。识别并统计显微镜下一幅细胞照片中的细胞数量,是血液常规检查的最基本任务之一。

    对于图21-1所示的细胞照片,按上述规则,容易数出该幅照片中包含7个细胞。注意,第3行第3列是一个孤立细胞(图中已用底纹区分),它与第2行第5列的细胞并非同一个,因为它位于第2行第4列元素的左下方,而不是上下左右的位置。
    图21-1 细胞计数示例
    在本实验对应的实验子目录中有一名为cellpicture.txt的文本文件,其内容为细胞照片的二值化矩阵。请编写程序,从该文件读取矩阵内容并统计该矩阵中的细胞数量。

    该文件的内容如下:

    12 14
    10111000011100
    01100110001101
    00000111000011
    00110000001000
    00011000111000
    00111100010011
    10011101100111
    11000100000001
    00000000011000
    00000000000000
    10001100110000
    10001000011111
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    其中,第1行的1214以空格分隔,表示该矩阵有12行14列。接下来,则是12行元素数据,每行14个0/1字符。

    21.1 从文件读入二值化矩阵

    下述程序负责从文件cellpicture.txt中读取二值化矩阵,并将其组织在嵌套列表d中。请将程序补充完整并运行,确保矩阵d的内容与文件一致。注意,矩阵元素的类型应为整数。

    d = []
    with open('cellpicture.txt') as f:
        m,n = map(int,f.readline().split())
        for x in range(m):
            r = list(map(int, f.readline()[:n]))
            d.append(r)
    
    for x in d:
        print(x)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    【解题提示】

    • f.readline( )从文本文件f中读取一行内容,其返回值为一个字符串,注意该字符串包括了每行末尾的换行符。
    • map(func,iterable)称之为映射函数,该函数逐一将可迭代对象iterable中的元素作为参数提供给由func指定的函数来执行,映射出一系列的新元素。map()函数返回一个类型为map的可迭代对象,可以通过list()函数将其转换成列表。下述代码中的map()函数,将列表元素1,3,5,7逐一交给square()函数运行产生其平方数,再借助于list()函数转换得到包含4个平方数的结果列表。
    def square(x):
        return x*x 
    print(list(map(square,[1,3,5,7])))
    
    • 1
    • 2
    • 3

    上述代码的执行结果为:

    [1, 9, 25, 49]
    
    • 1

    21.2图的宽度优先遍历

    细胞的计数依赖于对矩阵元素的遍历。在逐行逐列遍历矩阵元素的过程中,发现一个值为1的元素,则意味着新发现了一个细胞。此时,需要从该元素出发,往多个方向搜索,找出该元素归属细胞所包含的全部像素/元素,并将这些像素/元素标记为“已探索”,以避免在后续遍历过程中,这些像素被错误地认为属于一个“新细胞”。

    这个任务可以通过一个称为“图的宽度优先遍历”的算法来解决,该算法可用函数explorePixel()来描述。其中,d为代表二值矩阵的嵌套列表,该矩阵有m行n列,搜索出发点为i行j列。请在阅读完解题提示后,将下述代码补充完整。

    def explorePixel(d,m,n,i,j):
            q = [(i,j)]
            while q:
                x,y = q.pop(0)
                if d[x][y] < 0:
                    continue 
                d[x][y] = 0-d[x][y]
                if x>0 and d[x-1][y]>0:
                    q.append((x-1,y))
                if x<(m-1) and d[x+1][y]>0:
                    q.append((x+1,y))
                if y>0 and d[x][y-1]>0:
                    q.append((x,y-1))
                if y<(n-1) and d[x][y+1]>0:
                    q.append((x,y+1))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    【解题提示】

    • explorePixel( )函数的任务可以描述为:从位于i行j列的像素出发,向其周边进行搜索,将与该像素有连接关系的其他像素全部找出,并标记为“已探索”。

    • 当像素值=0时,该像素不属于细胞;像素值=1时,该像素属于细胞且“待探索”,像素值=-1时,该像素属于细胞且“已探索”。

    • 列表q的行为表现为一个先进先出队列,其中存储那些已发现,但其自身及其相邻像素尚未被探索的像素。最初,q仅包含像素(i, j)。然后,程序将一直循环,直至队列空为止,每次循环的过程如下:
      (1)从队列中取出第0个待探索的像素(x, y);
      (2)检查(x,y)是否为已探索,如是,continue进入下一轮循环;
      (3)将(x,y)标记为“已探索”,即将其值变更为 0– d[x][y];
      (4)按上、下、左、右的顺序检查(x,y)的相邻像素,如果相邻像素的值>0,说明该相邻像素也属于当前细胞,将其加入待探索像素队列q。

    • 函数中的x表示行坐标,y表示列坐标。即与平面坐标系的通常习惯有所不同,这里的x表示上下方向,y表示左右方向。

    为帮助读者理解上述“图的宽度优先遍历”算法,我们手工模拟一遍explorePixel()函数的执行过程,从像素(0,4)出发。请见图21-2,为了与程序一致,我们的行号、列号改为从下标0开始。
    图21-2 图的宽度优先遍历过程
    当程序遍历矩阵元素到第0行第4列时,发现像素(0,4)值为1,即该像素状态为待探索且属于一个新细胞,执行explorePixel(d,m,n,0,4)从像素(0,4)出发搜索与该像素连接的全部细胞像素:
    (1) 队列q被初始化为只包含像素(0,4),其值为:[ (0,4) ]。
    (2) 像素(0,4)出队列,其值等于1待探索,像素(0,4)被赋值-1。
    此时,q = 空列表。
    (3) 像素(0,4)位于第1行,其上方元素不存在。
    (4) 像素(0,4)的下方元素(1,4)的值为1,属于同一细胞,将(1,4)加入队列q。
    此时,q = [ (1,4) ]。
    (5) 像素(0,4)的左方和右方元素值为0,不属于细胞。
    (6) 像素(1,4)出队列,其值等于1待探索,赋值-1。
    此时,q = 空列表。
    (7) 像素(1,4)的上方元素(0,4)此时值为-1已探索。
    (8) 像素(1,4)的下方元素(2,4)值为1,属于同一细胞,加入队列q。
    此时,q = [ (2,4) ]。
    (9) 像素(1,4)的左方和右方元素值均为1,属于同一细胞,将左方元素(1,3)和右方元素(1,5)加入队列q。
    此时,q = [ (2,4), (1,3), (1,5) ]。
    (10) 像素(2,4)出队列,其值为1待探索,赋值-1。
    此时,q = [ (1,3), (1,5) ]。
    (11) 像素(2,4)的上方元素已探索,下方、左方、右方元素为0,故未发现新的待探索像素。
    (12) 像素(1,3)出队列,其值为1待探索,赋值-1;(1,3)的右方元素已探索 ,上方、左方、下方均为0,故未发现新的待探索像素。注意,(2,2)位于(1,3)的左下方,并不属于题目定义的相邻像素。
    此时,q = [ (1,5) ]。
    (13) 像素(1,5)出队列,其值为待探索,赋值-1;(1,5)的左方元素已探索,其它三个方向均为0,未发现新的待探索元素。
    (14) 此时,q = 空列表,循环结束。在循环过程中,(0,4)、(1,4)、(2,4)、(1,3)、(1,5)共5个像素被探索并标记为已探索,这5个像素构成了一个完整的细胞。

                if d[x][y] < 0:
                    continue 
    
    • 1
    • 2

    读者可能会上述两行代码感到疑惑:既然一个待探索元素在加入队列前其值确定为1即待探索,那么在该像素出队后,其值可能变为负数(已探索)吗?
    图21-3 重复加入队列的待探索元素
    我们考虑图21-3所展示的情况。当我们从(1,1)出发搜索该细胞的全部像素时,(2,2)作为像素(2,1)的相邻元素,在探索(2,1)时会被加入队列。同时,作为(1,2)的下方元素,在探索(1,2)时也会被加入队列。这样,队列中就会存在两个(2,2),当第1个(2,2)被取出时,其像素值为1未探索,而当第2个(2,2)被取出时,其像素值已经是-1已探索。此时,再去考虑(2,2)的相邻元素已不具实践意义,故略过。

    21.3 循环遍历与搜索

    在实现了函数explorePixel( )之后,下述程序逐行逐列地遍历全部矩阵元素,如果发现值>0的像素,说明遇到了一个属于细胞且“待探索”的元素,将细胞计数变量iCellCount加1,然后再调用explorePixel()函数从该像素出发探索整个细胞。由于explorePixel( )函数会将所有探索过的细胞像素置为已探索,所以当下述程序第2行、第3行的主循环遍历到已探索的细胞像素时,该像素值为-1,不会将其视为一个新细胞。

    请将下述程序补充完整,并与前两小节的程序合并,运行,识别并统计cellpixel.txt文件中的细胞个数。

    iCellCount = 0
    for i in range(m):
        for j in range(n):
            if d[i][j] <= 0:
                continue
            iCellCount += 1
            explorePixel(d,m,n,i,j)
    
    print(iCellCount)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    为了帮助更多的年轻朋友们学好编程,作者在B站上开了两门免费的网课,一门零基础讲Python,一门零基础C和C++一起学,拿走不谢!

    简洁的C及C++
    由编程界擅长教书,教书界特能编程的海洋饼干叔叔打造
    Python编程基础及应用
    由编程界擅长教书,教书界特能编程的海洋饼干叔叔打造
    如果你觉得纸质书看起来更顺手,目前Python有两本,C和C++在出版过程中。

    Python编程基础及应用

    Python编程基础及应用实验教程
    在这里插入图片描述

  • 相关阅读:
    含钆介孔二氧化硅海藻酸钠纳米微球/桑色素二氧化硅单发光纳米微球的制备过程
    求臻人故事 | 在求臻医学的沃土中,我像竹子般茁壮成长
    centOS 7 离线安装 MySQL 5.6 完美安装
    sharding-jdbc自定义查询(范围查询)案例&&查询时可能会踩的坑
    美国各流域边界下载,并利用arcgis提取与处理
    Python截取视频帧
    H3C(华三)交换堆叠/IRF配置实例
    Linux学习-27-usermod、chage、userdel和id等用户信息相关命令
    关键节点与邻居搜索:K-Core算法对比K-Hop算法的效能较量
    ns-3 多天线设置与ns-3信道设置
  • 原文地址:https://blog.csdn.net/SeaBiscuitUncle/article/details/126552756