geometricMethods.py 4.84 KB
Newer Older
1
import numpy as np
Josef Brandt's avatar
Josef Brandt committed
2 3
from methods import BoxSelectionSubsamplingMethod
from helpers import box_contains_contour
4

5

Josef Brandt's avatar
Josef Brandt committed
6 7
class CrossBoxSelector(BoxSelectionSubsamplingMethod):
    def __init__(self, particleContainer, desiredFraction: float = 0.1) -> None:
8 9 10
        super(CrossBoxSelector, self).__init__(particleContainer, desiredFraction)
        self.filterWidth: float = 1000
        self.filterHeight: float = 1000
Josef Brandt's avatar
Josef Brandt committed
11
        self.numBoxesAcross: int = 3      # either 3 or 5
12 13 14

    @property
    def boxSize(self) -> float:
Josef Brandt's avatar
Josef Brandt committed
15 16 17 18
        maxFraction = self.get_maximum_achievable_fraction()
        if maxFraction < self.fraction:
            self.fraction = maxFraction

19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
        totalBoxArea: float = ((self.filterWidth*self.filterHeight)*self.fraction)
        boxArea: float = totalBoxArea / (2*self.numBoxesAcross - 1)
        return boxArea**0.5

    def get_topLeft_of_boxes(self) -> list:
        topLeftCorners: list = []
        boxSize = self.boxSize
        xStartCoordinates: list = self._get_horizontal_box_starts(boxSize)
        yStartCoordinates: list = self._get_vertical_box_starts(boxSize)
        middleXCoordinate: float = xStartCoordinates[self.numBoxesAcross//2]
        middleYCoordinate: float = yStartCoordinates[self.numBoxesAcross//2]

        for i in range(self.numBoxesAcross):
            topLeftCorners.append((middleXCoordinate, yStartCoordinates[i]))
            if i != self.numBoxesAcross//2:
                topLeftCorners.append((xStartCoordinates[i], middleYCoordinate))

        return topLeftCorners

    def get_maximum_achievable_fraction(self) -> float:
        """
        Returns the maximum achievable fraction, given the desired number of boxes across.
        It is with respect to a rectangular filter of width*height, for circular filter this has to be divided by 0.786
        :return float:
        """
        maxBoxWidth: float = self.filterWidth / self.numBoxesAcross
        maxBoxHeight: float = self.filterHeight / self.numBoxesAcross
        numBoxes: int = 2*self.numBoxesAcross - 1
        totalBoxArea: float = numBoxes * (maxBoxWidth * maxBoxHeight)
        return totalBoxArea / (self.filterHeight * self.filterWidth)

    def _get_horizontal_box_starts(self, boxSize: float) -> list:
        """
        Returns a list of width-values at which the individual boxes start
        :param boxSize:
        :return list:
        """
        return self._get_box_starts(self.filterWidth, boxSize)

    def _get_vertical_box_starts(self, boxSize: float) -> list:
        """
        Returns a list of height-values at which the individual boxes start
        :param boxSize:
        :return list:
        """
        return self._get_box_starts(self.filterHeight, boxSize)

    def _get_box_starts(self, filterSize: float, boxSize: float) -> list:
        maxBoxSize: float = filterSize / self.numBoxesAcross
        assert maxBoxSize >= boxSize
        tileStarts: list = []
        for i in range(self.numBoxesAcross):
            start: float = i * filterSize / self.numBoxesAcross + (maxBoxSize - boxSize) / 2
            tileStarts.append(start)

74 75 76
        return tileStarts


Josef Brandt's avatar
Josef Brandt committed
77
class SpiralSelector(BoxSelectionSubsamplingMethod):
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
    def __init__(self, particleContainer, desiredFraction: float = 0.1) -> None:
        super(SpiralSelector, self).__init__(particleContainer, desiredFraction)
        self.filterWidth: float
        self.filterHeight: float

        self.boxSize: float = 50
        self.numBoxes = 20
        # self.numTurns: float = 4

    @property
    def spiralSlope(self) -> float:
        assert self.filterHeight == self.filterWidth
        return self.armDistance / (2*np.pi)

    @property
    def armDistance(self) -> float:
        return np.sqrt(2) * self.boxSize

    @property
    def numTurns(self) -> float:
        assert self.filterHeight == self.filterWidth
        return (self.filterHeight / 2) / self.armDistance

    @property
    def finalAngle(self) -> float:
        return self.numTurns * 2*np.pi

    def get_topLeft_of_boxes(self) -> list:
        totalLength: float = self._get_spiralLength_after_angle(self.finalAngle)
        boxDistance: float = totalLength / self.numBoxes * 0.5
        filterCenter: tuple = self.filterWidth / 2, self.filterHeight / 2
        slope = self.spiralSlope
        theta: float = 0

        topLefts: list = []
        for _ in range(self.numBoxes):
            newPoint: tuple = self._get_xy_at_angle(theta, filterCenter)
            topLefts.append(newPoint)
            theta += boxDistance / (slope * np.sqrt(1 + theta**2))

        return topLefts

    def _get_spiralLength_after_angle(self, theta: float):
        return 0.5*self.spiralSlope*(theta*(1+theta**2)**0.5 + np.sinh(theta)**(-1))

    def _get_xy_at_angle(self, theta: float, centerXY: tuple = (0, 0)) -> tuple:
        distance: float = self.spiralSlope * theta
Josef Brandt's avatar
Josef Brandt committed
125
        return distance * np.cos(theta) + centerXY[0], distance * np.sin(theta) + centerXY[1]