particleContainer.py 16.3 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
from .particleAndMeasurement import Particle, Measurement
Josef Brandt's avatar
Josef Brandt committed
28 29 30 31 32 33
specImportEnabled: bool = True
try:
    from ..gepardevaluation.analysis import importSpectra
except ModuleNotFoundError:
    specImportEnabled = False

34 35 36
if TYPE_CHECKING:
    from ..dataset import DataSet

37

JosefBrandt's avatar
JosefBrandt committed
38
class ParticleContainer(object):
Josef Brandt's avatar
Josef Brandt committed
39
    def __init__(self):
JosefBrandt's avatar
 
JosefBrandt committed
40
        super(ParticleContainer, self).__init__()
Josef Brandt's avatar
Josef Brandt committed
41
        self.datasetParent: 'DataSet' = None
42 43 44
        self.particles: List[Particle] = []
        self.measurements: List[Measurement] = []
        self.inconsistentParticles: List[Particle] = []
Josef Brandt's avatar
Josef Brandt committed
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61

    def setDataSet(self, ds):
        """
        dynamically sets a reference to the dataset containing this particle container
        :param ds:
        :return:
        """
        self.datasetParent = ds

    def unsetDataSet(self):
        """
        is called before dataset is saved, preventing pickle to resolve this reference to dataset into
        a copy of it (baking its data into the save file)
        :return:
        """
        self.datasetParent = None

Josef Brandt's avatar
Josef Brandt committed
62
    def addEmptyMeasurement(self) -> int:
JosefBrandt's avatar
JosefBrandt committed
63 64 65
        newMeas = Measurement()
        self.measurements.append(newMeas)
        return self.measurements.index(newMeas)
66
    
Josef Brandt's avatar
Josef Brandt committed
67
    def clearParticles(self) -> None:
68 69
        self.particles = []
    
Josef Brandt's avatar
Josef Brandt committed
70
    def clearMeasurements(self) -> None:
71
        self.measurements = []
72 73
        for particle in self.particles:
            particle.measurements = []
74

75
    def setMeasurementScanIndex(self, indexOfMeasurment, scanIndex):
76
        self.measurements[indexOfMeasurment].specScanIndex = scanIndex
77 78 79 80 81
    
    def setMeasurementPixelCoords(self, indexOfMeasurment, x, y):
        self.measurements[indexOfMeasurment].pixelcoord_x = x
        self.measurements[indexOfMeasurment].pixelcoord_y = y
    
Josef Brandt's avatar
Josef Brandt committed
82
    def getMeasurementScanindex(self, indexOfMeasurement) -> int:
JosefBrandt's avatar
JosefBrandt committed
83
        return self.measurements[indexOfMeasurement].getScanIndex()
Josef Brandt's avatar
Josef Brandt committed
84 85

    def getSpectraFromDisk(self) -> np.ndarray:
JosefBrandt's avatar
JosefBrandt committed
86
        spectra = None
Josef Brandt's avatar
Josef Brandt committed
87 88 89 90 91 92 93 94
        if specImportEnabled:
            specPath = self.datasetParent.getSpectraFileName()
            if os.path.exists(specPath):
                spectra = np.load(specPath)
            else:
                fname = QtWidgets.QFileDialog.getOpenFileName(QtWidgets.QWidget(), 'Select Spectra File',
                                                              self.datasetParent.path, 'text file (*.txt)')[0]
                if fname:
JosefBrandt's avatar
JosefBrandt committed
95
                    try:
Josef Brandt's avatar
Josef Brandt committed
96 97 98 99 100 101 102 103 104 105
                        instruments = importSpectra.listInstruments()
                        instrument, okPressed = QtWidgets.QInputDialog.getItem(QtWidgets.QWidget(), "File format",
                                                                               "Select instrument:", instruments, 0, False)
                        if okPressed and instrument:
                            spectra, spectraNames = importSpectra.chooseInstrument(instrument, fname)
                    except (ValueError, ImportError):
                        QtWidgets.QMessageBox.warning(QtWidgets.QWidget(), 'Error', 'Unknown format, no spectra loaded.')

                if spectra is not None:
                    np.save(self.datasetParent.getSpectraFileName(), spectra)
JosefBrandt's avatar
JosefBrandt committed
106
        return spectra
107
    
JosefBrandt's avatar
JosefBrandt committed
108 109 110
    def initializeParticles(self, numParticles):
        self.particles = []
        for i in range(numParticles):
111 112 113
            newParticle = Particle()
            newParticle.index = i
            self.particles.append(newParticle)
JosefBrandt's avatar
JosefBrandt committed
114
    
