Commit bc14998e by Josef Brandt

### Spiral working more robust

parent 1eba109f
 import numpy as np from itertools import combinations from methods import BoxSelectionSubsamplingMethod from helpers import box_contains_contour ... ... @@ -82,7 +83,6 @@ class SpiralSelector(BoxSelectionSubsamplingMethod): self.boxSize: float = 50 self.numBoxes = 20 # self.numTurns: float = 4 @property def spiralSlope(self) -> float: ... ... @@ -94,32 +94,91 @@ class SpiralSelector(BoxSelectionSubsamplingMethod): 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 actuallyCoveredFraction(self) -> float: return self.numBoxes*self.boxSize**2 / (self.filterHeight*self.filterWidth) 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 """ 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.filterWidth/2 - self.boxSize/2, self.filterHeight/2 - self.boxSize/2 slope = self.spiralSlope theta: float = 0 boxDistance = self.boxSize * 1.1 topLefts: list = [] for _ in range(self.numBoxes): for i in range(self.numBoxes): newPoint: tuple = self._get_xy_at_angle(theta, filterCenter) topLefts.append(newPoint) theta += boxDistance / (slope * np.sqrt(1 + theta**2)) boxDistance *= 1.05 topLefts = self._move_and_scale_toplefts(topLefts) 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 _move_and_scale_toplefts(self, topLefts: list) -> list: """ The spiral approximation leads to boxes that are outside the filter size limits. This function moves and scales the topLeft-Points so that all measure boxes lie within the filter limits. :return list: """ assert self.filterHeight == self.filterWidth # elliptical filters are not supportet here.. xCoords: np.array = np.array([float(point[0]) for point in topLefts]) - self.filterWidth / 2 yCoords: np.array = np.array([float(point[1]) for point in topLefts]) - self.filterHeight / 2 xCoordsBoxMiddles: np.array = xCoords + self.boxSize/2 yCoordsBoxMiddles: np.array = 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 desiredDistanceCenter: float = desiredDistanceTotal - halfBoxDistance scaleFactor: float = desiredDistanceCenter / distanceLastCenter xCoordsBoxMiddles *= scaleFactor yCoordsBoxMiddles *= scaleFactor xCoords = xCoordsBoxMiddles + (self.filterWidth - self.boxSize)/2 yCoords = yCoordsBoxMiddles + (self.filterHeight - self.boxSize)/2 newTopLefts = zip(np.round(xCoords), np.round(yCoords)) return list(tuple(newTopLefts)) def _get_max_distance_of_boxCenter_to_center(self, boxCenter: tuple, center: tuple = (0, 0)) -> float: """ Calculates the maximal distance of a box to the given center :param topLeft: :param boxSize: :return: """ center = np.array(center) boxSize = self.boxSize coords: np.array = 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) return np.max(distances) def _get_xy_at_angle(self, theta: float, centerXY: tuple = (0, 0)) -> tuple: distance: float = self.spiralSlope * theta return distance * np.cos(theta) + centerXY[0], distance * np.sin(theta) + centerXY[1] def _boxes_are_overlapping(self, topLefts: list) -> bool: """ Calculates if there is any overlap of the boxes :return: """ overlaps: bool = False for topLeft1, topLeft2 in combinations(topLefts, 2): if abs(topLeft1[0] - topLeft2[0]) < self.boxSize and abs(topLeft1[1] - topLeft2[1]) < self.boxSize: overlaps = True break return overlaps
 ... ... @@ -89,7 +89,7 @@ class CrossBoxesControls(QtWidgets.QGroupBox): class SpiralBoxMode(MeasureMode): def __init__(self, *args): super(SpiralBoxMode, self).__init__(*args) self.uiControls = QtWidgets.QGroupBox('Spiral Box Controls') self.uiControls: SpiralBoxControls = SpiralBoxControls(self) self.spiralBoxGenerator: SpiralSelector = SpiralSelector(None) self.update_measure_viewItems() ... ... @@ -97,13 +97,8 @@ class SpiralBoxMode(MeasureMode): self.spiralBoxGenerator.filterHeight = self.filterView.filter.height self.spiralBoxGenerator.filterWidth = self.filterView.filter.width # desiredCoverage: int = self.uiControls.coverageSpinbox.value() # maxCoverage: int = int(self.crossBoxGenerator.get_maximum_achievable_fraction() * 100) # self.uiControls.set_to_max_possible_coverage(maxCoverage) # if desiredCoverage > maxCoverage: # desiredCoverage = maxCoverage # self.crossBoxGenerator.fraction = desiredCoverage / 100 self.spiralBoxGenerator.boxSize = self.uiControls.boxSizeSpinbox.value() self.spiralBoxGenerator.numBoxes = self.uiControls.numBoxesSpinbox.value() viewItems = [] topLefts: list = self.spiralBoxGenerator.get_topLeft_of_boxes() ... ... @@ -112,4 +107,35 @@ class SpiralBoxMode(MeasureMode): newBox: MeasureBoxGraphItem = MeasureBoxGraphItem(x, y, boxSize, boxSize) viewItems.append(newBox) self.filterView.update_measure_boxes(viewItems) \ No newline at end of file self.filterView.update_measure_boxes(viewItems) class SpiralBoxControls(QtWidgets.QGroupBox): """ Gives a groupbox with the controls for setting up the cross boxes. """ def __init__(self, measureModeParent: MeasureMode): super(SpiralBoxControls, self).__init__() self.setTitle('Spiral Box Controls') self.measureModeParent = measureModeParent layout = QtWidgets.QHBoxLayout() self.setLayout(layout) layout.addWidget(QtWidgets.QLabel('Box Size:')) self.boxSizeSpinbox = QtWidgets.QSpinBox() self.boxSizeSpinbox.setValue(50) self.boxSizeSpinbox.setMaximum(1000) self.boxSizeSpinbox.valueChanged.connect(self._config_changed) layout.addWidget(self.boxSizeSpinbox) layout.addStretch() layout.addWidget(QtWidgets.QLabel('Num Boxes:')) self.numBoxesSpinbox = QtWidgets.QSpinBox() self.numBoxesSpinbox.setValue(10) self.numBoxesSpinbox.valueChanged.connect(self._config_changed) layout.addWidget(self.numBoxesSpinbox) def _config_changed(self): self.measureModeParent.update_measure_viewItems()
 ... ... @@ -44,6 +44,9 @@ class BoxSelectionSubsamplingMethod(SubsamplingMethod): def get_topLeft_of_boxes(self) -> list: raise NotImplementedError def get_parameterCombos_for_fraction(self) -> list: raise NotImplementedError class RandomSampling(SubsamplingMethod): def apply_subsampling_method(self): ... ...
 import unittest from geometricMethods import CrossBoxSelector import numpy as np from geometricMethods import CrossBoxSelector, SpiralSelector class TestSelectBoxes(unittest.TestCase): class TestSelectCrossBoxes(unittest.TestCase): def setUp(self) -> None: self.crossBoxSelector = CrossBoxSelector(None) ... ... @@ -62,3 +63,58 @@ class TestSelectBoxes(unittest.TestCase): boxSize: float = areaPerBox**0.5 self.assertEqual(self.crossBoxSelector.boxSize, boxSize) class TestSelectSpiralBoxes(unittest.TestCase): def setUp(self) -> None: self.spiralBoxSelector: SpiralSelector = SpiralSelector(None) def test_move_and_scale_toplefts(self): self.spiralBoxSelector.filterHeight = 100 self.spiralBoxSelector.filterWidth = 100 self.spiralBoxSelector.boxSize = 10 topLefts = [(45, 45), (0, 45), (90, 45)] newTopLefts = self.spiralBoxSelector._move_and_scale_toplefts(topLefts) self.assertEqual(newTopLefts[0], (45, 45)) self.assertEqual(newTopLefts[1], (0, 45)) self.assertEqual(newTopLefts[2], (90, 45)) def test_get_max_distance_of_boxCenter_to_center(self): boxCenter = 0, 0 self.spiralBoxSelector.boxSize = 1 maxDistance: float = self.spiralBoxSelector._get_max_distance_of_boxCenter_to_center(boxCenter) self.assertEqual(maxDistance, np.sqrt(0.5**2 + 0.5**2)) self.spiralBoxSelector.boxSize = 2 maxDistance: float = self.spiralBoxSelector._get_max_distance_of_boxCenter_to_center(boxCenter) self.assertEqual(maxDistance, np.sqrt(2)) boxCenter = 1, 0 self.spiralBoxSelector.boxSize = 2 maxDistance: float = self.spiralBoxSelector._get_max_distance_of_boxCenter_to_center(boxCenter) self.assertEqual(maxDistance, np.sqrt(2**2 + 1**2)) boxCenter = -1, -3 self.spiralBoxSelector.boxSize = 2 maxDistance: float = self.spiralBoxSelector._get_max_distance_of_boxCenter_to_center(boxCenter) self.assertEqual(maxDistance, np.sqrt(4**2 + 2**2)) def test_boxes_are_overlapping(self): self.spiralBoxSelector.boxSize = 10 topLefts: list = [(0, 0), (10, 10), (20, 10)] overlaps: bool = self.spiralBoxSelector._boxes_are_overlapping(topLefts) self.assertEqual(overlaps, False) topLefts: list = [(0, 0), (9, 10)] overlaps: bool = self.spiralBoxSelector._boxes_are_overlapping(topLefts) self.assertEqual(overlaps, False) topLefts: list = [(0, 0), (9, 9)] overlaps: bool = self.spiralBoxSelector._boxes_are_overlapping(topLefts) self.assertEqual(overlaps, True) topLefts: list = [(-2, 0), (5, 9)] overlaps: bool = self.spiralBoxSelector._boxes_are_overlapping(topLefts) self.assertEqual(overlaps, True)
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!