particleInfo.py 15 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
# -*- coding: utf-8 -*-
"""
GEPARD - Gepard-Enabled PARticle Detection
Copyright (C) 2018  Lars Bittrich and Josef Brandt, Leibniz-Institut für 
Polymerforschung Dresden e. V. <bittrich-lars@ipfdd.de>    

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program, see COPYING.  
If not, see <https://www.gnu.org/licenses/>.
"""
import numpy as np
import operator
23

JosefBrandt's avatar
JosefBrandt committed
24
class ParticleContainer(object):
JosefBrandt's avatar
JosefBrandt committed
25
    def __init__(self, datasetParent):
JosefBrandt's avatar
 
JosefBrandt committed
26
        super(ParticleContainer, self).__init__()
JosefBrandt's avatar
JosefBrandt committed
27
        self.datasetParent = datasetParent
JosefBrandt's avatar
JosefBrandt committed
28 29
        self.particles = []
        self.spectra = None
JosefBrandt's avatar
 
JosefBrandt committed
30
        self.inconsistentParticles = []
JosefBrandt's avatar
JosefBrandt committed
31 32
        
        self.typeHistogram = None
33

JosefBrandt's avatar
JosefBrandt committed
34 35 36
    def initializeParticles(self, numParticles):
        self.particles = []
        for i in range(numParticles):
37 38 39
            newParticle = Particle()
            newParticle.index = i
            self.particles.append(newParticle)
JosefBrandt's avatar
JosefBrandt committed
40
    
41
    def setParticleContours(self, contours):
JosefBrandt's avatar
JosefBrandt committed
42 43 44 45 46 47 48 49
        assert len(self.particles) == len(contours)
        for index, particle in enumerate(self.particles):
            particle.contour = contours[index]
        
    def setParticleStats(self, particlestats):
        assert len(self.particles) == len(particlestats)
        #particlestats is list of [long, short, longellipse, shortellipse, cv2.contourArea(cnt)]
        for index, particle in enumerate(self.particles):
JosefBrandt's avatar
 
JosefBrandt committed
50 51 52 53 54
            particle.longSize_box = float(particlestats[index][0])
            particle.shortSize_box = float(particlestats[index][1])
            particle.longSize_ellipse = float(particlestats[index][2])
            particle.shortSize_ellipse = float(particlestats[index][3])
            particle.area = float(particlestats[index][4])
JosefBrandt's avatar
JosefBrandt committed
55
    
JosefBrandt's avatar
 
JosefBrandt committed
56 57 58 59 60 61 62 63 64 65 66 67 68 69
    def testForInconsistentParticles(self): #i.e., particles that have multiple measurements with different assignments
        self.inconsistentParticles = []
        for particle in self.particles:
            if not particle.measurementsHaveSameOrigAssignment():
                self.inconsistentParticles.append(particle)
        if len(self.inconsistentParticles) > 0:
            print('Warning, inconsistent particles found!')
            for particle in self.inconsistentParticles:
                print(f'Particle with index {particle.index} has the following assignments:')
                for assignment in particle.getOrigMeasurementAssignments():
                    print(assignment)
        else:
            print('All particles have consistent spectra assignments')
    
JosefBrandt's avatar
 
JosefBrandt committed
70 71 72 73 74 75 76 77
    def applyPixelScaleToParticleStats(self, pixelscale):
        for index, particle in enumerate(self.particles):
            particle.longSize_box *= pixelscale
            particle.shortSize_box *= pixelscale
            particle.longSize_ellipse *= pixelscale
            particle.shortSize_ellipse *= pixelscale
            particle.area *= pixelscale**2

JosefBrandt's avatar
JosefBrandt committed
78 79 80 81
    def applyHQITresholdToParticles(self, minHQI):
        for particle in self.particles:
            particle.applyHQITresholdToMeasurements(minHQI)
            
JosefBrandt's avatar
 
JosefBrandt committed
82
    def applyAssignmentListToParticleMeasurements(self, assignmentList):
JosefBrandt's avatar
 
JosefBrandt committed
83 84
        '''AssignmentList is list of spectra assignments in order of spectra indices'''
        indicesOfTransferredAssignments = [None]*len(assignmentList)
JosefBrandt's avatar
 
