|
@@ -190,10 +190,18 @@ class PolygonalChain:
|
|
|
vertex_indices
|
|
|
)
|
|
|
|
|
|
+ def normalized(self) -> "PolygonalChain":
|
|
|
+ vertex_indices = list(self.vertex_indices)
|
|
|
+ min_index = vertex_indices.index(min(vertex_indices))
|
|
|
+ indices_min_first = vertex_indices[min_index:] + vertex_indices[:min_index]
|
|
|
+ if indices_min_first[1] < indices_min_first[-1]:
|
|
|
+ return PolygonalChain(indices_min_first)
|
|
|
+ return PolygonalChain(indices_min_first[0:1] + indices_min_first[-1:0:-1])
|
|
|
+
|
|
|
def __eq__(self, other: object) -> bool:
|
|
|
- return (
|
|
|
- isinstance(other, PolygonalChain)
|
|
|
- and self.vertex_indices == other.vertex_indices
|
|
|
+ return isinstance(other, PolygonalChain) and (
|
|
|
+ self.vertex_indices == other.vertex_indices
|
|
|
+ or self.normalized().vertex_indices == other.normalized().vertex_indices
|
|
|
)
|
|
|
|
|
|
def __repr__(self) -> str:
|
|
@@ -548,32 +556,82 @@ class Surface:
|
|
|
if len(border_vertex_indices) == 2:
|
|
|
yield LineSegment(border_vertex_indices)
|
|
|
|
|
|
+ _VertexSubindex = typing.Tuple[int, int]
|
|
|
+
|
|
|
+ @classmethod
|
|
|
+ def _duplicate_border(
|
|
|
+ cls,
|
|
|
+ neighbour_indices: typing.DefaultDict[
|
|
|
+ _VertexSubindex, typing.Set[_VertexSubindex]
|
|
|
+ ],
|
|
|
+ previous_index: _VertexSubindex,
|
|
|
+ current_index: _VertexSubindex,
|
|
|
+ junction_counter: int,
|
|
|
+ ) -> None:
|
|
|
+ split_index = (current_index[0], junction_counter)
|
|
|
+ neighbour_indices[previous_index].add(split_index)
|
|
|
+ neighbour_indices[split_index].add(previous_index)
|
|
|
+ next_index, *extra_indices = filter(
|
|
|
+ lambda i: i != previous_index, neighbour_indices[current_index]
|
|
|
+ )
|
|
|
+ if extra_indices:
|
|
|
+ neighbour_indices[next_index].add(split_index)
|
|
|
+ neighbour_indices[split_index].add(next_index)
|
|
|
+ neighbour_indices[next_index].remove(current_index)
|
|
|
+ neighbour_indices[current_index].remove(next_index)
|
|
|
+ return
|
|
|
+ cls._duplicate_border(
|
|
|
+ neighbour_indices=neighbour_indices,
|
|
|
+ previous_index=split_index,
|
|
|
+ current_index=next_index,
|
|
|
+ junction_counter=junction_counter,
|
|
|
+ )
|
|
|
+
|
|
|
def find_label_border_polygonal_chains(
|
|
|
self, label: Label
|
|
|
) -> typing.Iterator[PolygonalChain]:
|
|
|
- segments = set(self._find_label_border_segments(label))
|
|
|
- available_chains = collections.deque(
|
|
|
- PolygonalChain(segment.vertex_indices) for segment in segments
|
|
|
- )
|
|
|
-
|
|
|
-
|
|
|
- while available_chains:
|
|
|
- chain = available_chains.pop()
|
|
|
- last_chains_len = None
|
|
|
- while last_chains_len != len(available_chains):
|
|
|
- last_chains_len = len(available_chains)
|
|
|
- checked_chains = (
|
|
|
- collections.deque()
|
|
|
- )
|
|
|
- while available_chains:
|
|
|
- potential_neighbour = available_chains.pop()
|
|
|
- try:
|
|
|
- chain.connect(potential_neighbour)
|
|
|
- except PolygonalChainsNotOverlapingError:
|
|
|
- checked_chains.append(potential_neighbour)
|
|
|
- available_chains = checked_chains
|
|
|
- assert all((segment in segments) for segment in chain.segments())
|
|
|
- yield chain
|
|
|
+ neighbour_indices = collections.defaultdict(
|
|
|
+ set
|
|
|
+ )
|
|
|
+ for segment in self._find_label_border_segments(label):
|
|
|
+ vertex_indices = [(i, 0) for i in segment.vertex_indices]
|
|
|
+ neighbour_indices[vertex_indices[0]].add(vertex_indices[1])
|
|
|
+ neighbour_indices[vertex_indices[1]].add(vertex_indices[0])
|
|
|
+ junction_counter = 0
|
|
|
+ found_leaf = True
|
|
|
+ while found_leaf:
|
|
|
+ found_leaf = False
|
|
|
+ for leaf_index, leaf_neighbour_indices in neighbour_indices.items():
|
|
|
+ if len(leaf_neighbour_indices) == 1:
|
|
|
+ found_leaf = True
|
|
|
+ junction_counter += 1
|
|
|
+ self._duplicate_border(
|
|
|
+ neighbour_indices=neighbour_indices,
|
|
|
+ previous_index=leaf_index,
|
|
|
+
|
|
|
+ current_index=next(iter(leaf_neighbour_indices)),
|
|
|
+ junction_counter=junction_counter,
|
|
|
+ )
|
|
|
+ break
|
|
|
+ assert all(len(n) == 2 for n in neighbour_indices.values()), neighbour_indices
|
|
|
+ while neighbour_indices:
|
|
|
+
|
|
|
+ chain = collections.deque([next(iter(neighbour_indices.keys()))])
|
|
|
+ chain.append(neighbour_indices[chain[0]].pop())
|
|
|
+ neighbour_indices[chain[1]].remove(chain[0])
|
|
|
+ while chain[0] != chain[-1]:
|
|
|
+ previous_index = chain[-1]
|
|
|
+ next_index = neighbour_indices[previous_index].pop()
|
|
|
+ neighbour_indices[next_index].remove(previous_index)
|
|
|
+ chain.append(next_index)
|
|
|
+ assert not neighbour_indices[previous_index], neighbour_indices[
|
|
|
+ previous_index
|
|
|
+ ]
|
|
|
+ del neighbour_indices[previous_index]
|
|
|
+ assert not neighbour_indices[chain[0]], neighbour_indices[chain[0]]
|
|
|
+ del neighbour_indices[chain[0]]
|
|
|
+ chain.pop()
|
|
|
+ yield PolygonalChain(v[0] for v in chain)
|
|
|
|
|
|
def _unused_vertices(self) -> typing.Set[int]:
|
|
|
vertex_indices = set(range(len(self.vertices)))
|