Commit 3de80ba6 authored by Josef Brandt's avatar Josef Brandt

Subsampling results displayed in GUI

parent e2b9a501
......@@ -13,6 +13,7 @@ import concurrent.futures
import sys
sys.path.append("C://Users//xbrjos//Desktop//Python")
from gepard import dataset
from gepard.analysis.particleAndMeasurement import Particle
from helpers import ParticleBinSorter
import methods as meth
......@@ -42,7 +43,7 @@ def get_methods_to_test(dataset: dataset.DataSet, fractions: list = []) -> list:
boxCreator: gmeth.BoxSelectionCreator = gmeth.BoxSelectionCreator(dataset)
methods += boxCreator.get_crossBoxSubsamplers_for_fraction(fraction)
methods += boxCreator.get_spiralBoxSubsamplers_for_fraction(fraction)
methods.append(cmeth.ChemometricSubsampling(particleContainer, fraction))
# methods.append(cmeth.ChemometricSubsampling(particleContainer, fraction))
return methods
......@@ -53,6 +54,17 @@ def update_sample(sample, force: bool, index: int):
sample.update_result_with_methods(methods, force)
return sample, index
def is_MP_particle(particle: Particle) -> bool:
# TODO: UPDATE PATTERNS -> ARE THESE REASONABLE???
isMP: bool = False
mpPatterns = ['poly', 'rubber', 'pb', 'pr', 'pg', 'py', 'pv']
assignment = particle.getParticleAssignment()
for pattern in mpPatterns:
if assignment.lower().find(pattern) != -1:
isMP = True
break
return isMP
class TotalResults(object):
def __init__(self):
......@@ -85,7 +97,8 @@ class TotalResults(object):
indices: list = list(np.arange(len(self.sampleResults)))
numSamples: int = len(forceList)
numWorkers: int = 4 # in case of quadcore processor that seams reasonable??
chunksize: int = numSamples // numWorkers
chunksize: int = int(round(numSamples / numWorkers * 0.7)) # we want to have slightly more chunks than workers
print(f'multiprocessing with {numSamples} samples and chunksize of {chunksize}')
with concurrent.futures.ProcessPoolExecutor() as executor:
results = executor.map(update_sample, self.sampleResults, forceList, indices, chunksize=chunksize)
......@@ -137,15 +150,17 @@ class SubsamplingResult(object):
"""
Stores all interesting results from a subsampling experiment
"""
# TODO: UPDATE PATTERNS -> ARE THESE REASONABLE???
mpPatterns = ['poly', 'rubber', 'pb', 'pr', 'pg', 'py', 'pv']
# # # TODO: UPDATE PATTERNS -> ARE THESE REASONABLE???
# mpPatterns = ['poly', 'rubber', 'pb', 'pr', 'pg', 'py', 'pv']
def __init__(self, subsamplingMethod: meth.SubsamplingMethod):
super(SubsamplingResult, self).__init__()
self.method: meth.SubsamplingMethod = subsamplingMethod
self.mpCountErrors: list = []
# self.origParticleCount: int = None
# self.subSampledParticleCount: int = None
self.origParticleCount: int = 0
self.subSampledParticleCount: int = 0
self.origMPCount: int = 0
self.estimMPCounts: list = []
# self.mpCountErrorPerBin: tuple = None
@property
......@@ -159,15 +174,20 @@ class SubsamplingResult(object):
def mpCountErrorStDev(self) -> float:
stdev: float = 0.0
if len(self.mpCountErrors) > 0:
stdev = np.std(self.mpCountErrors)
stdev = float(np.std(self.mpCountErrors))
return stdev
@property
def estimMPCount(self) -> float:
return float(np.mean(self.estimMPCounts))
def reset_results(self) -> None:
"""
Deletes all results
:return:
"""
self.mpCountErrors = []
self.estimMPCounts = []
def add_result(self, origParticles: list, subParticles: list) -> None:
"""
......@@ -181,6 +201,7 @@ class SubsamplingResult(object):
# error: float = self._get_mp_count_error(origParticles, subParticles, 1.0)
# else:
error: float = self._get_mp_count_error(origParticles, subParticles, self.method.fraction)
self.origParticleCount = len(origParticles)
self.mpCountErrors.append(error)
def _get_mp_count_error_per_bin(self, allParticles: list, subParticles: list, fractionMeasured: float) -> tuple:
......@@ -194,7 +215,9 @@ class SubsamplingResult(object):
def _get_mp_count_error(self, allParticles: list, subParticles: list, fractionMeasured: float) -> float:
numMPOrig = self._get_number_of_MP_particles(allParticles)
self.origMPCount = numMPOrig
numMPEstimate = self._get_number_of_MP_particles(subParticles) / fractionMeasured
self.estimMPCounts.append(numMPEstimate)
if numMPOrig != 0:
mpCountError = self._get_error_from_values(numMPOrig, numMPEstimate)
......@@ -212,12 +235,8 @@ class SubsamplingResult(object):
def _get_number_of_MP_particles(self, particleList: list) -> int:
numMPParticles = 0
for particle in particleList:
assignment = particle.getParticleAssignment()
for pattern in self.mpPatterns:
if assignment.lower().find(pattern) != -1:
numMPParticles += 1
break
if is_MP_particle(particle):
numMPParticles += 1
return numMPParticles
......
......@@ -6,7 +6,7 @@ import gepard
from gepard import dataset
import helpers
from cythonModules import rotateContour as rc
from evaluation import is_MP_particle
class FilterView(QtWidgets.QGraphicsView):
......@@ -65,7 +65,7 @@ class FilterView(QtWidgets.QGraphicsView):
self._remove_particle_contours()
if self.dataset is not None:
for particle in self.dataset.particleContainer.particles:
newContour: ParticleContour = ParticleContour(particle.contour)
newContour: ParticleContour = ParticleContour(particle.contour, is_MP_particle(particle))
self.scene().addItem(newContour)
self.contourItems.append(newContour)
......@@ -167,12 +167,13 @@ class MeasureBoxGraphItem(QtWidgets.QGraphicsItem):
class ParticleContour(QtWidgets.QGraphicsItem):
def __init__(self, contourData, pos=(0, 0)) -> None:
def __init__(self, contourData, isMP: bool = False, pos: tuple = (0, 0)) -> None:
super(ParticleContour, self).__init__()
self.setZValue(1)
self.setPos(pos[0], pos[1])
self.brect: QtCore.QRectF = QtCore.QRectF(0, 0, 1, 1)
self.isMP: bool = isMP
self.isMeasured: bool = False # Wether the particle overlaps with a measuring box or nt
self.contourData = contourData
self.polygon = None
......@@ -199,11 +200,13 @@ class ParticleContour(QtWidgets.QGraphicsItem):
def paint(self, painter, option, widget) -> None:
if self.polygon is not None:
if self.isMeasured:
if self.isMP:
painter.setPen(QtCore.Qt.darkRed)
painter.setBrush(QtCore.Qt.red)
else:
painter.setPen(QtCore.Qt.darkCyan)
painter.setBrush(QtCore.Qt.cyan)
painter.setPen(QtCore.Qt.darkBlue)
painter.setBrush(QtCore.Qt.blue)
painter.setOpacity(1 if self.isMeasured else 0.2)
painter.drawPolygon(self.polygon)
from PyQt5 import QtWidgets
from PyQt5 import QtWidgets, QtCore
import sys
sys.path.append("C://Users//xbrjos//Desktop//Python")
import gepard
......@@ -6,6 +6,7 @@ from gepard import dataset
from gui.filterView import FilterView
from gui.measureModes import MeasureMode, CrossBoxMode, CrossBoxesControls, SpiralBoxMode
import helpers
from evaluation import SubsamplingResult
class MainView(QtWidgets.QWidget):
......@@ -30,7 +31,7 @@ class MainView(QtWidgets.QWidget):
self.rotationSpinBox.setMaximum(359)
self.rotationSpinBox.setValue(0)
self.rotationSpinBox.setMaximumWidth(50)
self.rotationSpinBox.valueChanged.connect(self._update_fiter_rotation)
self.rotationSpinBox.valueChanged.connect(self._update_filter_rotation)
self.controlGroup = QtWidgets.QGroupBox()
self.controlGroupLayout = QtWidgets.QHBoxLayout()
......@@ -44,7 +45,10 @@ class MainView(QtWidgets.QWidget):
self.controlGroupLayout.addWidget(self.activeModeControl)
self.controlGroupLayout.addStretch()
self.infoWidget: SampleInfoWidget = SampleInfoWidget()
self.layout.addWidget(self.controlGroup)
self.layout.addWidget(self.infoWidget)
self.filterView = FilterView()
self.layout.addWidget(self.filterView)
self._add_measure_modes()
......@@ -55,6 +59,8 @@ class MainView(QtWidgets.QWidget):
self.measureModes['crossSelection'] = CrossBoxMode(self.filterView)
self.modeSelector.addItem('spiralSelection')
self.modeSelector.addItem('crossSelection')
for mode in self.measureModes.values():
mode.updatedResult.connect(self.infoWidget.update_results)
def _switch_to_default_mode(self) -> None:
modes: list = list(self.measureModes.keys())
......@@ -95,17 +101,43 @@ class MainView(QtWidgets.QWidget):
self.filterView.filter.update_filterSize(width, height, diameter, (offsetx, offsety))
for mode in self.measureModes.values():
mode.boxGenerator.particleContainer = dset.particleContainer
mode.boxGenerator.offset = (offsetx, offsety)
mode.boxSelectionMethod.particleContainer = dset.particleContainer
mode.boxSelectionMethod.offset = (offsetx, offsety)
self.filterView.update_from_dataset(dset)
self.activeMode.update_measure_viewItems()
self.infoWidget.samplename = dset.name
self.infoWidget.update_label()
def _update_fiter_rotation(self):
def _update_filter_rotation(self):
self.filterView.update_rotation(self.rotationSpinBox.value())
self.activeMode.send_measuredParticles_to_filterview()
class SampleInfoWidget(QtWidgets.QLabel):
def __init__(self):
super(SampleInfoWidget, self).__init__()
self.samplename: str = ''
self.result: SubsamplingResult = None
self.update_label()
@QtCore.pyqtSlot(SubsamplingResult)
def update_results(self, result: SubsamplingResult):
self.result = result
self.update_label()
def update_label(self) -> None:
if self.samplename == '':
self.setText('No sample loaded')
else:
mpFrac: float = round(self.result.origMPCount / self.result.origParticleCount * 100, 1)
self.setText(f'Sample: {self.samplename}, {self.result.origParticleCount} particles, '
f'MP Fraction = {mpFrac} %, '
f'MP Count Error = {round(self.result.mpCountError, 1)} % '
f'| MP Particle Count: Orig: {self.result.origMPCount}, '
f'Estimated: {round(self.result.estimMPCount)}')
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
......
from PyQt5 import QtCore, QtWidgets
from gui.filterView import FilterView, MeasureBoxGraphItem
from geometricMethods import BoxSelectionSubsamplingMethod, CrossBoxSubSampling, SpiralBoxSubsampling
from evaluation import SubsamplingResult
class MeasureMode(QtCore.QObject):
updatedResult: QtCore.pyqtSignal = QtCore.pyqtSignal(SubsamplingResult)
def __init__(self, relatedFilterView: FilterView):
super(MeasureMode, self).__init__()
self.filterView: FilterView = relatedFilterView
self.uiControls: QtWidgets.QGroupBox = QtWidgets.QGroupBox()
self.boxGenerator: BoxSelectionSubsamplingMethod = None
self.boxSelectionMethod: BoxSelectionSubsamplingMethod = None
self.subsamplingResult: SubsamplingResult = SubsamplingResult(self.boxSelectionMethod)
self.subParticles: list = []
def get_control_groupBox(self) -> QtWidgets.QGroupBox:
......@@ -18,8 +22,12 @@ class MeasureMode(QtCore.QObject):
raise NotImplementedError
def send_measuredParticles_to_filterview(self) -> None:
if self.boxGenerator.particleContainer is not None:
subParticles = self.boxGenerator.apply_subsampling_method()
if self.boxSelectionMethod.particleContainer is not None:
subParticles = self.boxSelectionMethod.apply_subsampling_method()
self.subsamplingResult.method = self.boxSelectionMethod
self.subsamplingResult.reset_results()
self.subsamplingResult.add_result(self.boxSelectionMethod.particleContainer.particles, subParticles)
self.updatedResult.emit(self.subsamplingResult)
self.filterView.update_measured_particles(subParticles)
......@@ -27,23 +35,23 @@ class CrossBoxMode(MeasureMode):
def __init__(self, *args):
super(CrossBoxMode, self).__init__(*args)
self.uiControls = CrossBoxesControls(self)
self.boxGenerator: CrossBoxSubSampling = CrossBoxSubSampling(None)
self.boxSelectionMethod: CrossBoxSubSampling = CrossBoxSubSampling(None)
self.update_measure_viewItems()
def update_measure_viewItems(self) -> None:
self.boxGenerator.filterDiameter = self.filterView.filter.diameter
self.boxGenerator.numBoxesAcross = int(self.uiControls.numBoxesSelector.currentText())
self.boxSelectionMethod.filterDiameter = self.filterView.filter.diameter
self.boxSelectionMethod.numBoxesAcross = int(self.uiControls.numBoxesSelector.currentText())
desiredCoverage: int = self.uiControls.coverageSpinbox.value()
maxCoverage: int = int(self.boxGenerator.get_maximum_achievable_fraction() * 100)
maxCoverage: int = int(self.boxSelectionMethod.get_maximum_achievable_fraction() * 100)
self.uiControls.set_to_max_possible_coverage(maxCoverage)
if desiredCoverage > maxCoverage:
desiredCoverage = maxCoverage
self.boxGenerator.fraction = desiredCoverage / 100
self.boxSelectionMethod.fraction = desiredCoverage / 100
topLefts: list = self.boxGenerator.get_topLeft_of_boxes()
boxSize = self.boxGenerator.boxSize
topLefts: list = self.boxSelectionMethod.get_topLeft_of_boxes()
boxSize = self.boxSelectionMethod.boxSize
self.filterView.update_measure_boxes(topLefts, boxSize)
self.send_measuredParticles_to_filterview()
......@@ -92,17 +100,17 @@ class SpiralBoxMode(MeasureMode):
def __init__(self, *args):
super(SpiralBoxMode, self).__init__(*args)
self.uiControls: SpiralBoxControls = SpiralBoxControls(self)
self.boxGenerator: SpiralBoxSubsampling = SpiralBoxSubsampling(None)
self.boxSelectionMethod: SpiralBoxSubsampling = SpiralBoxSubsampling(None)
self.update_measure_viewItems()
def update_measure_viewItems(self) -> None:
self.boxGenerator.filterDiameter = self.filterView.filter.diameter
self.boxSelectionMethod.filterDiameter = self.filterView.filter.diameter
self.boxGenerator.numBoxes = self.uiControls.numBoxesSpinbox.value()
self.boxGenerator.fraction = self.uiControls.coverageSpinbox.value() / 100
self.boxSelectionMethod.numBoxes = self.uiControls.numBoxesSpinbox.value()
self.boxSelectionMethod.fraction = self.uiControls.coverageSpinbox.value() / 100
topLefts: list = self.boxGenerator.get_topLeft_of_boxes()
boxSize = self.boxGenerator.boxSize
topLefts: list = self.boxSelectionMethod.get_topLeft_of_boxes()
boxSize = self.boxSelectionMethod.boxSize
self.filterView.update_measure_boxes(topLefts, boxSize)
self.send_measuredParticles_to_filterview()
......
......@@ -23,7 +23,6 @@ def timingDecorator(callingFunction):
class ParticleBinSorter(object):
def __init__(self):
super(ParticleBinSorter, self).__init__()
self.bins = [5, 10, 20, 50, 100, 200, 500]
......
......@@ -10,26 +10,26 @@ SET GEPARD TO EVALUATION BRANCH (WITHOUT THE TILING STUFF), OTHERWISE SOME OF TH
"""
if __name__ == '__main__':
results: TotalResults = TotalResults()
pklsInFolders = get_pkls_from_directory(r'C:\Users\xbrjos\Desktop\temp MP\NewDatasets')
for folder in pklsInFolders.keys():
for samplePath in pklsInFolders[folder]:
newSampleResult: SampleResult = results.add_sample(samplePath)
for attr in get_attributes_from_foldername(folder):
newSampleResult.set_attribute(attr)
t0 = time.time()
results.update_all()
print('updating all took', time.time()-t0, 'seconds')
save_results('results1.res', results)
# results: TotalResults = load_results('results1.res')
# results: TotalResults = TotalResults()
# pklsInFolders = get_pkls_from_directory(r'C:\Users\xbrjos\Desktop\temp MP\NewDatasets')
#
# for folder in pklsInFolders.keys():
# for samplePath in pklsInFolders[folder]:
# newSampleResult: SampleResult = results.add_sample(samplePath)
# for attr in get_attributes_from_foldername(folder):
# newSampleResult.set_attribute(attr)
#
# t0 = time.time()
# results.update_all()
# print('updating all took', time.time()-t0, 'seconds')
#
# save_results('results1.res', results)
results: TotalResults = load_results('results1.res')
# save_results('results1.res', results)
plt.clf()
errorPerFraction: dict = results.get_error_vs_fraction_data(attributes=['air', 'water'],
methods=['random', 'sizeBin', 'chemo'])
methods=['spiral', 'cross'])
plt.subplot(121)
for methodLabel in errorPerFraction.keys():
......@@ -43,11 +43,12 @@ if __name__ == '__main__':
plt.xscale('log')
plt.xlabel('measured fraction', fontsize=12)
plt.ylabel('mpCountError (%)', fontsize=12)
plt.xlim([0.9*min(fractions), 1.05])
plt.ylim([0, 100])
plt.legend()
errorPerFraction: dict = results.get_error_vs_fraction_data(attributes=['sediment', 'soil', 'beach', 'slush'],
methods=['random', 'sizeBin', 'chemo'])
methods=['spiral', 'cross'])
plt.subplot(122)
for methodLabel in errorPerFraction.keys():
errorDict: dict = errorPerFraction[methodLabel]
......@@ -60,6 +61,7 @@ if __name__ == '__main__':
plt.xscale('log')
plt.xlabel('measured fraction', fontsize=12)
plt.ylabel('mpCountError (%)', fontsize=12)
plt.xlim([0.9*min(fractions), 1.05])
plt.ylim([0, 100])
plt.legend()
......
......@@ -258,7 +258,7 @@ class TestSampleResult(unittest.TestCase):
possibleRandomMethods = 2
possibleCrossBoxMethods = 2
possibleSpiralBoxMethods = 3
possibleChemometricMethods = 1
possibleChemometricMethods = 0
totalPossible = possibleCrossBoxMethods + possibleRandomMethods + \
possibleSpiralBoxMethods + possibleChemometricMethods
self.assertEqual(len(methods), totalPossible)
......@@ -272,7 +272,7 @@ class TestSampleResult(unittest.TestCase):
possibleRandomMethods = 2
possibleCrossBoxMethods = 1
possibleSpiralBoxMethods = 0
possibleChemometricMethods = 1
possibleChemometricMethods = 0
totalPossible = possibleCrossBoxMethods + possibleRandomMethods + \
possibleSpiralBoxMethods + possibleChemometricMethods
self.assertEqual(len(methods), totalPossible)
......@@ -286,7 +286,7 @@ class TestSampleResult(unittest.TestCase):
possibleRandomMethods = 2
possibleCrossBoxMethods = 0
possibleSpiralBoxMethods = 0
possibleChemometricMethods = 1
possibleChemometricMethods = 0
totalPossible = possibleCrossBoxMethods + possibleRandomMethods + \
possibleSpiralBoxMethods + possibleChemometricMethods
self.assertEqual(len(methods), totalPossible)
......@@ -300,7 +300,7 @@ class TestSampleResult(unittest.TestCase):
possibleRandomMethods = 4
possibleCrossBoxMethods = 3
possibleSpiralBoxMethods = 3
possibleChemometricMethods = 2
possibleChemometricMethods = 0
totalPossible = possibleCrossBoxMethods + possibleRandomMethods + \
possibleSpiralBoxMethods + possibleChemometricMethods
self.assertEqual(len(methods), totalPossible)
......@@ -363,17 +363,25 @@ class TestSubsamplingResult(unittest.TestCase):
self.subsamplingResult.add_result(origParticles, subParticles)
self.assertEqual(len(self.subsamplingResult.mpCountErrors), 1)
self.assertEqual(self.subsamplingResult.mpCountErrors[0], 50)
self.assertEqual(self.subsamplingResult.origMPCount, 100)
self.assertEqual(self.subsamplingResult.estimMPCounts, [150])
self.assertAlmostEqual(self.subsamplingResult.estimMPCount, 150)
subParticles = self._get_MP_particles(10) # at fraction of 0.1, 10 particles would be expected
self.subsamplingResult.add_result(origParticles, subParticles)
self.assertEqual(len(self.subsamplingResult.mpCountErrors), 2)
self.assertEqual(self.subsamplingResult.mpCountErrors[0], 50)
self.assertEqual(self.subsamplingResult.mpCountErrors[1], 0)
self.assertAlmostEqual(self.subsamplingResult.mpCountError, 25)
self.assertEqual(self.subsamplingResult.estimMPCounts, [150, 100])
self.assertEqual(self.subsamplingResult.estimMPCount, 125)
def test_reset_results(self):
self.subsamplingResult.mpCountErrors = [10, 30, 20]
self.subsamplingResult.estimMPCounts = [2, 5, 3]
self.subsamplingResult.reset_results()
self.assertEqual(self.subsamplingResult.mpCountErrors, [])
self.assertEqual(self.subsamplingResult.estimMPCounts, [])
def test_get_error_per_bin(self):
def get_full_and_sub_particles():
......@@ -450,7 +458,7 @@ class TestSubsamplingResult(unittest.TestCase):
self.subsamplingResult.mpCountErrors = [50, 75, 100]
self.assertAlmostEqual(self.subsamplingResult.mpCountErrorStDev, 20.412414523193153)
def test_get_averaged_errrs(self):
def test_get_averaged_erros(self):
self.subsamplingResult.mpCountErrors = [50, 75, 100]
self.assertEqual(self.subsamplingResult.mpCountError, 75)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment