-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ModelFitting): now support line fitting (#13)
Given a cloud of points this new algorithm is able to fit several lines utilizing point proximity into the data.
- Loading branch information
Showing
10 changed files
with
205 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package algorithms | ||
|
||
import math.distanceLineToPoint | ||
import model.NeighborhoodGraph | ||
import model.geometry.Line | ||
import model.geometry.Point | ||
import java.util.LinkedList | ||
|
||
/** Threshold for line fitting, defaults to 1mm deviation from the line */ | ||
const val THRESHOLD = 1.0 / 1000.0 | ||
|
||
fun <T : Point> fitLines(graph: NeighborhoodGraph<T>): List<Set<T>> = | ||
fitLines(graph.getObjects(), graph = graph) | ||
|
||
// this warns, that tailrec parameter defaults will be initialized in reverse order - which doesn't matter here. | ||
@Suppress("TAILREC_WITH_DEFAULTS") | ||
tailrec fun <T : Point> fitLines( | ||
points: Set<T>, | ||
takenPoints: MutableSet<T> = HashSet(), | ||
graph: NeighborhoodGraph<T>, | ||
lines: MutableList<Set<T>> = mutableListOf() | ||
): List<Set<T>> { | ||
if (points.isEmpty()) return lines | ||
|
||
val startPoint = points.first() | ||
|
||
val initialNeighbors = LinkedList(getNotTakenNeighbors(startPoint, takenPoints, graph)) | ||
lines.add(findNeighborsOnLine(setOf(startPoint), initialNeighbors, takenPoints, graph)) | ||
|
||
return fitLines(graph.getObjects() - takenPoints, takenPoints, graph, lines) | ||
} | ||
|
||
private tailrec fun <T : Point> findNeighborsOnLine( | ||
linePoints: Set<T>, | ||
nearestPoints: LinkedList<T>, | ||
takenPoints: MutableSet<T>, | ||
graph: NeighborhoodGraph<T> | ||
): Set<T> { | ||
if (nearestPoints.isEmpty() || takenPoints.size == graph.getSize()) { | ||
return linePoints | ||
} | ||
val nearestPoint = nearestPoints.pop() | ||
val newLinePoints = linePoints + nearestPoint | ||
|
||
val line = Line.fromSeveralPoints(newLinePoints) | ||
val maxDistance = newLinePoints.map { distanceLineToPoint(line, it) }.max()!! | ||
|
||
return if (maxDistance > THRESHOLD) { | ||
findNeighborsOnLine(linePoints, nearestPoints, takenPoints, graph) | ||
} else { | ||
takenPoints.add(nearestPoint) | ||
val notTakenNeighbors = getNotTakenNeighbors(nearestPoint, takenPoints, graph) | ||
nearestPoints.addAll(notTakenNeighbors) | ||
findNeighborsOnLine(newLinePoints, nearestPoints, takenPoints, graph) | ||
} | ||
} | ||
|
||
private fun <T : Point> getNotTakenNeighbors(point: T, takenPoints: Set<T>, graph: NeighborhoodGraph<T>): List<T> = | ||
graph.getNeighbors(point).filterNot { takenPoints.contains(it) } |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
package algorithms | ||
|
||
import io.kotlintest.matchers.collections.shouldContainExactly | ||
import io.kotlintest.specs.FreeSpec | ||
import io.kotlintest.shouldBe | ||
import model.NeighborhoodGraph | ||
import model.geometry.Line | ||
import model.geometry.Point | ||
|
||
class ModelFittingTest : FreeSpec({ | ||
|
||
"initRecursion" - { | ||
"will detect a line" { | ||
val points = listOf( | ||
Point(-2, 2), // will detect line | ||
Point(-1, 3), | ||
Point(-3, 1) | ||
) | ||
val targetLines = listOf( | ||
Line(1, 4) | ||
) | ||
val graph = NeighborhoodGraph.usingBruteForce(points) | ||
val lines = fitLines(graph) | ||
lines.flatMap { it.asIterable() }.size shouldBe points.size | ||
lines.map { Line.fromSeveralPoints(it) } shouldContainExactly targetLines | ||
} | ||
|
||
"will detect two lines" { | ||
val points = listOf( | ||
Point(-2, 2), | ||
Point(-1, 3), | ||
Point(-3, 1), | ||
|
||
Point(1, 2.5), | ||
Point(2, 1) | ||
) | ||
val targetLines = listOf( | ||
Line(1, 4), | ||
Line(-1.5, 4) | ||
) | ||
val graph = NeighborhoodGraph.usingBruteForce(points) | ||
val lines = fitLines(graph) | ||
lines.flatMap { it.asIterable() }.size shouldBe points.size | ||
lines.map { Line.fromSeveralPoints(it) } shouldContainExactly targetLines | ||
} | ||
|
||
"will detect a line without graph connection" { | ||
val points = listOf( | ||
Point(-2, 2), | ||
Point(-1, 3), | ||
Point(-3, 1), | ||
|
||
Point(1, 2.5), | ||
Point(2, 1), | ||
|
||
Point(-50, -30), | ||
Point(-51, -29), | ||
Point(-52, -28) | ||
) | ||
val targetLines = listOf( | ||
Line(1, 4), | ||
Line(-1.5, 4), | ||
Line(-1, -80) | ||
) | ||
val graph = NeighborhoodGraph.usingBruteForce(points) | ||
val lines = fitLines(graph) | ||
lines.flatMap { it.asIterable() }.size shouldBe points.size | ||
lines.map { Line.fromSeveralPoints(it) } shouldContainExactly targetLines | ||
} | ||
|
||
"will detect out of order points" { | ||
val points = listOf( | ||
Point(-2, 2), | ||
Point(-1, 3), | ||
|
||
Point(1, 2.5), | ||
Point(2, 1), | ||
|
||
Point(-3, 1), | ||
|
||
Point(-50, -30), | ||
Point(-51, -29), | ||
Point(-52, -28) | ||
) | ||
val targetLines = listOf( | ||
Line(1, 4), | ||
Line(-1.5, 4), | ||
Line(-1, -80) | ||
) | ||
val graph = NeighborhoodGraph.usingBruteForce(points) | ||
val lines = fitLines(graph) | ||
lines.flatMap { it.asIterable() }.size shouldBe points.size | ||
lines.map { Line.fromSeveralPoints(it) } shouldContainExactly targetLines | ||
} | ||
} | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters