loadresults.py 14 KB
Newer Older
1 2 3
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
JosefBrandt's avatar
 
JosefBrandt committed
4 5 6
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>    
7

JosefBrandt's avatar
 
JosefBrandt committed
8 9 10 11 12 13 14 15 16 17 18 19 20
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/>.
21 22 23
"""

import numpy as np
JosefBrandt's avatar
JosefBrandt committed
24 25
import os
from PyQt5 import QtWidgets, QtCore
26

JosefBrandt's avatar
JosefBrandt committed
27 28
class LoadTrueMatchResults(QtWidgets.QWidget):
    def __init__(self, particleContainer, analysisParent):
JosefBrandt's avatar
 
JosefBrandt committed
29
        super(LoadTrueMatchResults, self).__init__()
30 31 32 33 34
        self.setGeometry(400, 400, 200, 300)
        self.setWindowTitle('Get Truematch Results')
        self.layout = QtWidgets.QGridLayout()
        self.setLayout(self.layout)

JosefBrandt's avatar
 
JosefBrandt committed
35
        self.particleContainer = particleContainer
JosefBrandt's avatar
JosefBrandt committed
36 37
        self.analysisParent = analysisParent
        self.analysisParent.setDisabled(True)
38 39 40 41 42 43
        self.trueMatchResults = None
        self.polymertypes = None
        self.additives = None
        self.hqis = None
        self.addhqis = None
        
JosefBrandt's avatar
 
JosefBrandt committed
44 45
        self.loadBtn = QtWidgets.QPushButton('LoadTrueMatchResults')
        self.loadBtn.resize(self.loadBtn.sizeHint()) 
JosefBrandt's avatar
JosefBrandt committed
46
        self.loadBtn.clicked.connect(self.getImportFiles)
47 48 49 50 51 52 53 54 55 56 57
        
        self.reviewGroup = QtWidgets.QGroupBox('Review Changes')
        self.reviewGroup.setDisabled(True)
        reviewLayout = QtWidgets.QVBoxLayout()
        
        self.otherBtn = QtWidgets.QPushButton('manual overwrite')
        self.otherBtn.clicked.connect(self.show3FlagsReview)
        reviewLayout.addWidget(self.otherBtn)
        reviewLayout.addStretch()
        self.reviewGroup.setLayout(reviewLayout)

JosefBrandt's avatar
 
JosefBrandt committed
58
        self.layout.addWidget(self.loadBtn, 0, 0)
59 60 61 62 63
        self.layout.addWidget(self.reviewGroup, 2, 0)
        
        self.manualPolymers = {}
        self.manualAdditives = {}
        self.editEntryWindow = None
JosefBrandt's avatar
JosefBrandt committed
64
        self.sortWindow = None
65 66 67 68

        self.numFlagForTakeSpectrum = 1
        self.numFlagForSetUnknown = 2
        self.numFlagForPrompt = 3
JosefBrandt's avatar
JosefBrandt committed
69
        self.show()
70 71 72
    
    def show3FlagsReview(self):
        self.editEntryWindow = ModifyManualEdits(self, self.manualPolymers, self.manualAdditives)        
73
        self.setDisabled(True)
74 75
        self.editEntryWindow.show()

JosefBrandt's avatar
JosefBrandt committed
76 77 78
    def getImportFiles(self):
        dsetpath = self.analysisParent.dataset.path
        self.fnames =QtWidgets.QFileDialog.getOpenFileNames(self, 'Select TrueMatch result file', dsetpath, 'text file (*.txt)')[0]
79
        self.trueMatchResults = []
JosefBrandt's avatar
JosefBrandt committed
80 81 82 83 84 85 86 87 88
        if len(self.fnames) > 1:
            self.sortWindow = SortImportFiles(self, self.fnames)
        else:
            self.runCalculations()
            
    def runCalculations(self):
        allResults = []
        numSpectra = 0
        for fileindex, fname in enumerate(self.fnames):
89
            with open(fname) as file:
JosefBrandt's avatar
JosefBrandt committed
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
                currentResults = []
                for lineIndex, line in enumerate(file):
                    if lineIndex == 0:
                        line = line.strip().split(';')[:-1]         #disregard the last entry of each line, as each line ends with ; <- that produces an empty entry...
                        assert line[0] == 'Search Spectrum Name', 'Assertion Error in loadTrueMatchResults'
                        #detect, whether one- or multicomponent-search was done
                        if line[-1] == 'IsMarked':
                            numhits = np.int(line[-2].split(' ')[-1])
                            numcomps = 1
                        else:
                            numhits = np.int(line[-1].split(' ')[-1])
                            numcomps = np.int(line[-1].split(' ')[-3])
                    else:
                        currentResults.append(line)
                        numSpectra += 1
                        
                allResults.append(currentResults)

        if numSpectra > 0:
            print('{} components, {} hits per sample, {} spectra'.format(numcomps, numhits, numSpectra))        
        
        self.polymertypes = [None]*numSpectra
        self.hqis = [None]*numSpectra
        if numcomps > 1:
            self.additives = [None]*numSpectra
            self.addhqis = [None]*numSpectra
            
        addedIndices = []
        numSpectraInPreviousBatches = 0
        
        for resBatchIndex, resBatch in enumerate(allResults):
            print('spectra in batch:', len(resBatch))
            for result in resBatch:
                specIndex, polymertype, additive, hqi, addhqi = self.interpretEntry(resBatchIndex, numSpectraInPreviousBatches, result, numhits, numcomps)
                
                self.polymertypes[specIndex] = polymertype
                self.hqis[specIndex] = hqi
                if numcomps > 1:
                    self.additives[specIndex] = additive
                    self.addhqis[specIndex] = addhqi
                    
                addedIndices.append(specIndex)               
        
            numSpectraInPreviousBatches = len(addedIndices)
            print(numSpectraInPreviousBatches)
135
    
JosefBrandt's avatar
JosefBrandt committed
136 137 138 139 140 141
        assert sorted(addedIndices) == list(range(numSpectra)), f'mismatch in sorted indices in loadResults\bAdded {len(addedIndices)} assignments to {numSpectra} spectra'
        assert not None in self.polymertypes, 'wrong assignments in loadResults'
        assert not None in self.hqis, 'wrong assignments in loadResults'

        self.particleContainer.applyAssignmentListToParticleMeasurements(self.polymertypes)
        self.particleContainer.applyHQIListToParticleMeasurements(self.hqis)
142
        
JosefBrandt's avatar
JosefBrandt committed
143 144
        if len(self.manualPolymers) > 0:
            self.reviewGroup.setDisabled(False)
145
        
JosefBrandt's avatar
JosefBrandt committed
146 147 148
        self.analysisParent.applyHQIThresholdToResults()
        self.loadBtn.setText('Data loaded')
        QtWidgets.QMessageBox.about(self, 'Success', 'Results were imported successfully.\nYou can close this window or modify manual edits.')
149
    
JosefBrandt's avatar
JosefBrandt committed
150 151
    def interpretEntry(self, resBatchIndex, numSpectraInPreviousBatches, result, numhits, numcomps):
        result = result.split(';')
JosefBrandt's avatar
 
JosefBrandt committed
152 153 154
        specIndex, polymertype, additive, hqi, addhqi = None, None, None, None, 0   #assign defaults
        
        #specNameFormat is: SampleName_000_Spec.Data 1 (SpecIndex)
JosefBrandt's avatar
JosefBrandt committed
155 156 157
        specName = result[0]
        curSpecIndex = self.getSpecIndexFromName(specName)
        specIndex = numSpectraInPreviousBatches + curSpecIndex
JosefBrandt's avatar
 
JosefBrandt committed
158
        
159
        #find yes-flags
JosefBrandt's avatar
JosefBrandt committed
160
        flags = np.where(np.array(result) == 'yes')[0]
161 162 163
        if len(flags) == 0:
            #take highest HQI entry
            if numcomps == 1:
JosefBrandt's avatar
JosefBrandt committed
164 165
                polymertype = result[2]
                hqi = result[1]
166
            else:
JosefBrandt's avatar
JosefBrandt committed
167 168 169 170
                polymertype = result[5]
                hqi = result[1]
                additive = result[7]
                addhqi = result[6]
JosefBrandt's avatar
 
JosefBrandt committed
171

172 173 174
        elif len(flags) == 1:
            #exactly one flag was placed, take this entry
            if numcomps == 1:
JosefBrandt's avatar
JosefBrandt committed
175
                polymertype = result[int(flags[0])-1]
176 177
                hqi = 100
            else:
JosefBrandt's avatar
JosefBrandt committed
178 179
                polymertype = result[int(flags[0])+2]
                additive = result[int(flags[0])+4]
180 181 182 183 184 185 186 187 188 189 190 191
                hqi = 100
                addhqi = 100
            
        elif len(flags) == 2:
            polymertype = 'unknown'
            hqi = 0
            if numcomps > 1:
                additive = 'none'
                addhqi = 0
                
        elif len(flags) == 3:
            hqi = 100
JosefBrandt's avatar
JosefBrandt committed
192 193 194
            if (curSpecIndex, resBatchIndex) not in self.manualPolymers:
                polymertype, ok = QtWidgets.QInputDialog.getText(self, 'Name of main component', 'Spectrum at index {} of result file {} is:'.format(curSpecIndex, resBatchIndex+1))
                self.manualPolymers[(curSpecIndex, resBatchIndex)] = polymertype
195
            else:
JosefBrandt's avatar
JosefBrandt committed
196
                polymertype = self.manualPolymers[(curSpecIndex, resBatchIndex)]
197 198
            if numcomps > 1:
                addhqi = 100
JosefBrandt's avatar
JosefBrandt committed
199 200 201
                if (curSpecIndex, resBatchIndex) not in self.manualAdditives:
                    additive, ok = QtWidgets.QInputDialog.getText(self, 'Name of additive', 'Additive at index {} of result file {} is:'.format(curSpecIndex, resBatchIndex+1))
                    self.manualAdditives[(curSpecIndex, resBatchIndex)] = additive
202
                else:
JosefBrandt's avatar
JosefBrandt committed
203
                    additive = self.manualAdditives[(curSpecIndex, resBatchIndex)]
204
        else: 
JosefBrandt's avatar
 
JosefBrandt committed
205
            QtWidgets.QMessageBox.about(self, 'Error!', 'No rule for {} flags, found at spectrum index {}'.format(len(flags), specIndex))
206
            
JosefBrandt's avatar
 
JosefBrandt committed
207
        return specIndex, polymertype, additive, float(hqi), float(addhqi)
208
    
JosefBrandt's avatar
JosefBrandt committed
209 210 211 212 213 214 215 216 217 218 219
    def getSpecIndexFromName(self, specName):
        number = ''
        for i in reversed(range(len(specName))):
            if specName[i] == ')':
                continue
            if specName[i] != '(':
                number = specName[i] + number
            else:
                break
        return int(number)
    
220
    def closeEvent(self, event):
JosefBrandt's avatar
JosefBrandt committed
221 222 223 224 225
        for win in [self.editEntryWindow, self.sortWindow]:
            if win is not None:
                win.close()
                
        self.analysisParent.setEnabled(True)
226 227 228
        event.accept()


JosefBrandt's avatar
JosefBrandt committed
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279
class SortImportFiles(QtWidgets.QWidget):
    def __init__(self, loadResultsParent, importFiles):
        super(SortImportFiles, self).__init__()
        self.setWindowTitle('Sort Import Files')
        self.loadResultsParent = loadResultsParent
        self.loadResultsParent.setDisabled(True)
        self.importFiles = importFiles
        self.spinBoxes = []
        layout = QtWidgets.QGridLayout()
        self.setLayout(layout)
        
        label1 = QtWidgets.QLabel('File name')
        label1.setMinimumWidth(150)
        label2 = QtWidgets.QLabel('Order')
        label2.setMinimumWidth(50)
        
        layout.addWidget(label1, 0, 0, QtCore.Qt.AlignLeft)
        layout.addWidget(label2, 0, 1, QtCore.Qt.AlignRight)
        
        for fileIndex, importFile in enumerate(self.importFiles):
            layout.addWidget(QtWidgets.QLabel(os.path.basename(importFile)), 1+fileIndex, 0, QtCore.Qt.AlignLeft)
            newSpinBox = QtWidgets.QSpinBox()
            newSpinBox.setMaximumWidth(50)
            newSpinBox.setMinimum(1)
            newSpinBox.setMaximum(len(self.importFiles))
            newSpinBox.setValue(fileIndex+1)
            layout.addWidget(newSpinBox, 1+fileIndex, 1, QtCore.Qt.AlignRight)
            self.spinBoxes.append(newSpinBox)
        
        self.acceptBtn = QtWidgets.QPushButton('Accept and Close')
        self.acceptBtn.clicked.connect(self.sortResultFiles)
        layout.addWidget(self.acceptBtn, fileIndex+2, 0, QtCore.Qt.AlignLeft)
        
        self.show()
    
    def sortResultFiles(self):
        order = [box.value()-1 for box in self.spinBoxes]
        if list(np.unique(order)) != list(range(len(self.spinBoxes))):
            QtWidgets.QMessageBox.critical(self, 'Error', 'Invalid order indicated')
            return
        else:
            newFiles = []
            for index in order:
                newFiles.append(self.importFiles[index])
            
            self.loadResultsParent.fnames = newFiles
            self.loadResultsParent.runCalculations()
            self.loadResultsParent.setDisabled(False)
            self.close()
            
            
280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298
class ModifyManualEdits(QtWidgets.QWidget):
    def __init__(self, parentWindow, polymerEdits, additiveEdits = None):
        super(ModifyManualEdits, self).__init__()
        self.setWindowTitle('Edit Manual Changes')
        self.setGeometry(200, 200, 800, 500)
        layout = QtWidgets.QVBoxLayout()
        self.setLayout(layout)
        
        self.parent = parentWindow
        self.polymerEdits = polymerEdits
        self.additiveEdits = additiveEdits
        self.labels = []
        self.polymerLineEdits = []
        self.additiveLineEdits = []
        
        groupBox = QtWidgets.QGroupBox('Manually entered edits')
        groupLayout = QtWidgets.QGridLayout()

        #create Labels and LineEdits        
JosefBrandt's avatar
JosefBrandt committed
299 300
        for curSpecIndex, resBatchIndex in self.polymerEdits:
            self.labels.append(QtWidgets.QLabel(f'Index of Spectrum: {curSpecIndex}, in file number: {resBatchIndex+1}'))
301
            self.polymerLineEdits.append(QtWidgets.QLineEdit())
JosefBrandt's avatar
JosefBrandt committed
302
            self.polymerLineEdits[-1].setText(self.polymerEdits[(curSpecIndex, resBatchIndex)])
303 304
            if len(self.additiveEdits) > 0:
               self.additiveLineEdits.append(QtWidgets.QLineEdit()) 
JosefBrandt's avatar
JosefBrandt committed
305
               self.additiveLineEdits[-1].setText(self.additiveEdits[(curSpecIndex, resBatchIndex)])
306 307 308 309 310 311 312 313 314 315 316 317 318 319 320
        
        for i in range(len(self.labels)):
            groupLayout.addWidget(self.labels[i], i, 0)
            groupLayout.addWidget(self.polymerLineEdits[i], i, 1)
            if len(self.additiveEdits) > 0:
                groupLayout.addWidget(self.additiveLineEdits[i], i, 2)
        
        groupBox.setLayout(groupLayout)
        
        scrollarea = QtWidgets.QScrollArea()
        scrollarea.setWidget(groupBox)
        scrollarea.setMaximumHeight(600)
        layout.addWidget(scrollarea)
        
        saveAndCloseBtn = QtWidgets.QPushButton('Save and Exit')
JosefBrandt's avatar
JosefBrandt committed
321
        saveAndCloseBtn.clicked.connect(self.saveAndClose)
322 323
        layout.addWidget(saveAndCloseBtn)

JosefBrandt's avatar
JosefBrandt committed
324
    def saveAndClose(self):
325 326 327 328 329 330 331 332 333 334
        #read out LineEdits:
        for index, key in enumerate(self.polymerEdits):
            self.polymerEdits[key] = self.polymerLineEdits[index].text()
            if len(self.additiveEdits) > 0:
                self.additiveEdits[key] = self.additiveLineEdits[index].text()
        
        #copy values to parent Window
        self.parent.manualPolymers, self.parent.manualAdditives = self.polymerEdits, self.additiveEdits
        self.parent.runCalculations()
        self.close()