115
    def setParticleContours(self, contours):
JosefBrandt's avatar
JosefBrandt committed
116 117 118 119 120
        assert len(self.particles) == len(contours)
        for index, particle in enumerate(self.particles):
            particle.contour = contours[index]
        
    def setParticleStats(self, particlestats):
121
        assert len(self.particles) == len(particlestats), f'numParticles = {len(self.particles)}, len partStats = {len(particlestats)}'
JosefBrandt's avatar
JosefBrandt committed
122
        for index, particle in enumerate(self.particles):
123 124
            particle.__dict__.update(particlestats[index].__dict__)
            
Josef Brandt's avatar
Josef Brandt committed
125 126
    def testForInconsistentParticleAssignments(self):
        # Find particles that have multiple measurements with different assignments
JosefBrandt's avatar
 
JosefBrandt committed
127 128 129 130 131 132 133 134 135 136 137 138 139
        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
140 141 142 143 144 145 146 147
    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
148 149 150 151
    def applyHQITresholdToParticles(self, minHQI):
        for particle in self.particles:
            particle.applyHQITresholdToMeasurements(minHQI)
            
JosefBrandt's avatar
 
JosefBrandt committed
152
    def applyAssignmentListToParticleMeasurements(self, assignmentList):
JosefBrandt's avatar
 
JosefBrandt committed
153
        '''AssignmentList is list of spectra assignments in order of spectra indices'''
JosefBrandt's avatar
JosefBrandt committed
154 155 156 157
        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
158 159 160 161 162 163 164

    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
165
    
JosefBrandt's avatar
 
JosefBrandt committed
166
    def applyHQIListToParticleMeasurements(self, hqiList):
JosefBrandt's avatar
 
JosefBrandt committed
167
        '''HQI-List is list of spectra hqis in order of spectra indices'''
JosefBrandt's avatar
JosefBrandt committed
168 169 170 171
        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
172 173
    
    def getParticleOfIndex(self, index):
JosefBrandt's avatar
JosefBrandt committed
174 175
        try:
            particle = self.particles[index]
176
            assert particle.index == index, f'particle.index ({particle.index}) does not match requested index in particleList ({index})' 
JosefBrandt's avatar
JosefBrandt committed
177 178 179 180
        except:
            print('failed getting particle')
            print('requested Index:', index)
            print('len particles', len(self.particles))
181 182
            raise
        
JosefBrandt's avatar
 
JosefBrandt committed
183
        return particle
Josef Brandt's avatar
Josef Brandt committed
184 185 186 187 188 189 190 191 192 193 194 195 196

    def getParticleOfUID(self, uid):
        try:
            for particle in self.particles:
                if uid == particle.uid:
                    break
        except:
            print('failed getting particle')
            print('requested Index:', uid)
            print('len particles', len(self.particles))
        assert particle.uid == uid, f'particle.index ({particle.uid}) does match requested index in particleList ({uid})'
        return particle

JosefBrandt's avatar
 
JosefBrandt committed
197 198 199 200 201
    def getParticleIndexContainingSpecIndex(self, index):
        for particle in self.particles:
            if index in particle.getMeasurementIndices():
                return particle.index
    
JosefBrandt's avatar
JosefBrandt committed
202 203 204
    def getNumberOfParticles(self):
        return len(self.particles)
    
JosefBrandt's avatar
JosefBrandt committed
205 206 207
    def getNumberOfMeasurements(self):
        return len(self.measurements)
    
JosefBrandt's avatar
 
JosefBrandt committed
208 209 210 211 212 213
    def getParticleContours(self):
        contours = [part.contour for part in self.particles]
        return contours
    
    def getParticleContoursByIndex(self, partIndices):
        contours = []
JosefBrandt's avatar
 
JosefBrandt committed
214 215 216
        for index in partIndices:
            particle = self.getParticleOfIndex(index)
            contours.append(particle.contour)
JosefBrandt's avatar
 
JosefBrandt committed
217 218
        return contours
    
219 220 221 222 223 224 225
    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
226 227 228
    def getParticleAssignmentByIndex(self, partIndex):
        particle = self.getParticleOfIndex(partIndex)
        return particle.getParticleAssignment()
Josef Brandt's avatar
Josef Brandt committed
229 230 231 232

    def getParticleAssignmentByUID(self, uid):
        particle = self.getParticleOfUID(uid)
        return particle.getParticleAssignment()
JosefBrandt's avatar
 
JosefBrandt committed
233
    
Josef Brandt's avatar
Josef Brandt committed
234 235
    def getMeasurementPixelCoords(self) -> list:
        coords: list = []
236
        for meas in self.measurements:
