# ImageManip remap

This examples shows an application that processes a camera stream through two image manipulation nodes with 90-degree rotations
and different output sizes, displaying the original and manipulated frames with synchronized rotated rectangles, using DepthAI's
pipeline and transformation handling.

This example requires the DepthAI v3 API, see [installation instructions](https://docs.luxonis.com/software-v3/depthai.md).

## Pipeline

### examples/image_manip_remap.pipeline.json

```json
{"pipeline": {"connections": [{"node1Id": 1, "node1Output": "out", "node1OutputGroup": "", "node2Id": 3, "node2Input": "in", "node2InputGroup": ""}, {"node1Id": 0, "node1Output": "0", "node1OutputGroup": "dynamicOutputs", "node2Id": 5, "node2Input": "in", "node2InputGroup": ""}, {"node1Id": 0, "node1Output": "0", "node1OutputGroup": "dynamicOutputs", "node2Id": 1, "node2Input": "inputImage", "node2InputGroup": ""}], "globalProperties": {"calibData": null, "cameraTuningBlobSize": null, "cameraTuningBlobUri": "", "leonCssFrequencyHz": 700000000.0, "leonMssFrequencyHz": 700000000.0, "pipelineName": null, "pipelineVersion": null, "sippBufferSize": 18432, "sippDmaBufferSize": 16384, "xlinkChunkSize": -1}, "nodes": [[5, {"alias": "", "id": 5, "ioInfo": [[["", "in"], {"blocking": true, "group": "", "id": 8, "name": "in", "queueSize": 3, "type": 3, "waitForMessage": false}]], "logLevel": 3, "name": "XLinkOut", "parentId": -1, "properties": {"maxFpsLimit": -1.0, "metadataOnly": false, "streamName": "__x_0_0"}}], [3, {"alias": "", "id": 3, "ioInfo": [[["", "in"], {"blocking": true, "group": "", "id": 7, "name": "in", "queueSize": 3, "type": 3, "waitForMessage": false}]], "logLevel": 3, "name": "XLinkOut", "parentId": -1, "properties": {"maxFpsLimit": -1.0, "metadataOnly": false, "streamName": "__x_1__out"}}], [1, {"alias": "", "id": 1, "ioInfo": [[["", "out"], {"blocking": false, "group": "", "id": 6, "name": "out", "queueSize": 8, "type": 0, "waitForMessage": false}], [["", "inputImage"], {"blocking": true, "group": "", "id": 5, "name": "inputImage", "queueSize": 3, "type": 3, "waitForMessage": false}], [["", "inputConfig"], {"blocking": true, "group": "", "id": 4, "name": "inputConfig", "queueSize": 3, "type": 3, "waitForMessage": false}]], "logLevel": 3, "name": "ImageManip", "parentId": -1, "properties": {"backend": 0, "initialConfig": {"base": {"background": 0, "backgroundB": 0, "backgroundG": 0, "backgroundR": 0, "center": true, "colormap": 0, "operations": [{"op": {"index": 1, "value": {"angle": 1.5707963705062866, "center": true, "normalized": false, "offsetX": 0.0, "offsetY": 0.0}}}], "outputHeight": 320, "outputWidth": 200, "resizeMode": 1, "undistort": false}, "outputFrameType": 33, "reusePreviousImage": false, "skipCurrentImage": false}, "numFramesPool": 4, "outputFrameSize": 1048576, "performanceMode": 0}}], [0, {"alias": "", "id": 0, "ioInfo": [[["dynamicOutputs", "0"], {"blocking": false, "group": "dynamicOutputs", "id": 3, "name": "0", "queueSize": 8, "type": 0, "waitForMessage": false}], [["", "raw"], {"blocking": false, "group": "", "id": 2, "name": "raw", "queueSize": 8, "type": 0, "waitForMessage": false}], [["", "mockIsp"], {"blocking": true, "group": "", "id": 1, "name": "mockIsp", "queueSize": 8, "type": 3, "waitForMessage": false}], [["", "inputControl"], {"blocking": true, "group": "", "id": 0, "name": "inputControl", "queueSize": 3, "type": 3, "waitForMessage": false}]], "logLevel": 3, "name": "Camera", "parentId": -1, "properties": {"boardSocket": 0, "cameraName": "", "fps": -1.0, "imageOrientation": -1, "initialControl": {"aeLockMode": false, "aeMaxExposureTimeUs": 0, "aeRegion": {"height": 0, "priority": 0, "width": 0, "x": 0, "y": 0}, "afRegion": {"height": 0, "priority": 0, "width": 0, "x": 0, "y": 0}, "antiBandingMode": 0, "autoFocusMode": 3, "awbLockMode": false, "awbMode": 0, "brightness": 0, "captureIntent": 0, "chromaDenoise": 0, "cmdMask": 0, "contrast": 0, "controlMode": 0, "effectMode": 0, "enableHdr": false, "expCompensation": 0, "expManual": {"exposureTimeUs": 0, "frameDurationUs": 0, "sensitivityIso": 0}, "frameSyncMode": 0, "lensPosAutoInfinity": 0, "lensPosAutoMacro": 0, "lensPosition": 0, "lensPositionRaw": 0.0, "lowPowerNumFramesBurst": 0, "lowPowerNumFramesDiscard": 0, "lumaDenoise": 0, "miscControls": [], "saturation": 0, "sceneMode": 0, "sharpness": 0, "strobeConfig": {"activeLevel": 0, "enable": 0, "gpioNumber": 0}, "strobeTimings": {"durationUs": 0, "exposureBeginOffsetUs": 0, "exposureEndOffsetUs": 0}, "wbColorTemp": 0}, "isp3aFps": 0, "mockIspHeight": -1, "mockIspWidth": -1, "numFramesPoolIsp": 3, "numFramesPoolPreview": 4, "numFramesPoolRaw": 3, "numFramesPoolStill": 4, "numFramesPoolVideo": 4, "outputRequests": [{"enableUndistortion": null, "fps": {"value": {"index": 0, "value": 30.0}}, "resizeMode": 0, "size": {"value": {"index": 0, "value": [640, 400]}}, "type": 10}], "resolutionHeight": -1, "resolutionWidth": -1}}]]}}
```

## Source code

#### Python

```python
import depthai as dai
import cv2
import numpy as np

def draw_rotated_rectangle(frame, center, size, angle, color, thickness=2):
    """
    Draws a rotated rectangle on the given frame.

    Args:
        frame (numpy.ndarray): The image/frame to draw on.
        center (tuple): The (x, y) coordinates of the rectangle's center.
        size (tuple): The (width, height) of the rectangle.
        angle (float): The rotation angle of the rectangle in degrees (counter-clockwise).
        color (tuple): The color of the rectangle in BGR format (e.g., (0, 255, 0) for green).
        thickness (int): The thickness of the rectangle edges. Default is 2.
    """
    # Create a rotated rectangle
    rect = ((center[0], center[1]), (size[0], size[1]), angle)

    # Get the four vertices of the rotated rectangle
    box = cv2.boxPoints(rect)
    box = np.intp(box)  # Convert to integer coordinates

    # Draw the rectangle on the frame
    cv2.polylines(frame, [box], isClosed=True, color=color, thickness=thickness)

with dai.Pipeline() as pipeline:
    cam = pipeline.create(dai.node.Camera).build()
    camOut = cam.requestOutput((640, 400), dai.ImgFrame.Type.BGR888i, fps = 30.0)
    manip1 = pipeline.create(dai.node.ImageManip)
    manip2 = pipeline.create(dai.node.ImageManip)

    camOut.link(manip1.inputImage)
    manip1.out.link(manip2.inputImage)

    manip1.initialConfig.addRotateDeg(90)
    manip1.initialConfig.setOutputSize(200, 320)

    manip2.initialConfig.addRotateDeg(90)
    manip2.initialConfig.setOutputSize(320, 200)
    manip2.setRunOnHost(True)

    outQcam = camOut.createOutputQueue()
    outQ1 = manip1.out.createOutputQueue()
    outQ2 = manip2.out.createOutputQueue()

    pipeline.start()

    while True:
        camFrame: dai.ImgFrame = outQcam.get()
        manip1Frame: dai.ImgFrame = outQ1.get()
        manip2Frame: dai.ImgFrame = outQ2.get()

        camCv = camFrame.getCvFrame()
        manip1Cv = manip1Frame.getCvFrame()
        manip2Cv = manip2Frame.getCvFrame()

        rect2 = dai.RotatedRect(dai.Rect(dai.Point2f(100, 100), dai.Point2f(200, 150)), 0)
        rect1 = manip2Frame.getTransformation().remapRectTo(manip1Frame.getTransformation(), rect2)
        rectcam = manip1Frame.getTransformation().remapRectTo(camFrame.getTransformation(), rect1)

        draw_rotated_rectangle(manip2Cv, (rect2.center.x, rect2.center.y), (rect2.size.width, rect2.size.height), rect2.angle, (255, 0, 0))
        draw_rotated_rectangle(manip1Cv, (rect1.center.x, rect1.center.y), (rect1.size.width, rect1.size.height), rect1.angle, (255, 0, 0))
        draw_rotated_rectangle(camCv, (rectcam.center.x, rectcam.center.y), (rectcam.size.width, rectcam.size.height), rectcam.angle, (255, 0, 0))

        cv2.imshow("cam", camCv)
        cv2.imshow("manip1", manip1Cv)
        cv2.imshow("manip2", manip2Cv)
        if cv2.waitKey(1) == ord('q'):
            break
```

#### C++

```cpp
#include <atomic>
#include <csignal>
#include <iostream>
#include <memory>
#include <opencv2/opencv.hpp>

#include "depthai/depthai.hpp"

// Global flag for graceful shutdown
std::atomic<bool> quitEvent(false);

// Signal handler
void signalHandler(int signum) {
    quitEvent = true;
}

// Helper function to draw rotated rectangle
void draw_rotated_rectangle(cv::Mat& frame, const cv::Point2f& center, const cv::Size2f& size, float angle, const cv::Scalar& color, int thickness = 2) {
    // Create a rotated rectangle
    cv::RotatedRect rect(center, size, angle);

    // Get the four vertices of the rotated rectangle
    cv::Point2f vertices[4];
    rect.points(vertices);

    // Convert to integer points for drawing
    std::vector<cv::Point> points;
    for(int i = 0; i < 4; i++) {
        points.push_back(cv::Point(static_cast<int>(vertices[i].x), static_cast<int>(vertices[i].y)));
    }

    // Draw the rectangle on the frame
    cv::polylines(frame, std::vector<std::vector<cv::Point>>{points}, true, color, thickness);
}

int main() {
    // Set up signal handlers
    signal(SIGTERM, signalHandler);
    signal(SIGINT, signalHandler);

    try {
        // Create pipeline
        dai::Pipeline pipeline;

        // Create nodes
        auto cam = pipeline.create<dai::node::Camera>();
        cam->build();

        auto manip1 = pipeline.create<dai::node::ImageManip>();
        auto manip2 = pipeline.create<dai::node::ImageManip>();

        // Configure camera output
        auto camOut = cam->requestOutput(std::make_pair(640, 400), dai::ImgFrame::Type::BGR888i, dai::ImgResizeMode::LETTERBOX, 20, 20);

        // Configure image manipulators
        manip1->initialConfig->addRotateDeg(90);
        manip1->initialConfig->setOutputSize(200, 320);

        manip2->initialConfig->addRotateDeg(90);
        manip2->initialConfig->setOutputSize(320, 200);
        manip2->setRunOnHost(true);

        // Linking
        camOut->link(manip1->inputImage);
        manip1->out.link(manip2->inputImage);

        // Create output queues
        auto outQcam = camOut->createOutputQueue();
        auto outQ1 = manip1->out.createOutputQueue();
        auto outQ2 = manip2->out.createOutputQueue();

        // Start pipeline
        pipeline.start();

        // Main loop
        while(!quitEvent) {
            // Get frames
            auto camFrame = outQcam->get<dai::ImgFrame>();
            auto manip1Frame = outQ1->get<dai::ImgFrame>();
            auto manip2Frame = outQ2->get<dai::ImgFrame>();

            // Convert to OpenCV format
            cv::Mat camCv = camFrame->getCvFrame();
            cv::Mat manip1Cv = manip1Frame->getCvFrame();
            cv::Mat manip2Cv = manip2Frame->getCvFrame();

            // Create and remap rectangles
            dai::RotatedRect rect2(dai::Rect(dai::Point2f(100, 100), dai::Point2f(200, 150)), 0);
            auto rect1 = manip2Frame->transformation.remapRectTo(manip1Frame->transformation, rect2);
            auto rectcam = manip1Frame->transformation.remapRectTo(camFrame->transformation, rect1);

            // Draw rectangles
            draw_rotated_rectangle(
                manip2Cv, cv::Point2f(rect2.center.x, rect2.center.y), cv::Size2f(rect2.size.width, rect2.size.height), rect2.angle, cv::Scalar(255, 0, 0));

            draw_rotated_rectangle(
                manip1Cv, cv::Point2f(rect1.center.x, rect1.center.y), cv::Size2f(rect1.size.width, rect1.size.height), rect1.angle, cv::Scalar(255, 0, 0));

            draw_rotated_rectangle(camCv,
                                   cv::Point2f(rectcam.center.x, rectcam.center.y),
                                   cv::Size2f(rectcam.size.width, rectcam.size.height),
                                   rectcam.angle,
                                   cv::Scalar(255, 0, 0));

            // Display frames
            cv::imshow("cam", camCv);
            cv::imshow("manip1", manip1Cv);
            cv::imshow("manip2", manip2Cv);

            // Check for quit key
            if(cv::waitKey(1) == 'q') {
                break;
            }
        }

        // Cleanup
        pipeline.stop();
        pipeline.wait();

    } catch(const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        return 1;
    }

    return 0;
}
```

### Need assistance?

Head over to [Discussion Forum](https://discuss.luxonis.com/) for technical support or any other questions you might have.
