Commit f458c65b authored by Lars Bittrich's avatar Lars Bittrich
Browse files

segmentation.py: adaptation to allow usage of OpenCV 4.0 alongside 3.4

editParticles.py: correctly handle cancelation of Combine and Reassign actions; adaptation to allow usage of OpenCV 4.0 alongside 3.4
analysisview.py: some first restructuring; export dialogs are now QDialogs instead of QWidgets; restructuring of ExcelExport and bugfixes therein; hit quality index export is broken and now set to zero instead wrong numbers
parent 8c55b20a
Loading
Loading
Loading
Loading
+51 −78
Original line number Original line Diff line number Diff line
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
#!/usr/bin/env python3
"""
"""
GEPARD - Gepard-Enabled PARticle Detection
GEPARD - Gepard-Enabled PARticle Detection
Copyright (C) 2018  Lars Bittrich and Josef Brandt, Leibniz-Institut für 
Copyright (C) 2018  Lars Bittrich and Josef Brandt, Leibniz-Institut für 
@@ -20,8 +20,9 @@ along with this program, see COPYING.
If not, see <https://www.gnu.org/licenses/>.
If not, see <https://www.gnu.org/licenses/>.
"""
"""


import numpy as np
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5 import QtCore, QtGui, QtWidgets
import numpy as np
import pandas as pd
import sys
import sys
import operator
import operator
import os
import os
@@ -32,10 +33,9 @@ from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar


import pandas as pd
from .loadresults import LoadWITecResults
from analysis.loadresults import LoadWITecResults
from .editParticles import ParticleEditor
from analysis.editParticles import ParticleEditor
from .database import DataBaseWindow
from analysis.database import DataBaseWindow
try:
try:
    from analysis.sqlexport import SQLExport
    from analysis.sqlexport import SQLExport
    sqlEnabled = True
    sqlEnabled = True
@@ -58,7 +58,6 @@ class ParticleAnalysis(QtWidgets.QMainWindow):
            self.config = self.parent.dataset.resultParams
            self.config = self.parent.dataset.resultParams
        
        
        self.editor = ParticleEditor(self)
        self.editor = ParticleEditor(self)
        self.expWin = None
        
        
        self.spectraResults = None                  #entire List of all spectra assignments
        self.spectraResults = None                  #entire List of all spectra assignments
        self.additiveResults = None                 #entire List of all additives
        self.additiveResults = None                 #entire List of all additives
@@ -70,7 +69,6 @@ class ParticleAnalysis(QtWidgets.QMainWindow):
        self.uniquePolymers = None                  #list of present polymer types
        self.uniquePolymers = None                  #list of present polymer types
        self.spectra = None                         #acquired spectra
        self.spectra = None                         #acquired spectra
        self.indices = None                         #assignment of what spectra-indices belong to what substance
        self.indices = None                         #assignment of what spectra-indices belong to what substance
        self.expWindow = None
        self.additivePlot = None
        self.additivePlot = None
        self.importWindow = None
        self.importWindow = None
        self.directory = None
        self.directory = None
@@ -573,18 +571,13 @@ class ParticleAnalysis(QtWidgets.QMainWindow):
        self.createPolymerOverlay()
        self.createPolymerOverlay()
    
    
    def exportToExcel(self):
    def exportToExcel(self):
        if self.expWin is not None:
        expWin = ExpExcelDialog(self)
            self.expWin.close()
        expWin.exec()
            self.expWin.destroy()
        self.expWin = ExpExcelDialog(self)
        self.expWin.show()
            
            
    def exportToSQL(self):
    def exportToSQL(self):
        if self.expWin is not None:
        sqlexp = SQLExport(self)
            self.expWin.close()
        sqlexp.exec()
            self.expWin.destroy()
        
        self.expWin = SQLExport(self)
        self.expWin.show()
    
    
    def updateSpecPlot(self, centerOn=True, highlightContour=True):
    def updateSpecPlot(self, centerOn=True, highlightContour=True):
        #draw Sample Spectrum
        #draw Sample Spectrum
