helpers.py 5.09 KB
Newer Older
Josef Brandt's avatar
Josef Brandt committed
1 2
from PyQt5 import QtCore, QtGui
import numpy as np
3 4 5
import sys
sys.path.append("C://Users//xbrjos//Desktop//Python")
from gepard import dataset
Josef Brandt's avatar
Josef Brandt committed
6

7 8 9 10 11 12 13 14 15 16 17 18 19 20 21

class ParticleBinSorter(object):
    def __init__(self):
        super(ParticleBinSorter, self).__init__()
        self.bins = [5, 10, 20, 50, 100, 200, 500]
        
    def sort_particles_into_bins(self, particleList):
        particlesInBins = self._get_empty_bins()
        
        for particle in particleList:
            binIndex = self._get_binIndex_of_particle(particle)
            particlesInBins[binIndex].append(particle)
        return particlesInBins
    
    def _get_empty_bins(self):
Josef Brandt's avatar
Josef Brandt committed
22
        return [[] for _ in range(len(self.bins)+1)]
23 24 25 26 27 28 29 30 31
    
    def _get_binIndex_of_particle(self, particle):
        size = particle.getParticleSize()
        binIndex = 0
        for upperLimit in self.bins:
            if size <= upperLimit:
                break
            else:
                binIndex += 1
Josef Brandt's avatar
Josef Brandt committed
32 33 34
        return binIndex


Josef Brandt's avatar
Josef Brandt committed
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
def get_Anger_fraction(numParticles, sigma=1.65, mpFraction=0.01, errorMargin=0.1):
    """
    Returns the required fraction for reaching the defined errorMargin at a given number of Particles.
    According to: Anger et al. "Raman microspectroscopy as a tool for microplastic particle analysis",
    TrAC Trends in Analytical Chemistry, 2018, 214-226. (https://doi.org/10.1016/j.trac.2018.10.010)
    :param numParticles:
    :param sigma:
    :param mpFraction:
    :param errorMargin:
    :return:
    """
    N = numParticles
    P = mpFraction
    e = P * errorMargin
    numParticlesMeasured = np.ceil(P * (1 - P) / (e**2 / sigma**2 + P*(1 - P) / N))
    return np.int(numParticlesMeasured)


53
def box_overlaps_contour(boxTopLeftXY: tuple, boxWidthHeight: tuple, contourData: np.ndarray) -> bool:
54 55 56 57
    """
    Calculates, if a contour is overlapping a box.
    :param boxTopLeftXY: topLeft of Box
    :param boxWidthHeight: Width and height of box
58
    :param contourData: np.ndarrayof contour data
59 60
    :return:
    """
61
    isOverlapping: bool = False
Josef Brandt's avatar
Josef Brandt committed
62

63 64 65
    xmin, xmax = np.min(contourData[:, 0, 0]), np.max(contourData[:, 0, 0])
    width: float = xmax - xmin
    boxXmin, boxXmax = boxTopLeftXY[0], boxTopLeftXY[0] + boxWidthHeight[0]
66

67 68 69 70 71 72 73 74 75
    if xmin > (boxXmin-width/2):
        if xmax < (boxXmax+width/2):
            ymin, ymax = np.min(contourData[:, 0, 1]), np.max(contourData[:, 0, 1])
            height = ymax - ymin
            boxYmin, boxYmax = boxTopLeftXY[1], boxTopLeftXY[1] + boxWidthHeight[1]

            if ymin > (boxYmin-height/2):
                if ymax < (boxYmax+width/2):
                    isOverlapping = True
76 77 78 79 80 81 82 83 84 85 86 87 88 89 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 135 136

    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]

Josef Brandt's avatar
Josef Brandt committed
137

138 139 140 141 142 143 144 145 146 147
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