Commit 064d26eb authored by Josef Brandt's avatar Josef Brandt

Dataset can be loaded in mainView

parent c8650c49
import numpy as np
from itertools import combinations
from methods import BoxSelectionSubsamplingMethod
from helpers import box_contains_contour
from methods import SubsamplingMethod
import sys
sys.path.append("C://Users//xbrjos//Desktop//Python")
from gepard import dataset
class BoxSelectionSubsamplingMethod(SubsamplingMethod):
def __init__(self, *args):
super(BoxSelectionSubsamplingMethod, self).__init__(*args)
self.filterDiameter: float = 500
self.offset: tuple = (0, 0)
@property
def filterArea(self) -> float:
return np.pi * (self.filterDiameter / 2) ** 2
def apply_subsampling_method(self) -> tuple:
subParticles: list = []
topLefts: list = self.get_topLeft_of_boxes()
boxSize = self.boxSize
for particle in self.particleContainer.particles:
for topLeft in topLefts:
if box_overlaps_contour(topLeft, (boxSize, boxSize), particle.contour, self.offset):
subParticles.append(particle)
return self.fraction, subParticles
def get_topLeft_of_boxes(self) -> list:
raise NotImplementedError
class BoxSelectionCreator(object):
def __init__(self, dataset: dataset.DataSet):
super(BoxSelectionCreator, self).__init__()
self.dataset: dataset.DataSet = dataset
def get_crossBoxSelectors_for_fraction(self, desiredFraction: float) -> list:
"""
Creates CrossBoxSelectors that fullfil the desired fraction criterium.
:param desiredFraction:
:return:
"""
crossBoxSelectors = []
offset, diameter, widthHeight = self.get_filterDimensions_from_dataset()
# for numBoxesAcross in [3, 5]:
# newBoxSelector: CrossBoxSelector = CrossBoxSelector(self.dataset.particleContainer, desiredFraction)
# newBoxSelector.filterDiameter = diameter
# newBoxSelector.offset = offset
# newBoxSelector.numBoxesAcross = numBoxesAcross
#
# crossBoxSelectors.append(newBoxSelector)
return crossBoxSelectors
class CrossBoxSelector(BoxSelectionSubsamplingMethod):
......@@ -41,7 +94,7 @@ class CrossBoxSelector(BoxSelectionSubsamplingMethod):
:return float:
"""
alpha: float = np.deg2rad(135)
r: float = self.filterSize/2
r: float = self.filterDiameter/2
d: float = (self.numBoxesAcross-1) * r / self.numBoxesAcross # 2/3*r for numAcross = 3, 4/5*r numAcross = 5
delta: float = np.arcsin((np.sin(alpha) * d) / r)
gamma: float = np.pi - alpha - delta
......@@ -69,11 +122,11 @@ class CrossBoxSelector(BoxSelectionSubsamplingMethod):
return self._get_box_starts(boxSize)
def _get_box_starts(self, boxSize: float) -> list:
maxBoxSize: float = self.filterSize / self.numBoxesAcross
maxBoxSize: float = self.filterDiameter / self.numBoxesAcross
assert maxBoxSize >= boxSize
tileStarts: list = []
for i in range(self.numBoxesAcross):
start: float = i * self.filterSize / self.numBoxesAcross + (maxBoxSize - boxSize) / 2
start: float = i * self.filterDiameter / self.numBoxesAcross + (maxBoxSize - boxSize) / 2
tileStarts.append(start)
return tileStarts
......@@ -102,7 +155,7 @@ class SpiralSelector(BoxSelectionSubsamplingMethod):
Calculates the topLeft-points (x, y) of all measure boxes
The method uses an approximation for the spiral and is not purely accurate.
:return list:"""
filterCenter: tuple = self.filterSize/2 - self.boxSize/2, self.filterSize/2 - self.boxSize/2
filterCenter: tuple = self.filterDiameter/2 - self.boxSize/2, self.filterDiameter/2 - self.boxSize/2
slope = self.spiralSlope
theta: float = 0
boxDistance = self.boxSize * 1.1
......@@ -123,25 +176,25 @@ class SpiralSelector(BoxSelectionSubsamplingMethod):
This function moves and scales the topLeft-Points so that all measure boxes lie within the filter limits.
:return list:
"""
xCoords: np.array = np.array([float(point[0]) for point in topLefts]) - self.filterSize / 2
yCoords: np.array = np.array([float(point[1]) for point in topLefts]) - self.filterSize / 2
xCoords: np.ndarray= np.array([float(point[0]) for point in topLefts]) - self.filterDiameter / 2
yCoords: np.ndarray= np.array([float(point[1]) for point in topLefts]) - self.filterDiameter / 2
xCoordsBoxMiddles: np.array = xCoords + self.boxSize/2
yCoordsBoxMiddles: np.array = yCoords + self.boxSize/2
xCoordsBoxMiddles: np.ndarray= xCoords + self.boxSize/2
yCoordsBoxMiddles: np.ndarray= yCoords + self.boxSize/2
lastBoxCenter: tuple = (xCoordsBoxMiddles[-1], yCoordsBoxMiddles[-1])
distanceLastCenter: float = np.linalg.norm(lastBoxCenter)
maxDistanceInLastBox: float = self._get_max_distance_of_boxCenter_to_center(lastBoxCenter)
halfBoxDistance: float = maxDistanceInLastBox - distanceLastCenter
desiredDistanceTotal: float = self.filterHeight / 2
desiredDistanceTotal: float = self.filterDiameter / 2
desiredDistanceCenter: float = desiredDistanceTotal - halfBoxDistance
scaleFactor: float = desiredDistanceCenter / distanceLastCenter
xCoordsBoxMiddles *= scaleFactor
yCoordsBoxMiddles *= scaleFactor
xCoords = xCoordsBoxMiddles + (self.filterSize - self.boxSize)/2
yCoords = yCoordsBoxMiddles + (self.filterSize - self.boxSize)/2
xCoords = xCoordsBoxMiddles + (self.filterDiameter - self.boxSize)/2
yCoords = yCoordsBoxMiddles + (self.filterDiameter - self.boxSize)/2
newTopLefts = zip(np.round(xCoords), np.round(yCoords))
return list(tuple(newTopLefts))
......@@ -155,12 +208,12 @@ class SpiralSelector(BoxSelectionSubsamplingMethod):
"""
center = np.array(center)
boxSize = self.boxSize
coords: np.array = np.array([[boxCenter[0] - 0.5*boxSize, boxCenter[1] - 0.5*boxSize],
coords: np.ndarray= np.array([[boxCenter[0] - 0.5*boxSize, boxCenter[1] - 0.5*boxSize],
[boxCenter[0] + 0.5*boxSize, boxCenter[1] - 0.5*boxSize],
[boxCenter[0] - 0.5*boxSize, boxCenter[1] + 0.5*boxSize],
[boxCenter[0] + 0.5*boxSize, boxCenter[1] + 0.5*boxSize]])
distances: np.array = np.linalg.norm(coords - center, axis=1)
distances: np.ndarray= np.linalg.norm(coords - center, axis=1)
return np.max(distances)
def _get_xy_at_angle(self, theta: float, centerXY: tuple = (0, 0)) -> tuple:
......
from PyQt5 import QtGui, QtWidgets, QtCore
import sys
sys.path.append("C://Users//xbrjos//Desktop//Python")
import gepard
from gepard import dataset
import helpers
class FilterView(QtWidgets.QGraphicsView):
......@@ -7,6 +12,8 @@ class FilterView(QtWidgets.QGraphicsView):
super(FilterView, self).__init__()
self.setWindowTitle('FilterView')
self.dataset: dataset.DataSet = None
scene = QtWidgets.QGraphicsScene(self)
scene.setItemIndexMethod(QtWidgets.QGraphicsScene.NoIndex)
scene.setBackgroundBrush(QtCore.Qt.darkGray)
......@@ -14,37 +21,124 @@ class FilterView(QtWidgets.QGraphicsView):
self.setCacheMode(QtWidgets.QGraphicsView.CacheBackground)
self.setViewportUpdateMode(QtWidgets.QGraphicsView.BoundingRectViewportUpdate)
self.setRenderHint(QtGui.QPainter.Antialiasing)
self.setTransformationAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse)
self.setResizeAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse)
self.drag = None
self.filter = FilterGraphItem()
self.filter: FilterGraphItem = FilterGraphItem()
self.scene().addItem(self.filter)
self.measuringBoxes: list = []
self.contourItems: list = []
def update_measure_boxes(self, viewItems: list) -> None:
def update_measure_boxes(self, topLefts: list, boxSize: float) -> None:
self._remove_measure_boxes()
for item in viewItems:
self.measuringBoxes.append(item)
self.scene().addItem(item)
offset = self.filter.circleOffset
for x, y in topLefts:
newBox: MeasureBoxGraphItem = MeasureBoxGraphItem(x+offset[0], y+offset[1], boxSize, boxSize)
self.measuringBoxes.append(newBox)
self.scene().addItem(newBox)
self._update_measured_contours()
def _remove_measure_boxes(self) -> None:
for item in self.measuringBoxes:
self.scene().removeItem(item)
self.measuringBoxes = []
def load_and_update_from_dataset(self, fname: str) -> None:
self.dataset = dataset.loadData(fname)
offset, diameter, widthHeight = helpers.get_filterDimensions_from_dataset(self.dataset)
offsetx = helpers.convert_length_to_pixels(self.dataset, offset[0])
offsety = helpers.convert_length_to_pixels(self.dataset, offset[1])
diameter = helpers.convert_length_to_pixels(self.dataset, diameter)
width = helpers.convert_length_to_pixels(self.dataset, widthHeight[0])
height = helpers.convert_length_to_pixels(self.dataset, widthHeight[1])
self.filter.update_filterSize(width, height, diameter, (offsetx, offsety))
self._update_particle_contours()
self._fit_to_window()
def _update_particle_contours(self) -> None:
self._remove_particle_contours()
if self.dataset is not None:
for particle in self.dataset.particleContainer.particles:
newContour: ParticleContour = ParticleContour(particle.contour)
self.scene().addItem(newContour)
self.contourItems.append(newContour)
def _remove_particle_contours(self) -> None:
for cntItem in self.contourItems:
self.scene().removeItem(cntItem)
self.contourItems = []
def _update_measured_contours(self) -> None:
# offset = self.filter.circleOffset
# offset = (-offset[0], -offset[1])
offset = (0, 0)
for contourItem in self.contourItems:
contourItem.isMeasured = False
for measBox in self.measuringBoxes:
topLeftXY = (measBox.posX, measBox.posY)
boxWidthHeight = (measBox.width, measBox.height)
if helpers.box_overlaps_contour(topLeftXY, boxWidthHeight, contourItem.polygon, offset=offset):
contourItem.isMeasured = True
contourItem.update()
break
def wheelEvent(self, event: QtGui.QWheelEvent) -> None:
factor: float = 1.01 ** (event.angleDelta().y() / 8)
newScale: float = self.filter.scale() * factor
self.scale(newScale, newScale)
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.MiddleButton:
self.drag = event.pos()
def mouseMoveEvent(self, event):
if self.drag is not None:
p0 = event.pos()
move = self.drag - p0
self.horizontalScrollBar().setValue(move.x() + self.horizontalScrollBar().value())
self.verticalScrollBar().setValue(move.y() + self.verticalScrollBar().value())
self.drag = p0
def mouseReleaseEvent(self, event: QtGui.QMouseEvent) -> None:
self.drag = None
def _fit_to_window(self) -> None:
brect = self.scene().itemsBoundingRect()
self.fitInView(0, 0, brect.width(), brect.height(), QtCore.Qt.KeepAspectRatio)
class FilterGraphItem(QtWidgets.QGraphicsItem):
"""
The Graphical Representation of the actual filter
"""
def __init__(self, filterWidth: float = 500, filterHeight: float = 500):
def __init__(self, filterWidth: float = 500, filterHeight: float = 500, diameter: float = 500):
super(FilterGraphItem, self).__init__()
self.width = filterWidth
self.height = filterHeight
self.width: float = filterWidth
self.height: float = filterHeight
self.diameter: float = diameter
self.circleOffset: tuple = (0, 0)
self.setPos(0, 0)
self.rect = QtCore.QRectF(0, 0, self.width, self.height)
self.circleRect = QtCore.QRectF(self.circleOffset[0], self.circleOffset[1], self.diameter, self.diameter)
def boundingRect(self) -> QtCore.QRectF:
return self.rect
def update_filterSize(self, width: float, height: float, diameter: float, offset: tuple) -> None:
self.width = width
self.height = height
self.rect = QtCore.QRectF(0, 0, self.width, self.height)
self.diameter = diameter
self.circleOffset = offset
self.circleRect = QtCore.QRectF(self.circleOffset[0], self.circleOffset[1], self.diameter, self.diameter)
def paint(self, painter: QtGui.QPainter, option, widget) -> None:
painter.setPen(QtCore.Qt.black)
painter.setBrush(QtCore.Qt.white)
......@@ -52,15 +146,16 @@ class FilterGraphItem(QtWidgets.QGraphicsItem):
painter.setPen(QtCore.Qt.darkGray)
painter.setBrush(QtCore.Qt.lightGray)
painter.drawEllipse(self.rect)
painter.drawEllipse(self.circleRect)
class MeasureBoxGraphItem(QtWidgets.QGraphicsItem):
"""
Displays a box in which particles will be measured
"""
def __init__(self, posX:float=50, posY:float=50, width:float=50, height:float=50) -> None:
def __init__(self, posX: float = 50, posY: float = 50, width: float = 50, height: float = 50) -> None:
super(MeasureBoxGraphItem, self).__init__()
self.setZValue(5)
self.posX: float = posX
self.posY: float = posY
self.height: float = height
......@@ -74,6 +169,49 @@ class MeasureBoxGraphItem(QtWidgets.QGraphicsItem):
return self.rect
def paint(self, painter, option, widget) -> None:
painter.setBrush(QtCore.Qt.green)
painter.setBrush(QtGui.QColor(0, 255, 0, 180))
painter.setPen(QtCore.Qt.darkGreen)
painter.drawRects(self.rect)
\ No newline at end of file
painter.drawRects(self.rect)
class ParticleContour(QtWidgets.QGraphicsItem):
def __init__(self, contourData, pos=(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.isMeasured: bool = False # Wether the particle overlaps with a measuring box or nt
self.contourData = contourData
self.polygon = None
self.getBrectAndPolygon()
def getBrectAndPolygon(self) -> None:
"""
Calculates the bounding rect (needed for drawing the QGraphicsView) and converts the contourdata to a polygon.
:return:
"""
self.polygon = QtGui.QPolygonF()
x0 = self.contourData[:, 0, 0].min()
x1 = self.contourData[:, 0, 0].max()
y0 = self.contourData[:, 0, 1].min()
y1 = self.contourData[:, 0, 1].max()
for point in self.contourData:
self.polygon.append(QtCore.QPointF(point[0, 0], point[0, 1]))
self.brect.setCoords(x0, y0, x1, y1)
def boundingRect(self) -> QtCore.QRectF:
return self.brect
def paint(self, painter, option, widget) -> None:
if self.polygon is not None:
if self.isMeasured:
painter.setPen(QtCore.Qt.darkRed)
painter.setBrush(QtCore.Qt.red)
else:
painter.setPen(QtCore.Qt.darkCyan)
painter.setBrush(QtCore.Qt.cyan)
painter.drawPolygon(self.polygon)
......@@ -17,10 +17,14 @@ class MainView(QtWidgets.QWidget):
self.activeMode: MeasureMode = None
self.activeModeControl: QtWidgets.QGroupBox = QtWidgets.QGroupBox()
loadDsetBtn = QtWidgets.QPushButton('Load Dataset')
loadDsetBtn.released.connect(self._load_dataset)
self.controlGroup = QtWidgets.QGroupBox()
self.controlGroupLayout = QtWidgets.QHBoxLayout()
self.controlGroup.setLayout(self.controlGroupLayout)
self.controlGroupLayout.addWidget(loadDsetBtn)
self.controlGroupLayout.addWidget(QtWidgets.QLabel('Select Subsampling Mode:'))
self.controlGroupLayout.addWidget(self.modeSelector)
self.controlGroupLayout.addWidget(self.activeModeControl)
......@@ -57,6 +61,12 @@ class MainView(QtWidgets.QWidget):
self.activeMode.update_measure_viewItems()
def _load_dataset(self) -> None:
fname = QtWidgets.QFileDialog.getOpenFileName(self, 'Select .pkl file', filter='pkl file (*.pkl)')
if fname[0] != '':
self.filterView.load_and_update_from_dataset(fname[0])
self.activeMode.update_measure_viewItems()
if __name__ == '__main__':
import sys
......
......@@ -24,8 +24,7 @@ class CrossBoxMode(MeasureMode):
self.update_measure_viewItems()
def update_measure_viewItems(self) -> None:
assert self.filterView.filter.height == self.filterView.filter.width
self.crossBoxGenerator.filterSize = self.filterView.filter.height
self.crossBoxGenerator.filterDiameter = self.filterView.filter.diameter
self.crossBoxGenerator.numBoxesAcross = int(self.uiControls.numBoxesSelector.currentText())
desiredCoverage: int = self.uiControls.coverageSpinbox.value()
......@@ -36,14 +35,9 @@ class CrossBoxMode(MeasureMode):
self.crossBoxGenerator.fraction = desiredCoverage / 100
viewItems = []
topLefts: list = self.crossBoxGenerator.get_topLeft_of_boxes()
boxSize = self.crossBoxGenerator.boxSize
for (x, y) in topLefts:
newBox: MeasureBoxGraphItem = MeasureBoxGraphItem(x, y, boxSize, boxSize)
viewItems.append(newBox)
self.filterView.update_measure_boxes(viewItems)
self.filterView.update_measure_boxes(topLefts, boxSize)
class CrossBoxesControls(QtWidgets.QGroupBox):
......@@ -94,20 +88,19 @@ class SpiralBoxMode(MeasureMode):
self.update_measure_viewItems()
def update_measure_viewItems(self) -> None:
self.spiralBoxGenerator.filterHeight = self.filterView.filter.height
self.spiralBoxGenerator.filterWidth = self.filterView.filter.width
self.spiralBoxGenerator.filterDiameter = self.filterView.filter.diameter
self.spiralBoxGenerator.boxSize = self.uiControls.boxSizeSpinbox.value()
# minBoxSize: float = self.spiralBoxGenerator.filterDiameter*0.1
# if self.spiralBoxGenerator.boxSize < minBoxSize:
# self.spiralBoxGenerator.boxSize = minBoxSize
# self.uiControls.boxSizeSpinbox.setValue(int(round(minBoxSize)))
self.spiralBoxGenerator.numBoxes = self.uiControls.numBoxesSpinbox.value()
viewItems = []
topLefts: list = self.spiralBoxGenerator.get_topLeft_of_boxes()
boxSize = self.spiralBoxGenerator.boxSize
for (x, y) in topLefts:
newBox: MeasureBoxGraphItem = MeasureBoxGraphItem(x, y, boxSize, boxSize)
viewItems.append(newBox)
self.filterView.update_measure_boxes(viewItems)
self.filterView.update_measure_boxes(topLefts, boxSize)
class SpiralBoxControls(QtWidgets.QGroupBox):
......@@ -125,7 +118,7 @@ class SpiralBoxControls(QtWidgets.QGroupBox):
layout.addWidget(QtWidgets.QLabel('Box Size:'))
self.boxSizeSpinbox = QtWidgets.QSpinBox()
self.boxSizeSpinbox.setValue(50)
self.boxSizeSpinbox.setMaximum(1000)
self.boxSizeSpinbox.setMaximum(10000)
self.boxSizeSpinbox.valueChanged.connect(self._config_changed)
layout.addWidget(self.boxSizeSpinbox)
......
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Tue Jan 28 19:32:50 2020
@author: luna
"""
from PyQt5 import QtCore, QtGui
import numpy as np
import sys
sys.path.append("C://Users//xbrjos//Desktop//Python")
from gepard import dataset
class ParticleBinSorter(object):
......@@ -36,15 +32,108 @@ class ParticleBinSorter(object):
return binIndex
def box_contains_contour(boxXY: tuple, boxWidthHeight: tuple, contour: np.array) -> bool:
def box_overlaps_contour(boxTopLeftXY: tuple, boxWidthHeight: tuple, contour, offset: tuple = (0, 0)) -> bool:
"""
Calculates, if a contour is overlapping a box.
:param boxTopLeftXY: topLeft of Box
:param boxWidthHeight: Width and height of box
:param contour: np.ndarrayof contour data
:param offset: optional offset (x, y) of the box (i.e., the (0, 0) of the contours coord system does not match
the (0, 0) of the box coord system.
:return:
"""
contourPolygon = QtGui.QPolygonF()
for point in contour:
contourPolygon.append(QtCore.QPointF(point[0, 0], point[0, 1]))
if type(contour) == np.ndarray:
for point in contour:
contourPolygon.append(QtCore.QPointF(point[0, 0], point[0, 1]))
elif type(contour) == QtGui.QPolygonF:
contourPolygon = contour
else:
raise TypeError
boxPolygon = QtGui.QPolygonF()
boxPolygon.append(QtCore.QPointF(boxXY[0], boxXY[1]))
boxPolygon.append(QtCore.QPointF(boxXY[0], boxXY[1] + boxWidthHeight[1]))
boxPolygon.append(QtCore.QPointF(boxXY[0] + boxWidthHeight[0], boxXY[1]))
boxPolygon.append(QtCore.QPointF(boxXY[0] + boxWidthHeight[0], boxXY[1] + boxWidthHeight[1]))
boxPolygon.append(QtCore.QPointF(boxTopLeftXY[0]+offset[0], boxTopLeftXY[1]+offset[1]))
boxPolygon.append(QtCore.QPointF(boxTopLeftXY[0]+offset[0], boxTopLeftXY[1] + boxWidthHeight[1]+offset[1]))
boxPolygon.append(QtCore.QPointF(boxTopLeftXY[0]+offset[0] + boxWidthHeight[0], boxTopLeftXY[1]+offset[1]))
boxPolygon.append(QtCore.QPointF(boxTopLeftXY[0]+offset[0] + boxWidthHeight[0],
boxTopLeftXY[1] + boxWidthHeight[1]+offset[1]))
isOverlapping: bool = boxPolygon.intersects(contourPolygon)
if not isOverlapping:
# sometimes, the polygon.intersects method does not capture everything... We test the brects therefore..
polygonBrect: QtCore.QRectF = contourPolygon.boundingRect()
boxBrect: QtCore.QRectF = boxPolygon.boundingRect()
if boxBrect.contains(polygonBrect) or boxBrect.intersects(polygonBrect):
isOverlapping = True
return isOverlapping
def get_overlapping_fraction(polygon1: QtGui.QPolygonF, polygon2: QtGui.QPolygonF) -> float:
"""
Takes two polygons and returns the overlapping fraction (in terms of area)
:param polygon1: The polygon that the fraction shall be calculated of.
:param polygon2: The overlapping polygon, which's size is not of interest
:return:
"""
overlap: float = 0
overlapPoly: QtGui.QPolygonF = polygon1.intersected(polygon2)
if overlapPoly.size() > 0:
origSize: float = get_polygon_area(polygon1)
overlapSize: float = get_polygon_area(overlapPoly)
overlap = overlapSize/origSize
return overlap
def get_polygon_area(polygon: QtGui.QPolygonF) -> float:
"""
Calculates the area of a polygon, adapted from:
https://stackoverflow.com/questions/24467972/calculate-area-of-polygon-given-x-y-coordinates
:param polygon:
:return: area
"""
x: list = []
y: list = []
for index in range(polygon.size()):
point: QtCore.QPointF = polygon.at(index)
x.append(point.x())
y.append(point.y())
x: np.ndarray = np.array(x)
y: np.ndarray = np.array(y)
area = 0.5 * np.abs(np.dot(x, np.roll(y, 1)) - np.dot(y, np.roll(x, 1)))
return area
def get_filterDimensions_from_dataset(dataset) -> tuple:
"""
Processes the datasets boundary items to calculate diameter and offset (coord system offset of circular filter
with respect to actual dataset). This is used to set circular filter dimensions to use in the geometric
subsampling approaches.
The return values are in micrometer dimensions.
:param dataset: The dataset to read.
:return: (radius, offset, widthHeight) in µm
"""
maxDim = dataset.maxdim
imgDim = dataset.imagedim_df if dataset.imagescanMode == 'df' else dataset.imagedim_bf
minX, maxY, = maxDim[0] - imgDim[0] / 2, maxDim[1] + imgDim[1] / 2
maxX, minY = maxDim[2] + imgDim[0] / 2, maxDim[3] - imgDim[1] / 2
width = maxX - minX
height = maxY - minY
diameter: float = min([width, height])
offset: tuple = (width - diameter)/2, (height-diameter)/2
return offset, diameter, [width, height]
return contourPolygon.intersects(boxPolygon)
def convert_length_to_pixels(dataset: dataset.DataSet, length: float) -> float:
"""
:param dataset: dataset to use for conversion
:param length: length in µm
:return: length in px
"""
imgMode: str = dataset.imagescanMode
pixelScale: float = (dataset.pixelscale_df if imgMode == 'df' else dataset.pixelscale_bf)
length /= pixelScale
return length
......@@ -26,30 +26,6 @@ class SubsamplingMethod(object):
raise NotImplementedError
class BoxSelectionSubsamplingMethod(SubsamplingMethod):
def __init__(self, *args):
super(BoxSelectionSubsamplingMethod, self).__init__(*args)
self.filterSize: float = 500
@property
def filterArea(self) -> float:
return np.pi * (self.filterSize / 2) ** 2
def apply_subsampling_method(self) -> tuple:
subParticles: list = []
topLefts: list = self.get_topLeft_of_boxes()
boxSize = self.boxSize
for particle in self.particleContainer.particles:
for topLeft in topLefts:
if box_contains_contour(topLeft, (boxSize, boxSize), particle.contour):
subParticles.append(particle)
return self.fraction, subParticles
def get_topLeft_of_boxes(self) -> list:
raise NotImplementedError
class RandomSampling(SubsamplingMethod):
def apply_subsampling_method(self):
numOrigParticles = len(self.particleContainer.particles)
......
......@@ -6,6 +6,7 @@ sys.path.append("C://Users//xbrjos//Desktop//Python")
from gepard import dataset
from methods import IvlevaSubsampling, RandomSampling, SizeBinFractioning
from geometricMethods import BoxSelectionCreator
from helpers import ParticleBinSorter
from evaluation import ResultComparer
......@@ -19,58 +20,63 @@ fname: str = r'C:\Users\xbrjos\Desktop\temp MP\190313_Soil_5_A_50_5_1_50_1\19031
dset = dataset.loadData(fname)
print('loaded dataset')
pc = dset.particleContainer
origParticles = pc.particles
boxCreator = BoxSelectionCreator(dset)
center, size = boxCreator.get_filterDimensions_from_dataset()
print(center, size)
print(dset.mapToPixel(center, force=True))
resultComparer = ResultComparer()
numOrigMP = resultComparer._get_number_of_MP_particles(origParticles)
print(f'orig particles: {len(origParticles)}, of which are mp: {numOrigMP}')
# ivlevaSampling = IvlevaSubsampling(pc)
# ivlevaFraction, ivlevaParticles = ivlevaSampling.apply_subsampling_method()
# pc = dset.particleContainer
# origParticles = pc.particles
t0 = time.time()
fractions = np.arange(0.05, .55, 0.05)
errors = []
binErrors = []
numIterations = 1000
# resultComparer = ResultComparer()
# numOrigMP = resultComparer._get_number_of_MP_particles(origParticles)
# print(f'orig particles: {len(origParticles)}, of which are mp: {numOrigMP}')
# # ivlevaSampling = IvlevaSubsampling(pc)
# # ivlevaFraction, ivlevaParticles = ivlevaSampling.apply_subsampling_method()
for fraction in fractions:
print('random sampling, fraction:', fraction)
# randomSampling = RandomSampling(pc, desiredFraction=fraction)
randomSampling = SizeBinFractioning(pc, fraction)
iterErrors = []
binIterErrors = []
for _ in range(numIterations):
randomFraction, randomParticles = randomSampling.apply_subsampling_method()
iterErrors.append(resultComparer._get_mp_count_error(origParticles, randomParticles, randomFraction))
bins, errorsPerBin = resultComparer._get_mp_count_error_per_bin(origParticles, randomParticles, randomFraction)
binIterErrors.append(errorsPerBin)
# t0 = time.time()
# fractions = np.arange(0.05, .55, 0.05)
# errors = []
# binErrors = []
# numIterations = 1000
errors.append(round(np.mean(iterErrors)*100)) #from fraction to %
fractionBinErrors = []
for binIndex in range(len(bins)+1):
binError = round(np.mean([binIterErrors[i][binIndex] for i in range(numIterations)]) * 100)
fractionBinErrors.append(binError)
binErrors.append(fractionBinErrors)
# for fraction in fractions:
# print('random sampling, fraction:', fraction)
# # randomSampling = RandomSampling(pc, desiredFraction=fraction)
# randomSampling = SizeBinFractioning(pc, fraction)
# iterErrors = []
# binIterErrors = []
# for _ in range(numIterations):
# randomFraction, randomParticles = randomSampling.apply_subsampling_method()
# iterErrors.append(resultComparer._get_mp_count_error(origParticles, randomParticles, randomFraction))
# bins, errorsPerBin = resultComparer._get_mp_count_error_per_bin(origParticles, randomParticles, randomFraction)
# binIterErrors.append(errorsPerBin)
print('random sampling took', np.round(time.time()-t0, 2), 'seonds')
binLowerLimits = bins.copy()
binLowerLimits.insert(0, 0)
plt.subplot(121)
plt.plot(fractions, errors)
# plt.title(f'Random Sampling, averaged from {numIterations} trials, orig particle count: {len(origParticles)}')
plt.xlabel('Fraction measured')
plt.ylabel('Average error in MP particle count (%)')
# errors.append(round(np.mean(iterErrors)*100)) #from fraction to %
# fractionBinErrors = []
# for binIndex in range(len(bins)+1):
# binError = round(np.mean([binIterErrors[i][binIndex] for i in range(numIterations)]) * 100)
# fractionBinErrors.append(binError)
# binErrors.append(fractionBinErrors)
plt.subplot(122)
for fracMeas, curBinErrors in zip(fractions, binErrors):
plt.plot(binLowerLimits,