237
            coords.append([meas.pixelcoord_x, meas.pixelcoord_y])
JosefBrandt's avatar
JosefBrandt committed
238
        return coords
Josef Brandt's avatar
Josef Brandt committed
239 240 241 242 243 244 245 246

    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
247 248 249 250 251 252 253
    def getNumberOfParticlesOfAssignment(self, assignment):
        num = 0
        for particle in self.particles:
            if particle.getParticleAssignment() == assignment:
                num += 1
        return num
    
JosefBrandt's avatar
 
JosefBrandt committed
254 255 256 257 258 259 260
    def getNumberOfSpectraOfParticle(self, particleIndex):
        particle = self.getParticleOfIndex(particleIndex)
        return particle.getNumberOfMeasurements()

    def getSpectraIndicesOfParticle(self, particleIndex):
        particle = self.getParticleOfIndex(particleIndex)
        return particle.getMeasurementIndices()
Josef Brandt's avatar
Josef Brandt committed
261 262 263 264

    def getSpectraIndicesOfParticleByUID(self, particleUID):
        particle = self.getParticleOfUID(particleUID)
        return particle.getMeasurementIndices()
JosefBrandt's avatar
 
JosefBrandt committed
265
        
JosefBrandt's avatar
JosefBrandt committed
266 267 268 269 270 271
    def getListOfParticleAssignments(self):
        particleAssignments = []
        for particle in self.particles:
            particleAssignments.append(particle.getParticleAssignment())
        return particleAssignments
    
JosefBrandt's avatar
 
JosefBrandt committed
272 273 274 275 276 277
    def getListOfHighestHQIs(self):
        hqis = []
        for particle in self.particles:
            hqis.append(particle.getHighestHQI())
        return hqis
    
JosefBrandt's avatar
 
JosefBrandt committed
278 279 280 281
    def getHQIOfSpectrumIndex(self, specIndex):
        for particle in self.particles:
            if specIndex in particle.getMeasurementIndices():
                return particle.getHQIOfMeasurementIndex(specIndex)
JosefBrandt's avatar
 
JosefBrandt committed
282 283
                    
    def getSizesOfAllParticles(self):
JosefBrandt's avatar
JosefBrandt committed
284
        particleSizes = []
JosefBrandt's avatar
 
JosefBrandt committed
285 286
        for particle in self.particles:
            particleSizes.append(particle.getParticleSize())
JosefBrandt's avatar
 
JosefBrandt committed
287
        return particleSizes
JosefBrandt's avatar
 
JosefBrandt committed
288 289 290 291
        
    def getShortSizesOfAllParticles(self):
        shortSizes = []
        for particle in self.particles:
JosefBrandt's avatar
JosefBrandt committed
292
            shortSizes.append(particle.getShortParticleSize())
JosefBrandt's avatar
 
JosefBrandt committed
293
        return shortSizes
JosefBrandt's avatar
 
JosefBrandt committed
294
    
JosefBrandt's avatar
JosefBrandt committed
295 296 297 298 299 300
    def getAreasOfAllParticles(self):
        areas = []
        for particle in self.particles:
            areas.append(particle.getArea())
        return areas
    
301 302 303 304 305 306
    def getColorsOfAllParticles(self):
        colors = []
        for particle in self.particles:
            colors.append(particle.color)
        return colors
    
307 308 309 310 311 312
    def getShapesOfAllParticles(self):
        shapes = []
        for particle in self.particles:
            shapes.append(particle.shape)
        return shapes
    
313 314 315 316
    def getParticleColorByIndex(self, particleIndex):
        particle = self.getParticleOfIndex(particleIndex)
        return particle.color
    
317 318 319
    def getParticleShapeByIndex(self, particleIndex):
        particle = self.getParticleOfIndex(particleIndex)
        return particle.shape
Josef Brandt's avatar
Josef Brandt committed
320 321 322 323 324 325 326 327 328

    def getParticleIndexByUID(self, uid):
        for idx, particle in enumerate(self.particles):
            if uid == particle.uid:
                break

        assert uid == particle.uid
        return idx

JosefBrandt's avatar
 
JosefBrandt committed
329 330 331 332 333
    def getSizesOfParticleType(self, assignment):
        particleSizes = []
        for particle in self.particles:
            if particle.getParticleAssignment() == assignment:
                particleSizes.append(particle.getParticleSize())
JosefBrandt's avatar
 
JosefBrandt committed
334
        return particleSizes
JosefBrandt's avatar
 
JosefBrandt committed
335
    
JosefBrandt's avatar
 
JosefBrandt committed
336 337
    def getIndicesOfParticleType(self, assignment):
        indices = []
