From 5035a290b1083a26292842a8f5e84663e6377c62 Mon Sep 17 00:00:00 2001
From: Tamino Huxohl <thuxohl@techfak.uni-bielefeld.de>
Date: Tue, 30 Aug 2022 09:41:30 +0200
Subject: [PATCH] add script that allows to remove the bed from attenuation
 maps

---
 mu_map/data/remove_bed.py | 149 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 149 insertions(+)
 create mode 100644 mu_map/data/remove_bed.py

diff --git a/mu_map/data/remove_bed.py b/mu_map/data/remove_bed.py
new file mode 100644
index 0000000..b0dda80
--- /dev/null
+++ b/mu_map/data/remove_bed.py
@@ -0,0 +1,149 @@
+import json
+from typing import Dict
+
+import numpy as np
+
+
+DEFAULT_BED_CONTOURS_FILENAME = "bed_contours.json"
+
+
+def load_contours(filename: str) -> Dict[int, np.ndarray]:
+    """
+    Load contours from a json file.
+    The structure of the file is a dict where the key is the id of the according
+    image and the value is a numpy array of the contour.
+
+    :param filename: filename of a json file containing contours
+    :return: a dict mapping ids to contours
+    """
+    with open(filename, mode="r") as f:
+        contours = json.load(f)
+
+        for key, contour in contours.items():
+            del contours[key]
+            contours[int(key)] = np.array(contour).astype(int)
+    return contours
+
+
+if __name__ == "__main__":
+    import argparse
+    from enum import Enum
+    import os
+
+    import cv2 as cv
+
+    from mu_map.data.datasets import MuMapDataset
+    from mu_map.util import to_grayscale, COLOR_BLACK, COLOR_WHITE
+
+    parser = argparse.ArgumentParser(
+        description="draw and save contours to exclude the bed from mu maps",
+        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
+    )
+    parser.add_argument(
+        "dataset_dir", type=str, help="the directory containing the dataset"
+    )
+    parser.add_argument(
+        "--output_file",
+        type=str,
+        default=DEFAULT_BED_CONTOURS_FILENAME,
+        help="default file in dataset dir where the drawn contours are stored",
+    )
+    args = parser.parse_args()
+    args.output_file = os.path.join(args.dataset_dir, args.output_file)
+
+    controls = """
+    Controls:
+    Left click to add points to the current contour.
+
+    q: exit
+    d: delete last point
+    n: save contour and go to the next image
+    v: change the visual mode between drawing contours and hiding the are within
+    """
+    print(controls)
+    print()
+
+    # set bed contours file to None so that existing contours are not used
+    dataset = MuMapDataset(args.dataset_dir, bed_contours_file=None)
+
+    # TODO: implement that existing contours are loaded so that labeling can be continued?
+    bed_contours = {}
+
+    class VisualMode(Enum):
+        DRAW_CONTOURS = 1
+        HIDE_BED = 2
+
+    window_name = "Bed Removal"
+    for i, (_, mu_map) in enumerate(dataset):
+        print(f"Image {str(i + 1):>{len(str(len(dataset)))}}/{len(dataset)}", end="\r")
+        # select the center slice for display (the bed location is constant over all slices)
+        mu_map = mu_map[mu_map.shape[0] // 2]
+
+        # save the points of the contour in a list and defined a mouse callback
+        points = []
+
+        def mouse_callback(event, x, y, flags, param):
+            if event == cv.EVENT_LBUTTONUP:
+                points.append((x, y))
+
+        # create a window for display
+        cv.namedWindow(window_name, cv.WINDOW_NORMAL)
+        cv.resizeWindow(window_name, 1024, 1024)
+        cv.setMouseCallback(window_name, mouse_callback)
+
+        # set initial visual mode
+        visual_mode = VisualMode.DRAW_CONTOURS
+        while True:
+            # compute image to display
+            to_show = to_grayscale(mu_map)
+
+            if visual_mode == VisualMode.DRAW_CONTOURS:
+                # draw lines between all points
+                for p1, p2 in zip(points[:-1], points[1:]):
+                    to_show = cv.line(to_show, p1, p2, color=COLOR_WHITE, thickness=1)
+                # close the contour
+                if len(points) > 0:
+                    to_show = cv.line(
+                        to_show, points[0], points[-1], color=COLOR_WHITE, thickness=1
+                    )
+
+                # draw all points as circles
+                for point in points:
+                    to_show = cv.circle(
+                        to_show, point, radius=2, color=COLOR_BLACK, thickness=-1
+                    )
+                    to_show = cv.circle(
+                        to_show, point, radius=2, color=COLOR_WHITE, thickness=1
+                    )
+            else:
+                # eliminate area inside the contour
+                _points = np.array(points).astype(int)
+                to_show = cv.drawContours(
+                    to_show, [_points], -1, COLOR_BLACK, thickness=-1
+                )
+
+            # visualize image and handle inputs
+            cv.imshow(window_name, to_show)
+            key = cv.waitKey(100)
+            if key == ord("q"):
+                exit(0)
+            elif key == ord("d"):
+                points = points[:-1]
+            elif key == ord("n"):
+                break
+            elif key == ord("v"):
+                visual_mode = (
+                    VisualMode.DRAW_CONTOURS
+                    if visual_mode == VisualMode.HIDE_BED
+                    else VisualMode.HIDE_BED
+                )
+
+        # remove current window
+        cv.destroyWindow(window_name)
+
+        # save current contour in dict
+        bed_contours[int(dataset.table.loc[i, "id"])] = points
+
+    # write contours to output file
+    with open(args.output_file, mode="w") as f:
+        f.write(json.dumps(data, indent=2, sort_keys=True))
-- 
GitLab