@@ -859,14 +852,14 @@ class ParticleAnalysis(QtWidgets.QMainWindow):


    
    
    def closeEvent(self, event):
    def closeEvent(self, event):
        for window in [self.expWindow, self.additivePlot, self.importWindow, self.dbWin]:
        for window in [self.additivePlot, self.importWindow, self.dbWin]:
            try: window.close()
            try: window.close()
            except: pass
            except: pass
        self.parent.imparent.particelAnalysisAct.setChecked(False)
        self.parent.imparent.particelAnalysisAct.setChecked(False)
        event.accept()
        event.accept()
    
    


class ExpExcelDialog(QtWidgets.QWidget):
class ExpExcelDialog(QtWidgets.QDialog):
    def __init__(self, parent):
    def __init__(self, parent):
        super(ExpExcelDialog, self).__init__()
        super(ExpExcelDialog, self).__init__()
        self.setWindowTitle('Export Options')
        self.setWindowTitle('Export Options')
@@ -919,22 +912,6 @@ class ExpExcelDialog(QtWidgets.QWidget):
        
        
        self.layout.addWidget(excelgroup)
        self.layout.addWidget(excelgroup)


#        sqlGroup = QtWidgets.QGroupBox('Export to SQL')
#        sqlLayout = QtWidgets.QVBoxLayout()
#
#        self.sqlbtn = QtWidgets.QPushButton('Export to SQL Database')
#        self.sqlbtn.resize(self.sqlbtn.sizeHint()) 
#        if sqlEnabled:
#            self.sqlbtn.clicked.connect(self.toSQL)
#        else:
#            self.sqlbtn.setDisabled(True)
#        
#        self.sqlExport = None
#
#        sqlLayout.addWidget(self.sqlbtn)
#        sqlGroup.setLayout(sqlLayout)
#        
#        self.layout.addWidget(sqlGroup)
        self.show()
        self.show()
    
    
    def toExcel(self):
    def toExcel(self):
@@ -958,35 +935,34 @@ class ExpExcelDialog(QtWidgets.QWidget):
                    requiredcolumns.append('50 - 100 µm')
                    requiredcolumns.append('50 - 100 µm')
                    requiredcolumns.append('> 100 µm')
                    requiredcolumns.append('> 100 µm')
        
        
        self.finalData = np.array(['nothing to see here, iam a boring placeholder']*((self.polymers.shape[0]+1)*len(requiredcolumns))).reshape(((self.polymers.shape[0]+1), len(requiredcolumns)))
        finalData = np.zeros((self.polymers.shape[0],len(requiredcolumns)-1))

        polymertypes = [""]*self.polymers.shape[0]
        #create header:
        rowindex = 0
        self.finalData[0, :] = np.array(requiredcolumns)
        self.finalData[0, 0] = 'Polymer Type'
        
        rowindex = 1
        for polymer in np.unique(self.polymers):
        for polymer in np.unique(self.polymers):
            indices = np.where(self.polymers == polymer)[0]
            indices = self.polymers == polymer
            numentries = int(len(indices))
            numentries = int(np.sum(indices))
            print("Num:", numentries)
            sys.stdout.flush()
            
            
            for colindex, column in enumerate(requiredcolumns):
            for colindex, column in enumerate(requiredcolumns):
                if column == 'Polymer Type (mandatory)':
                if column == 'Polymer Type (mandatory)':
                    self.finalData[rowindex:rowindex+numentries, colindex] = self.polymers[indices]    
                    polymertypes[rowindex:rowindex+numentries] = self.polymers[indices]    
                if column == 'Additives':
                if column == 'Additives':
                    self.finalData[rowindex:rowindex+numentries, colindex] = self.additives[indices]                
                    finalData[rowindex:rowindex+numentries, colindex-1] = self.additives[indices]                
                if column == 'Long Size (µm)':
                if column == 'Long Size (µm)':
                    self.finalData[rowindex:rowindex+numentries, colindex] = longSize[indices]
                    finalData[rowindex:rowindex+numentries, colindex-1] = longSize[indices]
                if column == 'Short Size (µm)':
                if column == 'Short Size (µm)':
                    self.finalData[rowindex:rowindex+numentries, colindex] = shortSize[indices]
                    finalData[rowindex:rowindex+numentries, colindex-1] = shortSize[indices]
                if column == 'Area (µm²)':
                if column == 'Area (µm²)':
                    self.finalData[rowindex:rowindex+numentries, colindex] = area[indices]
                    finalData[rowindex:rowindex+numentries, colindex-1] = area[indices]
                if column == 'HQI':
                # hit quality index array does not match the data size if particles have been combined
                    self.finalData[rowindex:rowindex+numentries, colindex] = self.hqis[indices]
                #if column == 'HQI':
                #    finalData[rowindex:rowindex+numentries, colindex-1] = self.hqis[indices]
            
            
            if '> 100 µm' in requiredcolumns:
            if '> 100 µm' in requiredcolumns:
                ##append size classes
                ##append size classes
                numPrevCols = len(requiredcolumns) - 6      #number of previous columns
                numPrevCols = len(requiredcolumns) - 1 - len(self.sizeClasses)      #number of previous columns
                for tableindex, dataindex in enumerate(indices):    
                for tableindex, dataindex in enumerate(np.arange(len(indices))[indices]):    
                    for classindex in range(len(self.sizeClasses)):
                    for classindex in range(len(self.sizeClasses)):
                        upLimit = self.sizeClasses[classindex]
                        upLimit = self.sizeClasses[classindex]
                        if classindex == 0: lowLimit = 0
                        if classindex == 0: lowLimit = 0
@@ -994,9 +970,9 @@ class ExpExcelDialog(QtWidgets.QWidget):
                        curSize = self.sizes[dataindex]
                        curSize = self.sizes[dataindex]
                        
                        
                        if  curSize > lowLimit and curSize <= upLimit:
                        if  curSize > lowLimit and curSize <= upLimit:
                            self.finalData[rowindex+tableindex, numPrevCols + classindex] = np.int(1)
                            finalData[rowindex+tableindex, numPrevCols + classindex] = np.int(1)
                        else:
                        else:
                            self.finalData[rowindex+tableindex, numPrevCols + classindex] = np.int(0)               
                            finalData[rowindex+tableindex, numPrevCols + classindex] = np.int(0)               
            
            
            rowindex = rowindex + numentries
            rowindex = rowindex + numentries
            
            
@@ -1014,32 +990,29 @@ class ExpExcelDialog(QtWidgets.QWidget):
        
        
        writer = pd.ExcelWriter(xlsname, engine = 'xlsxwriter')
        writer = pd.ExcelWriter(xlsname, engine = 'xlsxwriter')
        
        
        df = pd.DataFrame(self.finalData)
        df = pd.DataFrame(finalData, columns=requiredcolumns[1:])
        df.to_excel(writer, sheet_name = 'Individual Particles', header = False, index = False)
        df.insert(0, 'Polymer Type', polymertypes)
        df.to_excel(writer, sheet_name = 'Individual Particles', index = False)
        if  '> 100 µm' in requiredcolumns:
        if  '> 100 µm' in requiredcolumns:
            #generate particle statistics report
            #generate particle statistics report
            report = [['Polymer Type', '0 - 5 µm', '5 - 10 µm', '10 - 20 µm', '20 - 50 µm', '50 - 100 µm', '> 100 µm']]
            header = ['0 - 5 µm', '5 - 10 µm', '10 - 20 µm', '20 - 50 µm', '50 - 100 µm', '> 100 µm']
            index = np.unique(self.polymers)
            particleclasses = []
            
            
            for polymer in np.unique(self.polymers):
            for polymer in index:
                indices = np.where(self.polymers == polymer)[0]
                indices = np.where(self.polymers == polymer)[0]
                classes = np.array([0, 0, 0, 0, 0, 0])
                sortind = np.searchsorted([5,10,20,50,100], self.sizes[indices], 'right')
                for size in self.sizes[indices]:
                classes = np.bincount(sortind, minlength=6)
                    if size < 5: classes[0] += 1
                particleclasses.append(classes)
                    elif size < 10: classes[1] += 1
                    elif size < 20: classes[2] += 1
                    elif size < 50: classes[3] += 1
                    elif size < 100: classes[4] += 1
                    else: classes[5] += 1
                report.append([polymer, classes[0], classes[1], classes[2], classes[3], classes[4], classes[5]])
            
            report = pd.DataFrame(np.array(report))
            report.to_excel(writer, sheet_name = 'Particle Statistics', header = False, index = False)
        QtWidgets.QMessageBox.about(self, 'Done!', 'Particle Data exported')
            
            
    def closeEvent(self, event):
            particleclasses = np.array(particleclasses)
        if self.expWin is not None:
            report = pd.DataFrame(np.array(particleclasses), columns=header,
            self.expWin.close()
                                  dtype=int)
        event.accept()
            report.insert(0, 'Polymer Type', index)
            report.insert(len(report.columns), 'Sum total', particleclasses.sum(axis=1))
            report.to_excel(writer, sheet_name = 'Particle Statistics', index=False)
        writer.save()
        self.accept()




class AdditiveViewer(QtWidgets.QWidget):
class AdditiveViewer(QtWidgets.QWidget):
@@ -1066,6 +1039,6 @@ if __name__ == '__main__':
        app = QtWidgets.QApplication(sys.argv)
        app = QtWidgets.QApplication(sys.argv)
        meas = ParticleAnalysis(None)
        meas = ParticleAnalysis(None)
        meas.showMaximized()
        meas.showMaximized()
        ret = app.exec_()
        return app.exec_()
    
    
    run()
    run()
 No newline at end of file
+8 −1
Original line number Original line Diff line number Diff line
@@ -52,6 +52,8 @@ class ParticleEditor(object):
    def combineParticles(self, contourIndices, new_assignment):
    def combineParticles(self, contourIndices, new_assignment):
        if new_assignment == 'other':
        if new_assignment == 'other':
            new_assignment = self.getNewEntry()
            new_assignment = self.getNewEntry()
        if new_assignment is None:
            return
        
        
        contourIndices = sorted(contourIndices)   #we want to keep the contour with lowest index
        contourIndices = sorted(contourIndices)   #we want to keep the contour with lowest index
        print('merging contours:', contourIndices)
        print('merging contours:', contourIndices)
@@ -80,6 +82,9 @@ class ParticleEditor(object):
        
        
        img = np.uint8(cv2.morphologyEx(img, cv2.MORPH_CLOSE, np.ones((3, 3))))
        img = np.uint8(cv2.morphologyEx(img, cv2.MORPH_CLOSE, np.ones((3, 3))))
        
        
        if cv2.__version__ > '3.5':
            contours, hierarchy = cv2.findContours(img, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE)
        else:
            temp, contours, hierarchy = cv2.findContours(img, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE)
            temp, contours, hierarchy = cv2.findContours(img, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE)


        newContour = contours[0]
        newContour = contours[0]
@@ -139,6 +144,8 @@ class ParticleEditor(object):
    def reassignParticles(self, contourindices, new_assignment):
    def reassignParticles(self, contourindices, new_assignment):
        if new_assignment == 'other':
        if new_assignment == 'other':
            new_assignment = self.getNewEntry()
            new_assignment = self.getNewEntry()
        if new_assignment is None:
            return
            
            
        self.createSafetyBackup()
        self.createSafetyBackup()
        print(f'reassigning indices {contourindices} into {new_assignment}')
        print(f'reassigning indices {contourindices} into {new_assignment}')
+9 −2
Original line number Original line Diff line number Diff line
@@ -197,6 +197,9 @@ class Segmentation(object):
    def erodeConvexDefects(self, thresh, numiter):
    def erodeConvexDefects(self, thresh, numiter):
        thresh = cv2.copyMakeBorder(thresh, 1, 1, 1, 1, 0)
        thresh = cv2.copyMakeBorder(thresh, 1, 1, 1, 1, 0)
        for iterations in range(numiter):
        for iterations in range(numiter):
            if cv2.__version__ > '3.5':
                contours, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
            else:
                thresh2, contours, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
                thresh2, contours, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
            for cnt in contours:
            for cnt in contours:
                hull = cv2.convexHull(cnt, returnPoints = False)
                hull = cv2.convexHull(cnt, returnPoints = False)
@@ -387,6 +390,10 @@ class Segmentation(object):
        if return_step=="watershed":
        if return_step=="watershed":
            return np.uint8(255*(markers!=0)), 0  
            return np.uint8(255*(markers!=0)), 0  



        if cv2.__version__ > '3.5':        
            contours, hierarchy = cv2.findContours(markers, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE)
        else:
            temp, contours, hierarchy = cv2.findContours(markers, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE)
            temp, contours, hierarchy = cv2.findContours(markers, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE)
        print("contours")
        print("contours")
        if self.cancelcomputation:
        if self.cancelcomputation: