diff --git a/src/dopt_sensor_anomalies/detection.py b/src/dopt_sensor_anomalies/detection.py index 5d55cf3..64d3b3c 100644 --- a/src/dopt_sensor_anomalies/detection.py +++ b/src/dopt_sensor_anomalies/detection.py @@ -28,13 +28,7 @@ warnings.filterwarnings( category=UserWarning, ) -# input parameters: user-defined -file_path: Path = Path(r"C:\Users\demon\Documents\EKF\Analyse_fuer_Florian\bild2.bmp") -pixels_per_metric_X: float = 0.251 -pixels_per_metric_Y: float = 0.251 - -# measuring def midpoint( pt_A: npt.NDArray[np.floating], pt_B: npt.NDArray[np.floating], @@ -47,11 +41,8 @@ def check_box_redundancy( box_2: t.Box, tolerance: float = 5.0, ) -> bool: - # unpack the boxes c1, s1, _ = box_1 c2, s2, _ = box_2 - # sort width and height such that (w, h) == (h, w) is treated the same - # (might have been recognized in different orders) s1 = sorted(s1) s2 = sorted(s2) @@ -61,7 +52,6 @@ def check_box_redundancy( return bool(center_dist < tolerance and size_diff < tolerance) -# ** main function def measure_length( img_path: Path, pixels_per_metric_X: float, @@ -74,78 +64,61 @@ def measure_length( cropped = image[500:1500, 100 : image.shape[1] - 100] orig = cropped.copy() - # change colours in the image to black and white + gray = cv2.cvtColor(cropped, cv2.COLOR_BGR2GRAY) _, binary = cv2.threshold(gray, const.THRESHOLD_BW, 255, cv2.THRESH_BINARY) - # perform edge detection, identify rectangular shapes + kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5)) closed = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel) edged = cv2.Canny(closed, 50, 100) - # find contours in the edge map + cnts = cv2.findContours(edged.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) cnts = imutils.grab_contours(cnts) if cnts is None: raise errors.ContourCalculationError( "No contours were found in the provided image. Can not continue analysis." ) - # sort the contours from left to right (i.e., use x coordinates) + cnts, _ = contours.sort_contours(cnts) - # check if this sorting was correct (might not be correct if we have overlaps or misfindings) - # get x coordinates of bounding boxes x_coords = [cv2.boundingRect(c)[0] for c in cnts] - # check if x coordinates are sorted in increasing order is_sorted = np.all(x1 <= x2 for x1, x2 in zip(x_coords, x_coords[1:])) # type: ignore if not is_sorted: raise errors.ContourCalculationError( "Contour detection not valid: contours are not " "properly sorted from left to right." ) - # to store only electrodes contours and nothing redundant accepted_boxes: list[t.Box] = [] filtered_cnts: list[Any] = [] - # loop over the contours individually for c in cnts: - # compute the rotated bounding box of the contour rbox = cast(t.Box, cv2.minAreaRect(c)) - # !! should only be newer OpenCV versions - # box = cv2.cv.BoxPoints(rbox) if is_cv2() else cv2.boxPoints(rbox) box = cv2.boxPoints(rbox) box = np.array(box, dtype=np.int32) - # order the points in the contour in top-left, top-right, bottom-right, and bottom-left box = cast(npt.NDArray[np.float32], perspective.order_points(box)) - # unpack the bounding box (tl, tr, br, bl) = box - # compute the midpoints between the top-left and top-right as well as bottom-left and bottom-right coordinates (tltrX, tltrY) = midpoint(tl, tr) (blbrX, blbrY) = midpoint(bl, br) - # compute the midpoints between the top-left and bottom-left as well as the top-right and bottom-right coordinates (tlblX, tlblY) = midpoint(tl, bl) (trbrX, trbrY) = midpoint(tr, br) - # compute the Euclidean distance between the midpoints dA = dist.euclidean((tltrX, tltrY), (blbrX, blbrY)) dB = dist.euclidean((tlblX, tlblY), (trbrX, trbrY)) - # if the contour is not sufficiently large, ignore it if dA < 100 or dB < 100: continue - # check for redundancy is_duplicate = any( check_box_redundancy(rbox, existing) for existing in accepted_boxes ) if is_duplicate: continue - # accept box and contour accepted_boxes.append(rbox) filtered_cnts.append(c) - # compute the size of the electrode object - dimA = dA / pixels_per_metric_Y # y - dimB = dB / pixels_per_metric_X # x + dimA = dA / pixels_per_metric_Y + dimB = dB / pixels_per_metric_X data_csv.extend( [ @@ -160,7 +133,6 @@ def measure_length( "Contour detection not valid: no contours recognized" ) - # if incorrect number of electrodes has been identified num_contours = len(filtered_cnts) if num_contours != const.NUM_VALID_ELECTRODES: raise errors.InvalidElectrodeCount( @@ -168,7 +140,6 @@ def measure_length( f"expected value: count = {num_contours}, expected = {const.NUM_VALID_ELECTRODES}" ) - # identify left and right sensor areas x_min = min(np.min(c[:, 0, 0]) for c in filtered_cnts) - 20 x_max = max(np.max(c[:, 0, 0]) for c in filtered_cnts) + 20 y_min = min(np.min(c[:, 0, 1]) for c in filtered_cnts) - 20 @@ -178,15 +149,12 @@ def measure_length( leftmost_x_fourth = min(filtered_cnts[3][:, 0, 0]) x_middle = rightmost_x_third + int((leftmost_x_fourth - rightmost_x_third) / 2.0) - # perform further cropping and separation of left and right sensor cropped_sensor_left = orig[y_min:y_max, x_min:x_middle] cropped_sensor_right = orig[y_min:y_max, x_middle:x_max] return data_csv, t.SensorImages(left=cropped_sensor_left, right=cropped_sensor_right) -# helper function -# anomaly detection def infer_image( image: npt.NDArray[np.uint8], model: Patchcore, @@ -211,7 +179,6 @@ def infer_image( anomaly_label = output.pred_label.item() anomaly_map = output.anomaly_map.squeeze().cpu().numpy() - # resize heatmap to original image size img_np = np.array(pil_image) anomaly_map_resized = cv2.resize(anomaly_map, (img_np.shape[1], img_np.shape[0])) @@ -223,7 +190,6 @@ def infer_image( ) -# ** main function def anomaly_detection( img_path: Path, detection_models: t.DetectionModels, @@ -233,15 +199,12 @@ def anomaly_detection( file_stem = img_path.stem folder_path = img_path.parent - # reconstruct the model and initialize the engine model = Patchcore( backbone=const.BACKBONE, layers=const.LAYERS, coreset_sampling_ratio=const.RATIO ) - # preparation for plot _, axes = plt.subplots(1, 2, figsize=(12, 6)) - # loop over left and right sensor + for i, (side, image) in enumerate(sensor_images.items()): - # Ich habe die Modellpfade als Funktionsparameter hinzugefügt image = cast(npt.NDArray[np.uint8], image) checkpoint = torch.load(detection_models[side]) model.load_state_dict(checkpoint["model_state_dict"]) diff --git a/tests/test_detection.py b/tests/test_detection.py index c502181..d245d51 100644 --- a/tests/test_detection.py +++ b/tests/test_detection.py @@ -10,28 +10,6 @@ import dopt_sensor_anomalies.detection as detect import dopt_sensor_anomalies.types as t from dopt_sensor_anomalies import constants, errors -# TODO remove -# @pytest.fixture(scope="module") -# def img_paths() -> tuple[Path, ...]: -# img_folder = Path(__file__).parent / "_img" -# if not img_folder.exists(): -# raise FileNotFoundError("Img path not existing") -# img_paths = tuple(img_folder.glob("*.bmp")) -# if not img_paths: -# raise ValueError("No images found") -# return img_paths - - -# @pytest.fixture(scope="module") -# def single_img_path() -> Path: -# img_folder = Path(__file__).parent / "_img" -# if not img_folder.exists(): -# raise FileNotFoundError("Img path not existing") -# img_paths = tuple(img_folder.glob("*_12.bmp")) -# if not img_paths: -# raise ValueError("No images found") -# return img_paths[0] - def test_midpoint(): ptA = np.array([1.0, 2.0])