JosefBrandt committed
85 86 87 88
        for particle in self.particles:
            for meas in particle.getMeasurements():
                scanIndex = meas.getScanIndex()
                meas.setAssignment(assignmentList[scanIndex])
JosefBrandt's avatar
 
JosefBrandt committed
89 90
                indicesOfTransferredAssignments[scanIndex] = scanIndex
        assert not None in indicesOfTransferredAssignments
JosefBrandt's avatar
 
JosefBrandt committed
91
    
JosefBrandt's avatar
 
JosefBrandt committed
92
    def applyHQIListToParticleMeasurements(self, hqiList):
JosefBrandt's avatar
 
JosefBrandt committed
93 94
        '''HQI-List is list of spectra hqis in order of spectra indices'''
        indicesOfTransferredHQIs = [None]*len(hqiList)
JosefBrandt's avatar
 
JosefBrandt committed
95 96 97
        for particle in self.particles:
            for meas in particle.getMeasurements():
                scanIndex = meas.getScanIndex()
JosefBrandt's avatar
 
JosefBrandt committed
98 99 100
                meas.setHQI(hqiList[scanIndex])
                indicesOfTransferredHQIs[scanIndex] = scanIndex
        assert not None in indicesOfTransferredHQIs
JosefBrandt's avatar
 
JosefBrandt committed
101
    
JosefBrandt's avatar
 
JosefBrandt committed
102 103 104 105
    def reassignParticleToAssignment(self, particleIndex, newAssignment):
        particle = self.getParticleOfIndex(particleIndex)
        particle.setAllSpectraToNewAssignment(newAssignment)
    
JosefBrandt's avatar
 
JosefBrandt committed
106 107 108 109
    def getParticleOfIndex(self, index):
        particle = self.particles[index]
        assert particle.index == index, f'particle.index ({particle.index}) does match requested index in particleList ({index})' 
        return particle
JosefBrandt's avatar
 
JosefBrandt committed
110 111 112 113 114 115
    
    def getParticleIndexContainingSpecIndex(self, index):
        for particle in self.particles:
            if index in particle.getMeasurementIndices():
                return particle.index
    
JosefBrandt's avatar
JosefBrandt committed
116 117 118
    def getNumberOfParticles(self):
        return len(self.particles)
    
JosefBrandt's avatar
 
JosefBrandt committed
119 120 121 122 123 124
    def getParticleContours(self):
        contours = [part.contour for part in self.particles]
        return contours
    
    def getParticleContoursByIndex(self, partIndices):
        contours = []
JosefBrandt's avatar
 
JosefBrandt committed
125 126 127
        for index in partIndices:
            particle = self.getParticleOfIndex(index)
            contours.append(particle.contour)
JosefBrandt's avatar
 
JosefBrandt committed
128 129
        return contours
    
JosefBrandt's avatar
 
JosefBrandt committed
130 131 132 133
    def getParticleAssignmentByIndex(self, partIndex):
        particle = self.getParticleOfIndex(partIndex)
        return particle.getParticleAssignment()
    
JosefBrandt's avatar
JosefBrandt committed
134 135 136
    def getMeasurementPixelCoords(self):
        coords = []
        for particle in self.particles:
JosefBrandt's avatar
 
JosefBrandt committed
137
            for measurement in particle.getMeasurements():
JosefBrandt's avatar
JosefBrandt committed
138 139 140
                coords.append([measurement.pixelcoord_x, measurement.pixelcoord_y])
        return coords
    
JosefBrandt's avatar
 
JosefBrandt committed
141 142 143 144 145 146 147
    def getNumberOfParticlesOfAssignment(self, assignment):
        num = 0
        for particle in self.particles:
            if particle.getParticleAssignment() == assignment:
                num += 1
        return num
    
JosefBrandt's avatar
 
JosefBrandt committed
148 149 150 151 152 153 154 155
    def getNumberOfSpectraOfParticle(self, particleIndex):
        particle = self.getParticleOfIndex(particleIndex)
        return particle.getNumberOfMeasurements()

    def getSpectraIndicesOfParticle(self, particleIndex):
        particle = self.getParticleOfIndex(particleIndex)
        return particle.getMeasurementIndices()
        
JosefBrandt's avatar
JosefBrandt committed
156 157 158 159 160 161
    def getListOfParticleAssignments(self):
        particleAssignments = []
        for particle in self.particles:
            particleAssignments.append(particle.getParticleAssignment())
        return particleAssignments
    
JosefBrandt's avatar
 
JosefBrandt committed
162 163 164 165 166 167
    def getListOfHighestHQIs(self):
        hqis = []
        for particle in self.particles:
            hqis.append(particle.getHighestHQI())
        return hqis
    
JosefBrandt's avatar
 
JosefBrandt committed
168 169 170 171
    def getHQIOfSpectrumIndex(self, specIndex):
        for particle in self.particles:
            if specIndex in particle.getMeasurementIndices():
                return particle.getHQIOfMeasurementIndex(specIndex)
JosefBrandt's avatar
 
JosefBrandt committed
172 173
                    
    def getSizesOfAllParticles(self):
JosefBrandt's avatar
JosefBrandt committed
174
        particleSizes = []
JosefBrandt's avatar
 
JosefBrandt committed
175 176
        for particle in self.particles:
            particleSizes.append(particle.getParticleSize())
JosefBrandt's avatar
 
JosefBrandt committed
177
        return particleSizes
JosefBrandt's avatar
 
JosefBrandt committed
178 179 180 181
        
    def getShortSizesOfAllParticles(self):
        shortSizes = []
        for particle in self.particles:
JosefBrandt's avatar
 
JosefBrandt committed
182 183
            shortSizes.append(particle.getShortParticleSize())#
        return shortSizes
JosefBrandt's avatar
 
JosefBrandt committed
184 185 186 187 188 189
    
    def getSizesOfParticleType(self, assignment):
        particleSizes = []
        for particle in self.particles:
            if particle.getParticleAssignment() == assignment:
                particleSizes.append(particle.getParticleSize())
JosefBrandt's avatar
 
JosefBrandt committed
190
        return particleSizes
JosefBrandt's avatar
 
JosefBrandt committed
191
    
JosefBrandt's avatar
 
JosefBrandt committed
192 193
    def getIndicesOfParticleType(self, assignment):
        indices = []
JosefBrandt's avatar
 
JosefBrandt committed
194
        for particle in self.particles:
JosefBrandt's avatar
 
JosefBrandt committed
195 196 197 198 199 200 201
            if particle.getParticleAssignment() == assignment:
                indices.append(particle.index)
        return indices
    
    def getSizeOfParticleByIndex(self, index):
        particle = self.getParticleOfIndex(index)
        return particle.getParticleSize()
JosefBrandt's avatar
JosefBrandt committed
202
    
JosefBrandt's avatar
 
JosefBrandt committed
203 204 205 206
    def getUniquePolymers(self):
        typeHist = self.getTypeHistogram()
        return typeHist.keys()

JosefBrandt's avatar
JosefBrandt committed
207
    def getTypeHistogram(self):
JosefBrandt's avatar
 
JosefBrandt committed
208
        uniquePolymers = np.unique(self.getListOfParticleAssignments())
JosefBrandt's avatar
JosefBrandt committed
209 210 211 212 213
        typehistogram = {i: 0 for i in uniquePolymers}
        for assignment in self.getListOfParticleAssignments():
            typehistogram[assignment] += 1
        ##sort typehistogram, it will be converted into a list!!
        sorted_typehistogram = sorted(typehistogram.items(), key = operator.itemgetter(1), reverse = True)
214
        #convert back to dsetParticlecontoursict
JosefBrandt's avatar
JosefBrandt committed
215
        final_typehistogram = {i[0]: i[1] for i in sorted_typehistogram}
JosefBrandt's avatar
 
JosefBrandt committed
216
        return final_typehistogram
JosefBrandt's avatar
JosefBrandt committed
217
    
JosefBrandt's avatar
JosefBrandt committed
218
    def addMergedParticle(self, particleIndices, newContour, newStats, newAssignment=None):
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
        newParticle = Particle()
        #copy Measurements
        for index in particleIndices:
            particle = self.getParticleOfIndex(index)
            for meas in particle.getMeasurements():
                if newAssignment is not None:
                    meas.setAssignment(newAssignment)
                    meas.setHQI(100)
                newParticle.addExistingMeasurement(meas)
        
        #set Particle Stats
        long, short, longellipse, shortellipse, area = newStats
        newParticle.longSize_box = long
        newParticle.shortSize_box = short
        newParticle.longSize_ellipse = longellipse
        newParticle.shortSize_ellipse = shortellipse
        newParticle.area = area
        newParticle.contour = newContour
        
JosefBrandt's avatar
 
JosefBrandt committed
238 239
        self.particles.append(newParticle)
        print('added new particle')
240
        
JosefBrandt's avatar
JosefBrandt committed
241 242 243
    def removeParticle(self, index):
        particle = self.getParticleOfIndex(index)   #just for asserting to have the correct particle!
        del self.particles[index]
244 245 246 247
            
    def resetParticleIndices(self):
        for newIndex, particle in enumerate(self.particles):
            particle.index = newIndex
JosefBrandt's avatar
JosefBrandt committed
248 249

class Particle(object):
250 251 252
    def __init__(self):
        super(Particle, self).__init__()
        self.index = None
JosefBrandt's avatar
 
JosefBrandt committed
253 254 255 256
        self.longSize_ellipse = np.nan
        self.shortSize_ellipse = np.nan
        self.longSize_box = np.nan
        self.shortSize_box = np.nan
JosefBrandt's avatar
JosefBrandt committed
257 258 259
        self.area = None
        self.contour = None
        self.measurements = []
JosefBrandt's avatar
JosefBrandt committed
260
        self.viewItem = None
261 262 263 264
        
    def addExistingMeasurement(self, meas):
        self.measurements.append(meas)
    
JosefBrandt's avatar
JosefBrandt committed
265 266 267 268 269 270 271 272 273 274 275 276
    def addEmptyMeasurement(self):
        self.measurements.append(Measurement())
        indexOfNewMeasurment = len(self.measurements)-1
        return indexOfNewMeasurment
    
    def setMeasurementScanIndex(self, indexOfMeasurment, scanIndex):
        self.measurements[indexOfMeasurment].ramanScanIndex = scanIndex
    
    def setMeasurementPixelCoords(self, indexOfMeasurment, x, y):
        self.measurements[indexOfMeasurment].pixelcoord_x= x
        self.measurements[indexOfMeasurment].pixelcoord_y = y

JosefBrandt's avatar
 
JosefBrandt committed
277 278 279 280 281
    def setAllSpectraToNewAssignment(self, newAssignment):
        for meas in self.measurements:
            meas.setAssignment(newAssignment)
            meas.setHQI(100)

JosefBrandt's avatar
JosefBrandt committed
282
    def getParticleAssignment(self):
JosefBrandt's avatar
 
JosefBrandt committed
283 284 285 286 287 288 289
        return self.getMeasAssignmentWithHighestHQI()   #probably another method could be more suitable...
    
    def getHighestHQI(self):
        hqis = []
        for meas in self.measurements:
            hqis.append(meas.getHQI())
        return max(hqis)
JosefBrandt's avatar
JosefBrandt committed
290
    
JosefBrandt's avatar
 
JosefBrandt committed
291 292 293 294 295 296 297 298 299 300 301
    def getHQIOfMeasurementIndex(self, index):
        for meas in self.measurements:
            if meas.ramanScanIndex == index:
                return meas.getHQI()
    
    def getMeasurementIndices(self):
        indices = []
        for meas in self.measurements:
            indices.append(meas.ramanScanIndex)
        return indices
    
JosefBrandt's avatar
 
JosefBrandt committed
302 303 304 305 306 307 308 309 310 311 312 313
    def getMeasurements(self):
        return self.measurements
    
    def getMeasAssignmentWithHighestHQI(self):
        hqis = []
        assignments = []
        for meas in self.measurements:
            hqis.append(meas.getHQI())
            assignments.append(meas.getAssignment())
        indexOfHighestHQI = hqis.index(max(hqis))
        return assignments[indexOfHighestHQI]
        
JosefBrandt's avatar
JosefBrandt committed
314
    def getParticleSize(self):
JosefBrandt's avatar
 
JosefBrandt committed
315 316 317 318
        if not np.isnan(self.longSize_ellipse):
            size = self.longSize_ellipse
        elif not np.isnan(self.longSize_box):
            size = self.longSize_box
JosefBrandt's avatar
JosefBrandt committed
319 320 321
        else:
            print(f'Error, particle size requested, but not yet set.\nParticle Index is {self.index}')
            raise ValueError
JosefBrandt's avatar
 
JosefBrandt committed
322 323
        assert size is not None, f'Error, size or particle {self.index} is None'
        return round(size)
JosefBrandt's avatar
JosefBrandt committed
324
    
JosefBrandt's avatar
 
JosefBrandt committed
325
    def getShortParticleSize(self):
JosefBrandt's avatar
 
JosefBrandt committed
326
        if not np.isnan(self.shortSize_ellipse):
JosefBrandt's avatar
 
JosefBrandt committed
327
            return self.shortSize_ellipse
JosefBrandt's avatar
 
JosefBrandt committed
328
        elif not np.isnan(self.shortSize_box):
JosefBrandt's avatar
 
JosefBrandt committed
329
            return self.shortSize_box
JosefBrandt's avatar
 
JosefBrandt committed
330 331 332 333
        else:
            print(f'Error, particle size requested, but not yet set.\nParticle Index is {self.index}')
            raise ValueError
    
JosefBrandt's avatar
 
JosefBrandt committed
334 335
    def getNumberOfMeasurements(self):
        return len(self.measurements)
JosefBrandt's avatar
 
JosefBrandt committed
336 337 338
    
    def measurementsHaveSameOrigAssignment(self):
        allResults = [meas.getOrigAssignment() for meas in self.measurements]
JosefBrandt's avatar
JosefBrandt committed
339 340 341 342 343
        if len(np.unique(allResults)) == 1:
            return True
        elif len(np.unique(allResults)) > 1:
            return False

JosefBrandt's avatar
 
JosefBrandt committed
344 345 346 347
    def getOrigMeasurementAssignments(self):
        assignments = [meas.getOrigAssignment() for meas in self.measurements]
        return assignments

JosefBrandt's avatar
JosefBrandt committed
348 349
    def applyHQITresholdToMeasurements(self, minHQI):
        for measurement in self.measurements:
JosefBrandt's avatar
 
JosefBrandt committed
350
            measurement.applyHQIThreshold(minHQI)
JosefBrandt's avatar
JosefBrandt committed
351
    
JosefBrandt's avatar
JosefBrandt committed
352 353 354
#    def recreateViewItem(self):
#        pass
            
JosefBrandt's avatar
JosefBrandt committed
355 356 357
    
class Measurement(object):
    def __init__(self):
358
        super(Measurement, self).__init__()
JosefBrandt's avatar
JosefBrandt committed
359 360 361 362 363 364 365 366
        self.ramanScanIndex = None
        self.pixelcoord_x= None
        self.pixelcoord_y = None
        
        self.assignment_orig = 'Not Evaluated'
        self.assignment_afterHQI = None
        self.hqi = None
    
JosefBrandt's avatar
 
JosefBrandt committed
367
    def setAssignment(self, assignment):
JosefBrandt's avatar
JosefBrandt committed
368
        self.assignment_orig = assignment
369
        self.applyHQIThreshold()
JosefBrandt's avatar
 
JosefBrandt committed
370 371
        
    def setHQI(self, hqi):
JosefBrandt's avatar
JosefBrandt committed
372 373 374 375
        self.hqi = hqi
        self.applyHQIThreshold()
    
    def applyHQIThreshold(self, minHQI=0):
376 377 378 379 380
        if self.hqi is not None:    #i.e. skip for initial setup, when hqi is not yet aplied...
            if self.hqi >= minHQI:
                self.assignment_afterHQI = self.assignment_orig
            else:
                self.assignment_afterHQI = 'unknown'
JosefBrandt's avatar
 
JosefBrandt committed
381 382 383 384
    
    def getHQI(self):
        return self.hqi
    
JosefBrandt's avatar
JosefBrandt committed
385 386 387 388 389 390
    def getAssignment(self):
        if self.assignment_afterHQI is None:
            return self.assignment_orig
        else:
            return self.assignment_afterHQI
        
JosefBrandt's avatar
 
JosefBrandt committed
391 392 393 394
    def getOrigAssignment(self):
        return self.assignment_orig
    
    def getScanIndex(self):
JosefBrandt's avatar
 
JosefBrandt committed
395
        return self.ramanScanIndex