JosefBrandt's avatar
 
JosefBrandt committed
338
        for particle in self.particles:
JosefBrandt's avatar
 
JosefBrandt committed
339 340 341
            if particle.getParticleAssignment() == assignment:
                indices.append(particle.index)
        return indices
Josef Brandt's avatar
Josef Brandt committed
342 343 344 345 346 347 348 349

    def getUIDsOfParticleType(self, assignment):
        ids = []
        for particle in self.particles:
            if particle.getParticleAssignment() == assignment:
                ids.append(particle.uid)
        return ids

JosefBrandt's avatar
 
JosefBrandt committed
350 351 352
    def getSizeOfParticleByIndex(self, index):
        particle = self.getParticleOfIndex(index)
        return particle.getParticleSize()
JosefBrandt's avatar
JosefBrandt committed
353
    
JosefBrandt's avatar
 
JosefBrandt committed
354 355 356 357
    def getUniquePolymers(self):
        typeHist = self.getTypeHistogram()
        return typeHist.keys()

JosefBrandt's avatar
JosefBrandt committed
358
    def getTypeHistogram(self):
JosefBrandt's avatar
 
JosefBrandt committed
359
        uniquePolymers = np.unique(self.getListOfParticleAssignments())
JosefBrandt's avatar
JosefBrandt committed
360 361 362
        typehistogram = {i: 0 for i in uniquePolymers}
        for assignment in self.getListOfParticleAssignments():
            typehistogram[assignment] += 1
363
        # sort typehistogram, it will be converted into a list!!
JosefBrandt's avatar
JosefBrandt committed
364
        sorted_typehistogram = sorted(typehistogram.items(), key = operator.itemgetter(1), reverse = True)
365
        # convert back to dsetParticlecontoursict
JosefBrandt's avatar
JosefBrandt committed
366
        final_typehistogram = {i[0]: i[1] for i in sorted_typehistogram}
JosefBrandt's avatar
 
JosefBrandt committed
367
        return final_typehistogram
JosefBrandt's avatar
JosefBrandt committed
368
    
369 370 371 372 373
    def reassignParticleToAssignment(self, particleIndex, newAssignment):
        particle = self.getParticleOfIndex(particleIndex)
        particle.setAllSpectraToNewAssignment(newAssignment)
        particle.wasManuallyEdited = True
    
374 375 376
    def changeParticleColor(self, index, newColor):
        particle = self.getParticleOfIndex(index)
        particle.color = newColor
377
        particle.wasManuallyEdited = True
Josef Brandt's avatar
Josef Brandt committed
378

379 380
    def changeParticleShape(self, index, newShape):
        particle = self.getParticleOfIndex(index)
Josef Brandt's avatar
Josef Brandt committed
381 382 383 384 385 386

        if 'fibre' == newShape:
            particle.longSize, particle.shortSize = pc.getFibreDimension(particle.contour)
            particle.longSize *= self.datasetParent.getPixelScale()
            particle.shortSize *= self.datasetParent.getPixelScale()

387
        particle.shape = newShape
388
        particle.wasManuallyEdited = True
389
    
Josef Brandt's avatar
Josef Brandt committed
390
    def addMergedParticle(self, particleIndices, newContour, newStats, newAssignment=None) -> Particle:
391
        newParticle = Particle()
392
        newParticle.contour = newContour
Josef Brandt's avatar
Josef Brandt committed
393
        # copy Measurements
394 395 396 397 398 399
        for index in particleIndices:
            particle = self.getParticleOfIndex(index)
            for meas in particle.getMeasurements():
                if newAssignment is not None:
                    meas.setAssignment(newAssignment)
                    meas.setHQI(100)
400
                newParticle.addMeasurement(meas)
401 402

        newParticle.__dict__.update(newStats.__dict__)
403
        newParticle.wasManuallyEdited = True
JosefBrandt's avatar
 
JosefBrandt committed
404
        self.particles.append(newParticle)
Josef Brandt's avatar
Josef Brandt committed
405 406 407 408 409 410
        return newParticle

    def removeParticles(self, indices):
        assert isinstance(indices, list)
        for idx in indices:
            self.removeParticle(idx)
411
        
JosefBrandt's avatar
JosefBrandt committed
412 413 414
    def removeParticle(self, index):
        particle = self.getParticleOfIndex(index)   #just for asserting to have the correct particle!
        del self.particles[index]
415 416 417
            
    def resetParticleIndices(self):
        for newIndex, particle in enumerate(self.particles):
Josef Brandt's avatar
Josef Brandt committed
418
            particle.index = newIndex