Merge in RR/image-prediction from tdd_refactoring to master
Squashed commit of the following:
commit f6c64430007590f5d2b234a7f784e26025d06484
Author: Matthias Bisping <matthias.bisping@iqser.com>
Date: Mon Apr 25 12:18:47 2022 +0200
renaming
commit 8f40b51282191edf3e2a5edcd6d6acb388ada453
Author: Matthias Bisping <matthias.bisping@iqser.com>
Date: Mon Apr 25 12:07:18 2022 +0200
adjusted expetced output for alpha channel in response
commit 7e666302d5eadb1e84b70cae27e8ec6108d7a135
Author: Matthias Bisping <matthias.bisping@iqser.com>
Date: Mon Apr 25 11:52:51 2022 +0200
added alpha channel check result to response
commit a6b9f64b51cd888fc0c427a38bd43ae2ae2cb051
Author: Matthias Bisping <matthias.bisping@iqser.com>
Date: Mon Apr 25 11:27:57 2022 +0200
readme updated
commit 0d06ad657e3c21dcef361c53df37b05aba64528b
Author: Matthias Bisping <matthias.bisping@iqser.com>
Date: Mon Apr 25 11:19:35 2022 +0200
readme updated and config
commit 75748a1d82f0ebdf3ad7d348c6d820c8858aa3cb
Author: Matthias Bisping <matthias.bisping@iqser.com>
Date: Mon Apr 25 11:19:26 2022 +0200
refactoring
commit 60101337828d11f5ee5fed0d8c4ec80cde536d8a
Author: Matthias Bisping <matthias.bisping@iqser.com>
Date: Mon Apr 25 11:18:23 2022 +0200
multiple reoutes for prediction
commit c8476cb5f55e470b831ae4557a031a2c1294eb86
Author: Matthias Bisping <matthias.bisping@iqser.com>
Date: Mon Apr 25 11:17:49 2022 +0200
add banner.txt to container
commit 26ef5fce8a9bc015f1c35f32d40e8bea50a96454
Author: Matthias Bisping <Matthias.Bisping@iqser.com>
Date: Mon Apr 25 10:08:49 2022 +0200
Pull request #8: Pipeline refactoring
Merge in RR/image-prediction from pipeline_refactoring to tdd_refactoring
Squashed commit of the following:
commit 6989fcb3313007b7eecf4bba39077fcde6924a9a
Author: Matthias Bisping <matthias.bisping@iqser.com>
Date: Mon Apr 25 09:49:49 2022 +0200
removed obsolete module
commit 7428aeee37b11c31cffa597c85b018ba71e79a1d
Author: Matthias Bisping <matthias.bisping@iqser.com>
Date: Mon Apr 25 09:45:45 2022 +0200
refactoring
commit 0dcd3894154fdf34bd3ba4ef816362434474f472
Author: Matthias Bisping <matthias.bisping@iqser.com>
Date: Mon Apr 25 08:57:21 2022 +0200
refactoring; removed obsolete extractor-classifier
commit 1078aa81144f4219149b3fcacdae8b09c4b905c0
Author: Matthias Bisping <matthias.bisping@iqser.com>
Date: Fri Apr 22 17:18:10 2022 +0200
removed obsolete imports
commit 71f61fc5fc915da3941cf5ed5d9cc90fccc49031
Author: Matthias Bisping <matthias.bisping@iqser.com>
Date: Fri Apr 22 17:16:25 2022 +0200
comment changed
commit b582726cd1de233edb55c5a76c91e99f9dd3bd13
Author: Matthias Bisping <matthias.bisping@iqser.com>
Date: Fri Apr 22 17:12:11 2022 +0200
refactoring
commit 8abc9010048078868b235d6793ac6c8b20abb985
Author: Matthias Bisping <matthias.bisping@iqser.com>
Date: Thu Apr 21 21:25:47 2022 +0200
formatting
commit 2c87c419fe3185a25c27139e7fcf79f60971ad24
Author: Matthias Bisping <matthias.bisping@iqser.com>
Date: Thu Apr 21 21:24:05 2022 +0200
formatting
commit 50b161192db43a84464125c6d79650225e1010d6
Author: Matthias Bisping <matthias.bisping@iqser.com>
Date: Thu Apr 21 21:20:18 2022 +0200
refactoring
commit 9a1446cccfa070852a5d9c0bdbc36037b82541fc
Author: Matthias Bisping <matthias.bisping@iqser.com>
Date: Thu Apr 21 21:04:57 2022 +0200
refactoring
commit 6c10b55ff8e61412cb2fe5a5625e660ecaf1d7d1
Author: Matthias Bisping <matthias.bisping@iqser.com>
Date: Thu Apr 21 19:48:05 2022 +0200
refactoring
commit 72e785e3e31c132ab352119e9921725f91fac9e2
Author: Matthias Bisping <matthias.bisping@iqser.com>
Date: Thu Apr 21 19:43:39 2022 +0200
refactoring
commit f036ee55e6747daf31e3929bdc2d93dc5f2a56ca
Author: Matthias Bisping <matthias.bisping@iqser.com>
Date: Wed Apr 20 18:30:41 2022 +0200
refactoring pipeline WIP
commit 120721f5f1a7e910c0c2ebc79dc87c2908794c80
Author: Matthias Bisping <matthias.bisping@iqser.com>
Date: Wed Apr 20 15:39:58 2022 +0200
rm debug ls
commit 81226d4f8599af0db0e9718fbb1789cfad91a855
Author: Matthias Bisping <matthias.bisping@iqser.com>
Date: Wed Apr 20 15:28:27 2022 +0200
no compose down
commit 943f7799d49b6a6b0fed985a76ed4fe725dfaeef
Author: Matthias Bisping <matthias.bisping@iqser.com>
Date: Wed Apr 20 15:22:17 2022 +0200
coverage combine
commit d4cd96607157ea414db417cfd7133f56cb56afe1
Author: Matthias Bisping <matthias.bisping@iqser.com>
Date: Wed Apr 20 14:43:09 2022 +0200
model builder path in mlruns adjusted
commit 5b90bb47c3421feb6123c179eb68d1125d58ff1e
Author: Matthias Bisping <matthias.bisping@iqser.com>
Date: Wed Apr 20 10:56:58 2022 +0200
dvc pull in test running script
commit a935cacf2305a4a78a15ff571f368962f4538369
Author: Matthias Bisping <matthias.bisping@iqser.com>
Date: Wed Apr 20 10:50:36 2022 +0200
no clean working dir
commit ba09df7884485b8ab8efbf42a8058de9af60c75c
Author: Matthias Bisping <matthias.bisping@iqser.com>
Date: Wed Apr 20 10:43:22 2022 +0200
debug ls
commit 71263a9983dbfe2060ef5b74de7cc2cbbad43416
Author: Matthias Bisping <matthias.bisping@iqser.com>
Date: Wed Apr 20 09:11:03 2022 +0200
debug ls
commit 41fbadc331e65e4ffe6d053e2d925e5e0543d8b7
Author: Matthias Bisping <matthias.bisping@iqser.com>
Date: Tue Apr 19 20:08:08 2022 +0200
debug echo
commit bb19698d640b3a99ea404e5b4b06d719a9bfe9e9
Author: Matthias Bisping <matthias.bisping@iqser.com>
Date: Tue Apr 19 20:01:59 2022 +0200
skip server predict test
commit 5094015a87fc0976c9d3ff5d1f4c6fdbd96b7eae
Author: Matthias Bisping <matthias.bisping@iqser.com>
Date: Tue Apr 19 19:05:50 2022 +0200
sonar stage after build stage
... and 253 more commits
190 lines
6.6 KiB
Python
190 lines
6.6 KiB
Python
from copy import deepcopy
|
|
from functools import reduce
|
|
from typing import Iterable, Callable, List
|
|
|
|
from PIL import Image
|
|
from funcy import juxt, first, rest, rcompose, rpartial, complement, ilen
|
|
|
|
from image_prediction.image_extractor.extractor import ImageMetadataPair
|
|
from image_prediction.info import Info
|
|
from image_prediction.stitching.grouping import CoordGrouper
|
|
from image_prediction.stitching.split_mapper import HorizontalSplitMapper, VerticalSplitMapper
|
|
from image_prediction.stitching.utils import make_coord_getter, flatten_groups_once, validate_box
|
|
from image_prediction.utils.generic import until
|
|
|
|
|
|
def make_merger_sentinel():
|
|
def no_new_mergers(pairs):
|
|
nonlocal number_of_pairs_so_far
|
|
|
|
number_of_pairs_now = len(pairs)
|
|
|
|
if number_of_pairs_now == number_of_pairs_so_far:
|
|
return True
|
|
|
|
else:
|
|
number_of_pairs_so_far = number_of_pairs_now
|
|
return False
|
|
|
|
number_of_pairs_so_far = -1
|
|
|
|
return no_new_mergers
|
|
|
|
|
|
def merge_along_both_axes(pairs: Iterable[ImageMetadataPair], tolerance=0) -> List[ImageMetadataPair]:
|
|
pairs = merge_along_axis(pairs, "x", tolerance=tolerance)
|
|
pairs = list(merge_along_axis(pairs, "y", tolerance=tolerance))
|
|
|
|
return pairs
|
|
|
|
|
|
def merge_along_axis(pairs: Iterable[ImageMetadataPair], axis, tolerance=0) -> Iterable[ImageMetadataPair]:
|
|
"""Partially merges image-metadata pairs of adjacent images along a given axis. Needs to be iterated with
|
|
alternating axes until no more merges happen to merge all adjacent images.
|
|
|
|
Explanation:
|
|
|
|
Merging algorithm works as follows:
|
|
A dot represents a pair, a bracket a group and a colon a merged pair.
|
|
1) Start with pairs: (........)
|
|
2) Align on lesser: ([....] [....])
|
|
3) Align on greater: ([[..] [..]] [[....]])
|
|
4) Flatten once: ([..] [..] [....])
|
|
5) Merge orthogonally: ([:] [..] [:..])
|
|
6) Flatten once: (:..:..)
|
|
"""
|
|
|
|
def group_pairs_within_groups_by_greater_coordinate(groups):
|
|
return map(CoordGrouper(axis, tolerance=tolerance).group_pairs_by_greater_coordinate, groups)
|
|
|
|
def merge_groups_along_orthogonal_axis(groups):
|
|
return map(rpartial(make_group_merger(axis), tolerance), groups)
|
|
|
|
def group_pairs_by_lesser_coordinate(pairs):
|
|
return CoordGrouper(axis, tolerance=tolerance).group_pairs_by_lesser_coordinate(pairs)
|
|
|
|
return rcompose(
|
|
group_pairs_by_lesser_coordinate,
|
|
group_pairs_within_groups_by_greater_coordinate,
|
|
flatten_groups_once,
|
|
merge_groups_along_orthogonal_axis,
|
|
flatten_groups_once,
|
|
)(pairs)
|
|
|
|
|
|
def make_group_merger(axis):
|
|
return {"y": merge_group_vertically, "x": merge_group_horizontally}[axis]
|
|
|
|
|
|
def merge_group_vertically(group: Iterable[ImageMetadataPair], tolerance=0):
|
|
return merge_group(group, "y", tolerance=tolerance)
|
|
|
|
|
|
def merge_group_horizontally(group: Iterable[ImageMetadataPair], tolerance=0):
|
|
return merge_group(group, "x", tolerance=tolerance)
|
|
|
|
|
|
def merge_group(group: Iterable[ImageMetadataPair], direction, tolerance=0):
|
|
reduce_group = make_merger_aggregator(direction, tolerance=tolerance)
|
|
no_new_mergers = make_merger_sentinel()
|
|
return until(no_new_mergers, reduce_group, group)
|
|
|
|
|
|
def make_merger_aggregator(axis, tolerance=0) -> Callable[[Iterable[ImageMetadataPair]], Iterable[ImageMetadataPair]]:
|
|
"""Produces a function f : [H, T1, ... Tn] -> [HTi...Tj, Tk ... Tl] that merges adjacent image-metadata pairs on the
|
|
head H and aggregates non-adjacent in the tail T.
|
|
|
|
Note:
|
|
When tolerance > 0, the bounding box of the merged image no longer matches the bounding box of the mereged
|
|
metadata. This is intended behaviour, but might be not be expected by the caller.
|
|
"""
|
|
|
|
def merger_aggregator(pairs: Iterable[ImageMetadataPair]):
|
|
def merge_on_head_and_aggregate_in_tail(pairs_aggr: Iterable[ImageMetadataPair], pair: ImageMetadataPair):
|
|
"""Keeps the image that is being merged with as the head and aggregates non-mergables in the tail."""
|
|
aggr, non_aggr = juxt(first, rest)(pairs_aggr)
|
|
if abs(c2_getter(aggr) - c1_getter(pair)) <= tolerance:
|
|
aggr = pair_merger(aggr, pair)
|
|
return aggr, *non_aggr
|
|
else:
|
|
return aggr, pair, *non_aggr
|
|
|
|
# Requires H to be the least element in image-concatenation direction by c1, since the concatenation happens
|
|
# only in c1 -> c2 direction.
|
|
pairs = sorted(pairs, key=c1_getter)
|
|
head_pair, pairs = juxt(first, rest)(pairs)
|
|
return list(reduce(merge_on_head_and_aggregate_in_tail, pairs, [head_pair]))
|
|
|
|
assert tolerance >= 0
|
|
|
|
c1_getter = make_coord_getter(f"{axis}1")
|
|
c2_getter = make_coord_getter(f"{axis}2")
|
|
pair_merger = make_pair_merger(axis)
|
|
|
|
return merger_aggregator
|
|
|
|
|
|
def make_pair_merger(axis):
|
|
return {"y": merge_pair_vertically, "x": merge_pair_horizontally}[axis]
|
|
|
|
|
|
def merge_pair_vertically(p1: ImageMetadataPair, p2: ImageMetadataPair):
|
|
metadata_merged = merge_metadata_vertically(p1.metadata, p2.metadata)
|
|
image_concatenated = concat_images_vertically(p1.image, p2.image, metadata_merged)
|
|
return ImageMetadataPair(image_concatenated, metadata_merged)
|
|
|
|
|
|
def merge_pair_horizontally(p1: ImageMetadataPair, p2: ImageMetadataPair):
|
|
metadata_merged = merge_metadata_horizontally(p1.metadata, p2.metadata)
|
|
image_concatenated = concat_images_horizontally(p1.image, p2.image, metadata_merged)
|
|
return ImageMetadataPair(image_concatenated, metadata_merged)
|
|
|
|
|
|
def merge_metadata_vertically(m1: dict, m2: dict):
|
|
m1, m2 = map(VerticalSplitMapper, [m1, m2])
|
|
return merge_metadata(m1, m2)
|
|
|
|
|
|
def merge_metadata_horizontally(m1: dict, m2: dict):
|
|
m1, m2 = map(HorizontalSplitMapper, [m1, m2])
|
|
return merge_metadata(m1, m2)
|
|
|
|
|
|
def merge_metadata(m1: dict, m2: dict):
|
|
|
|
c1 = min(m1.c1, m2.c1)
|
|
c2 = max(m1.c2, m2.c2)
|
|
dim = abs(c2 - c1)
|
|
|
|
merged = deepcopy(m1)
|
|
merged.dim = dim
|
|
merged.c1 = c1
|
|
merged.c2 = c2
|
|
|
|
validate_box(merged.wrapped)
|
|
|
|
return merged.wrapped
|
|
|
|
|
|
def concat_images_vertically(im1: Image, im2: Image, metadata: dict):
|
|
return concat_images(im1, im2, metadata, 1)
|
|
|
|
|
|
def concat_images_horizontally(im1: Image, im2: Image, metadata: dict):
|
|
return concat_images(im1, im2, metadata, 0)
|
|
|
|
|
|
def concat_images(im1: Image, im2: Image, metadata: dict, axis):
|
|
|
|
im_aggr = Image.new(im1.mode, (metadata[Info.WIDTH], metadata[Info.HEIGHT]))
|
|
|
|
images = [im1, im2]
|
|
|
|
offsets = 0, im1.size[axis], im_aggr.size[axis] - im2.size[axis]
|
|
|
|
for im, offset in zip(images, offsets):
|
|
box = (offset, 0) if not axis else (0, offset)
|
|
im_aggr.paste(im, box=box)
|
|
|
|
return im_aggr
|