項目筆記《DeepLung:Deep 3D Dual Path Nets for Automated Pulmonary Nodule Detection and Classification》(三)(上)結果評估


在(一)中,我將肺結節檢測項目總結為三階段,這里我要講講這個項目的第三階段,至於第二階段,由於數據增強部分的代碼我始終看不大懂,先不講。

結果評估的程序在evaluationScript文件夾下,這個文件夾下的文件名比較煩,看的比較懵。

annotations文件夾里面放的是結節標簽文件,無關結節標簽文件(是結節,但是不統計在內,也不作為非結節區域,就是評估的時候如果你檢測到了它,既不算正確,也不算錯誤,略過),還有用戶id文件。

tool文件夾放的是讀取csv文件的模塊。

frocwrtdetpepchluna16.py用來將輸出的.npy格式的結果轉化為.csv格式。

noduleCADEvaluationLUNA16.py用來對.csv文件中的結果進行評估,得出froc曲線圖。

下面詳細分析下代碼,首先是frocwrtdetpepchluna16.py。

 

def getcsv(detp, eps): #給定閾值和epoch
    for ep in eps:#針對每個epoch
        bboxpath = results_path + str(ep) + '/' #找到每個epoch的路徑
        for detpthresh in detp:
            print 'ep', ep, 'detp', detpthresh
            f = open(bboxpath + 'predanno'+ str(detpthresh) + 'd3.csv', 'w') #根據閾值分別創建與之對應的文件
            fwriter = csv.writer(f)
            fwriter.writerow(firstline) #寫入第一行,包括用戶id,結節坐標x,y,z,結節概率p
            fnamelist = []
            for fname in os.listdir(bboxpath):
                if fname.endswith('_pbb.npy'): #找到以_pbb.npy結尾的文件(輸出的結節預測值),添加進文件列表
                    fnamelist.append(fname)
                  
            print(len(fnamelist))
            convert = functools.partial(convertcsv, bboxpath = bboxpath, detp = detpthresh) #這個函數對convertcsv函數進行修飾,其實就是預設定幾個參數,不用再輸入
            for fname in fnamelist:
                print fname
                rowlist = convert(fname) #將每一個_pbb.npy文件轉換為csv文件  
                for row in rowlist:
                    print row
                    fwriter.writerow(row)  

       
       #這段注釋掉的是原來的代碼,用來對那末多pbb.npy文件並行處理,事實證明,確實快了好幾倍,實際運行應該用下面這段
       
       #map函數也是修飾函數,將fnamelist的元素並行送給convert處理
#predannolist = p.map(functools.partial(convertcsv, bboxpath=bboxpath, detp=detpthresh), fnamelist) 
                                                                                           
            #for predanno in predannolist:                                         
                # print predanno
            #    for row in predanno:
                    # print row
            #        fwriter.writerow(row)
            f.close()

上面這段代碼不是核心代碼,這段的作用是對輸出的結果文件調用convertcsv函數處理,結果就是每一個epoch都生成一個csv文件,存放80多個測試病例的預測結節位置及概率。

那么接下來就說說比較核心的convertcsv函數

def convertcsv(bboxfname, bboxpath, detp):#給定pbb.npy的文件名,pbb.npy的路徑,閾值
    sliceim,origin,spacing,isflip = load_itk_image(datapath+bboxfname[:-8]+'.mhd')#加載原始數據
    origin = np.load(sideinfopath+bboxfname[:-8]+'_origin.npy', mmap_mode='r') #以下幾行加載預處理后的坐標原點,分辨率,拓展box
    spacing = np.load(sideinfopath+bboxfname[:-8]+'_spacing.npy', mmap_mode='r')
    resolution = np.array([1, 1, 1])
    extendbox = np.load(sideinfopath+bboxfname[:-8]+'_extendbox.npy', mmap_mode='r')
    pbb = np.load(bboxpath+bboxfname, mmap_mode='r') #加載pbb.npy文件
    #print "load finished!"
    pbbold = np.array(pbb[pbb[:,0] > detp]) #根據閾值過濾掉概率低的
    pbbold = np.array(pbbold[pbbold[:,-1] > 3])  #根據半徑過濾掉小於3mm的
    pbbold = pbbold[np.argsort(-pbbold[:,0])][:1000] #這條是我加上的,取概率值前1000的結節作為輸出,不然直接進行nms耗時太長
    
    pbb = nms(pbbold, nmsthresh) #對輸出的結節進行nms
    #print "after nms bboxs:",pbb.shape[0]
    # print len(pbb), pbb[0]
    # print bboxfname, pbbold.shape, pbb.shape, pbbold.shape
    pbb = np.array(pbb[:, :-1]) #去掉直徑
    # print pbb[:, 0]
    pbb[:, 1:] = np.array(pbb[:, 1:] + np.expand_dims(extendbox[:,0], 1).T) #對輸出加上拓展box的坐標,其實就是恢復為原來的坐標,我對這個拓展box深惡痛絕
    pbb[:, 1:] = np.array(pbb[:, 1:] * np.expand_dims(resolution, 1).T / np.expand_dims(spacing, 1).T) #將輸出恢復為原來的分辨率,這樣就對應了原始數據中的體素坐標
    if isflip:#如果有翻轉的情況,將坐標翻轉(我理解是這樣的,原始數據有翻轉的情況,但是label還是未翻轉的label,那么將label翻轉,所以模型的輸出也是翻轉的,現在要再翻轉回去,與label對應)
        #拓展box與翻轉這兩個操作讓我神煩 Mask
= np.load(sideinfopath+bboxfname[:-8]+'_mask.npy', mmap_mode='r') pbb[:, 2] = Mask.shape[1] - pbb[:, 2] pbb[:, 3] = Mask.shape[2] - pbb[:, 3] pos = VoxelToWorldCoord(pbb[:, 1:], origin, spacing) #將輸出轉換為世界坐標 #print "voxel to world finished!" rowlist = [] # print pos.shape for nk in xrange(pos.shape[0]): # pos[nk, 2], pos[nk, 1], pos[nk, 0] #現在依次將文件名,z,y,x,概率(經過sigmoid處理)寫入rowlist,每行都是一個輸出結節 rowlist.append([bboxfname[:-8], pos[nk, 2], pos[nk, 1], pos[nk, 0], 1/(1+np.exp(-pbb[nk,0]))]) # print len(rowlist), len(rowlist[0]) return rowlist#bboxfname[:-8], pos[:K, 2], pos[:K, 1], pos[:K, 0], 1/(1+np.exp(-pbb[:K,0]))

這段代碼各種操作是真的繁瑣,看着都頭疼。這段主要完成的就是對輸出結節應用閾值和nms,輸出的結果再轉換為與label一樣的坐標體系,因為最后的准確與否全看與label是否一致。看一下打印的輸出信息,這里用的是subset9為驗證集,我只貼出了一個epoch的測試結果,閾值取為-0.125,這是sigmoid函數的輸入,對應概率值0.4695。

ep 1 detp -0.125
write to/home/code/DeepLung/detector/results/new_arch/retrft969/val1/predanno-0.125pbb.csv

 

至此,輸出處理完畢,可以求取最后的FROC值了,離大功告成只差一步。

不過,每個epoch都會對應一個csv結果文件,我們還要選取一個最好的結果,選取標准就是FROC值,這些都是如何做的呢,不妨看一下代碼。

def getfroc(detp, eps):
    maxfroc = 0
    maxep = 0
    for ep in eps: #對每個epoch分別處理
        bboxpath = results_path + str(ep) + '/'
        predannofnamalist = []
        for detpthresh in detp: #此處的detp就是閾值,只不過這里采用的是一個閾值列表,就我自己而言,我采用的閾值是-0.125,列表中只有一個元素
            predannofnamalist.append(bboxpath + 'predanno'+ str(detpthresh) + 'pbb.csv')
        #froclist = p.map(getfrocvalue, predannofnamalist)
        froclist = [getfrocvalue(predanno) for predanno in predannofnamalist] #調用getfrocvalue求取froc值
        if maxfroc < max(froclist):
            maxep = ep
            maxfroc = max(froclist)
        print froclistprint maxfroc, maxep

 

接下來調用getfrocvalue函數,這個函數其實是調用了noduleCADEvaluationLUNA16.py,也就是核心模塊,剛才的frocwrtdetpepchluna16.py只是做了一層處理,對輸出進行篩選和變換,以用來進行最后的大決戰。

def getfrocvalue(results_filename):
    return noduleCADEvaluation(annotations_filename,annotations_excluded_filename,seriesuids_filename,results_filename,'./')

可以看到,我們將標簽文件,無關標簽文件,用戶id文件,以及最后的結果文件也就是之前的csv文件作為輸入,輸出會是一個標量值,也就是評分。

 對noduleCADEvaluationLUNA16.py的分析我想放到(三)(下)去講,因為會比較長。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM