particleContainer.py 14 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 24
import os
from PyQt5 import QtWidgets
25
from typing import List, TYPE_CHECKING
26

27 28
from . import importSpectra
from .particleAndMeasurement import Particle, Measurement
29 30 31
if TYPE_CHECKING:
    from ..dataset import DataSet

32

JosefBrandt's avatar
JosefBrandt committed
33
class ParticleContainer(object):
34
    def __init__(self, datasetParent: 'DataSet'):
JosefBrandt's avatar
 
JosefBrandt committed
35
        super(ParticleContainer, self).__init__()
36 37 38 39
        self.datasetParent: 'DataSet' = datasetParent
        self.particles: List[Particle] = []
        self.measurements: List[Measurement] = []
        self.inconsistentParticles: List[Particle] = []
40
    
Josef Brandt's avatar
Josef Brandt committed
41
    def addEmptyMeasurement(self) -> int:
JosefBrandt's avatar
JosefBrandt committed
42 43 44
        newMeas = Measurement()
        self.measurements.append(newMeas)
        return self.measurements.index(newMeas)
45
    
Josef Brandt's avatar
Josef Brandt committed
46
    def clearParticles(self) -> None:
47 48
        self.particles = []
    
Josef Brandt's avatar
Josef Brandt committed
49
    def clearMeasurements(self) -> None:
50
        self.measurements = []
51 52
        for particle in self.particles:
            particle.measurements = []
53

54 55 56 57 58 59 60
    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
61 62 63 64 65 66 67 68 69 70
    def getMeasurementScanindex(self, indexOfMeasurement):
        return self.measurements[indexOfMeasurement].getScanIndex()
    
    def getSpectraFromDisk(self):
        spectra = None
        specPath = self.datasetParent.getSpectraFileName()
        
        if os.path.exists(specPath):
            spectra = np.load(specPath)
        else:
71 72
            
            fname = QtWidgets.QFileDialog.getOpenFileName(QtWidgets.QWidget(), 'Select Spectra File', self.datasetParent.path, 'text file (*.txt)')[0]
JosefBrandt's avatar
JosefBrandt committed
73 74
            if fname:
                #TODO: implement a more elegant way of testing through the individual imports...
75
                try:
JosefBrandt's avatar
JosefBrandt committed
76
                    spectra, spectraNames = importSpectra.importWITecSpectra(fname)
77
                except ImportError:
JosefBrandt's avatar
JosefBrandt committed
78 79 80 81 82 83 84
                    try:
                        spectra, spectraNames = importSpectra.importRenishawSpectra(fname)
                    except ImportError:
                        try:
                            spectra, spectraNames = importSpectra.importPerkinElmerSpectra(fname)
                        except ImportError:
                            pass
85
                
JosefBrandt's avatar
JosefBrandt committed
86 87 88
            if spectra is not None:
                np.save(self.datasetParent.getSpectraFileName(), spectra)
        return spectra
89
    
JosefBrandt's avatar
JosefBrandt committed
90 91 92
    def initializeParticles(self, numParticles):
        self.particles = []
        for i in range(numParticles):
93 94 95
            newParticle = Particle()
            newParticle.index = i
            self.particles.append(newParticle)
JosefBrandt's avatar
JosefBrandt committed
96
    
97
    def setParticleContours(self, contours):
JosefBrandt's avatar
JosefBrandt committed
98 99 100 101 102
        assert len(self.particles) == len(contours)
        for index, particle in enumerate(self.particles):
            particle.contour = contours[index]
        
    def setParticleStats(self, particlestats):
103
        assert len(self.particles) == len(particlestats), f'numParticles = {len(self.particles)}, len partStats = {len(particlestats)}'
JosefBrandt's avatar
JosefBrandt committed
104
        for index, particle in enumerate(self.particles):
105 106
            particle.__dict__.update(particlestats[index].__dict__)
            
107
    def testForInconsistentParticleAssignments(self): #i.e., particles that have multiple measurements with different assignments
JosefBrandt's avatar
 
JosefBrandt committed
108 109 110 111 112 113 114 115 116 117 118 119 120
        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
121 122 123 124 125 126 127 128
    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
129 130 131 132
    def applyHQITresholdToParticles(self, minHQI):
        for particle in self.particles:
            particle.applyHQITresholdToMeasurements(minHQI)
            
JosefBrandt's avatar
 
JosefBrandt committed
133
    def applyAssignmentListToParticleMeasurements(self, assignmentList):
JosefBrandt's avatar
 
JosefBrandt committed
134
        '''AssignmentList is list of spectra assignments in order of spectra indices'''
JosefBrandt's avatar
JosefBrandt committed
135 136 137 138
        assert len(assignmentList) == len(self.measurements), f'assertion error in assignment of results: {len(assignmentList)} results for {len(self.measurements)} spectra...'
        for meas in self.measurements:
            scanIndex = meas.getScanIndex()
            meas.setAssignment(assignmentList[scanIndex])
Josef Brandt's avatar
Josef Brandt committed
139 140 141 142 143 144 145

    def applyDatabaseResultsToParticleMeasurements(self, resultDictList: list) -> None:
        """
        The resultDictList has a dict for each measurement, containing multiple database results.
        Key: HQI, Val: Resultname
        """
        pass
JosefBrandt's avatar
 
JosefBrandt committed
146
    
JosefBrandt's avatar
 
JosefBrandt committed
147
    def applyHQIListToParticleMeasurements(self, hqiList):
JosefBrandt's avatar
 
JosefBrandt committed
148
        '''HQI-List is list of spectra hqis in order of spectra indices'''
JosefBrandt's avatar
JosefBrandt committed
149 150 151 152
        assert len(hqiList) == len(self.measurements), f'assertion error in assignment of hqis: {len(hqiList)} results for {len(self.measurements)} spectra...'
        for meas in self.measurements:
            scanIndex = meas.getScanIndex()
            meas.setHQI(hqiList[scanIndex])
JosefBrandt's avatar
 
JosefBrandt committed
153 154
    
    def getParticleOfIndex(self, index):
JosefBrandt's avatar
JosefBrandt committed
155 156
        try:
            particle = self.particles[index]
157
            assert particle.index == index, f'particle.index ({particle.index}) does not match requested index in particleList ({index})' 
JosefBrandt's avatar
JosefBrandt committed
158 159 160 161
        except:
            print('failed getting particle')
            print('requested Index:', index)
            print('len particles', len(self.particles))
162 163
            raise
        
JosefBrandt's avatar
 
JosefBrandt committed
164
        return particle
JosefBrandt's avatar
 
JosefBrandt committed
165 166 167 168 169 170
    
    def getParticleIndexContainingSpecIndex(self, index):
        for particle in self.particles:
            if index in particle.getMeasurementIndices():
                return particle.index
    
JosefBrandt's avatar
JosefBrandt committed
171 172 173
    def getNumberOfParticles(self):
        return len(self.particles)
    
JosefBrandt's avatar
JosefBrandt committed
174 175 176
    def getNumberOfMeasurements(self):
        return len(self.measurements)
    
JosefBrandt's avatar
 
JosefBrandt committed
177 178 179 180 181 182
    def getParticleContours(self):
        contours = [part.contour for part in self.particles]
        return contours
    
    def getParticleContoursByIndex(self, partIndices):
        contours = []
JosefBrandt's avatar
 
JosefBrandt committed
183 184 185
        for index in partIndices:
            particle = self.getParticleOfIndex(index)
            contours.append(particle.contour)
JosefBrandt's avatar
 
JosefBrandt committed
186 187
        return contours
    
188 189 190 191 192 193 194
    def getIndicesOfColoredNotUnknownParticles(self):
        coloredParticleIndices = []
        for particle in self.particles:
            if particle.getParticleAssignment() != 'unknown' and particle.color not in ['white', 'non-determinable']:
                coloredParticleIndices.append(particle.index)
        return coloredParticleIndices
    
JosefBrandt's avatar
 
JosefBrandt committed
195 196 197 198
    def getParticleAssignmentByIndex(self, partIndex):
        particle = self.getParticleOfIndex(partIndex)
        return particle.getParticleAssignment()
    
Josef Brandt's avatar
Josef Brandt committed
199 200
    def getMeasurementPixelCoords(self) -> list:
        coords: list = []
201
        for meas in self.measurements:
202
            coords.append([meas.pixelcoord_x, meas.pixelcoord_y])
JosefBrandt's avatar
JosefBrandt committed
203
        return coords
Josef Brandt's avatar
Josef Brandt committed
204 205 206 207 208 209 210 211 212

    def getApertureCenterCoords(self) -> list:
        coords: list = []
        for particle in self.particles:
            assert particle.aperture is not None, 'Aperture for particle was not set!'
            coords.append([particle.aperture.centerX, particle.aperture.centerY])
        return coords


JosefBrandt's avatar
 
JosefBrandt committed
213 214 215 216 217 218 219
    def getNumberOfParticlesOfAssignment(self, assignment):
        num = 0
        for particle in self.particles:
            if particle.getParticleAssignment() == assignment:
                num += 1
        return num
    
JosefBrandt's avatar
 
JosefBrandt committed
220 221 222 223 224 225 226 227
    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
228 229 230 231 232 233
    def getListOfParticleAssignments(self):
        particleAssignments = []
        for particle in self.particles:
            particleAssignments.append(particle.getParticleAssignment())
        return particleAssignments
    
JosefBrandt's avatar
 
JosefBrandt committed
234 235 236 237 238 239
    def getListOfHighestHQIs(self):
        hqis = []
        for particle in self.particles:
            hqis.append(particle.getHighestHQI())
        return hqis
    
JosefBrandt's avatar
 
JosefBrandt committed
240 241 242 243
    def getHQIOfSpectrumIndex(self, specIndex):
        for particle in self.particles:
            if specIndex in particle.getMeasurementIndices():
                return particle.getHQIOfMeasurementIndex(specIndex)
JosefBrandt's avatar
 
JosefBrandt committed
244 245
                    
    def getSizesOfAllParticles(self):
JosefBrandt's avatar
JosefBrandt committed
246
        particleSizes = []
JosefBrandt's avatar
 
JosefBrandt committed
247 248
        for particle in self.particles:
            particleSizes.append(particle.getParticleSize())
JosefBrandt's avatar
 
JosefBrandt committed
249
        return particleSizes
JosefBrandt's avatar
 
JosefBrandt committed
250 251 252 253
        
    def getShortSizesOfAllParticles(self):
        shortSizes = []
        for particle in self.particles:
JosefBrandt's avatar
JosefBrandt committed
254
            shortSizes.append(particle.getShortParticleSize())
JosefBrandt's avatar
 
JosefBrandt committed
255
        return shortSizes
JosefBrandt's avatar
 
JosefBrandt committed
256
    
JosefBrandt's avatar
JosefBrandt committed
257 258 259 260 261 262
    def getAreasOfAllParticles(self):
        areas = []
        for particle in self.particles:
            areas.append(particle.getArea())
        return areas
    
263 264 265 266 267 268
    def getColorsOfAllParticles(self):
        colors = []
        for particle in self.particles:
            colors.append(particle.color)
        return colors
    
269 270 271 272 273 274
    def getShapesOfAllParticles(self):
        shapes = []
        for particle in self.particles:
            shapes.append(particle.shape)
        return shapes
    
275 276 277 278
    def getParticleColorByIndex(self, particleIndex):
        particle = self.getParticleOfIndex(particleIndex)
        return particle.color
    
279 280 281 282
    def getParticleShapeByIndex(self, particleIndex):
        particle = self.getParticleOfIndex(particleIndex)
        return particle.shape
    
JosefBrandt's avatar
 
JosefBrandt committed
283 284 285 286 287
    def getSizesOfParticleType(self, assignment):
        particleSizes = []
        for particle in self.particles:
            if particle.getParticleAssignment() == assignment:
                particleSizes.append(particle.getParticleSize())
JosefBrandt's avatar
 
JosefBrandt committed
288
        return particleSizes
JosefBrandt's avatar
 
JosefBrandt committed
289
    
JosefBrandt's avatar
 
JosefBrandt committed
290 291
    def getIndicesOfParticleType(self, assignment):
        indices = []
JosefBrandt's avatar
 
JosefBrandt committed
292
        for particle in self.particles:
JosefBrandt's avatar
 
JosefBrandt committed
293 294 295 296 297 298 299
            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
300
    
JosefBrandt's avatar
 
JosefBrandt committed
301 302 303 304
    def getUniquePolymers(self):
        typeHist = self.getTypeHistogram()
        return typeHist.keys()

JosefBrandt's avatar
JosefBrandt committed
305
    def getTypeHistogram(self):
JosefBrandt's avatar
 
JosefBrandt committed
306
        uniquePolymers = np.unique(self.getListOfParticleAssignments())
JosefBrandt's avatar
JosefBrandt committed
307 308 309
        typehistogram = {i: 0 for i in uniquePolymers}
        for assignment in self.getListOfParticleAssignments():
            typehistogram[assignment] += 1
310
        # sort typehistogram, it will be converted into a list!!
JosefBrandt's avatar
JosefBrandt committed
311
        sorted_typehistogram = sorted(typehistogram.items(), key = operator.itemgetter(1), reverse = True)
312
        # convert back to dsetParticlecontoursict
JosefBrandt's avatar
JosefBrandt committed
313
        final_typehistogram = {i[0]: i[1] for i in sorted_typehistogram}
JosefBrandt's avatar
 
JosefBrandt committed
314
        return final_typehistogram
JosefBrandt's avatar
JosefBrandt committed
315
    
316 317 318 319 320
    def reassignParticleToAssignment(self, particleIndex, newAssignment):
        particle = self.getParticleOfIndex(particleIndex)
        particle.setAllSpectraToNewAssignment(newAssignment)
        particle.wasManuallyEdited = True
    
321 322 323
    def changeParticleColor(self, index, newColor):
        particle = self.getParticleOfIndex(index)
        particle.color = newColor
324
        particle.wasManuallyEdited = True
325
    
326 327 328
    def changeParticleShape(self, index, newShape):
        particle = self.getParticleOfIndex(index)
        particle.shape = newShape
329
        particle.wasManuallyEdited = True
330
    
JosefBrandt's avatar
JosefBrandt committed
331
    def addMergedParticle(self, particleIndices, newContour, newStats, newAssignment=None):
332
        newParticle = Particle()
333
        newParticle.contour = newContour
334 335 336 337 338 339 340
        #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)
341
                newParticle.addMeasurement(meas)
342 343

        newParticle.__dict__.update(newStats.__dict__)
344
        newParticle.wasManuallyEdited = True
JosefBrandt's avatar
 
JosefBrandt committed
345 346
        self.particles.append(newParticle)
        print('added new particle')
347
        
JosefBrandt's avatar
JosefBrandt committed
348 349 350
    def removeParticle(self, index):
        particle = self.getParticleOfIndex(index)   #just for asserting to have the correct particle!
        del self.particles[index]
351 352 353
            
    def resetParticleIndices(self):
        for newIndex, particle in enumerate(self.particles):
354
            particle.index = newIndex