# Thermal Crosshair

The example uses DepthAI to process and display thermal camera data, showing a colorized temperature map and raw thermal image
side-by-side, with a mouse-controlled crosshair displaying real-time temperature readings in degrees Celsius.

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

## Demo

## Pipeline

### examples/thermal_crosshair.pipeline.json

```json
{"pipeline": {"connections": [{"node1Id": 3, "node1Output": "out", "node1OutputGroup": "", "node2Id": 0, "node2Input": "inputConfig", "node2InputGroup": ""}, {"node1Id": 0, "node1Output": "color", "node1OutputGroup": "", "node2Id": 4, "node2Input": "in", "node2InputGroup": ""}], "globalProperties": {"calibData": null, "cameraTuningBlobSize": null, "cameraTuningBlobUri": "", "leonCssFrequencyHz": 700000000.0, "leonMssFrequencyHz": 700000000.0, "pipelineName": null, "pipelineVersion": null, "sippBufferSize": 18432, "sippDmaBufferSize": 16384, "xlinkChunkSize": -1}, "nodes": [[4, {"alias": "", "id": 4, "ioInfo": [[["", "in"], {"blocking": true, "group": "", "id": 4, "name": "in", "queueSize": 3, "type": 3, "waitForMessage": false}]], "logLevel": 3, "name": "XLinkOut", "parentId": -1, "properties": {"maxFpsLimit": -1.0, "metadataOnly": false, "streamName": "__x_0_color"}}], [3, {"alias": "", "id": 3, "ioInfo": [[["", "out"], {"blocking": false, "group": "", "id": 3, "name": "out", "queueSize": 8, "type": 0, "waitForMessage": false}]], "logLevel": 3, "name": "XLinkIn", "parentId": -1, "properties": {"maxDataSize": 5242880, "numFrames": 8, "streamName": "__x_0__inputConfig"}}], [0, {"alias": "", "id": 0, "ioInfo": [[["", "color"], {"blocking": false, "group": "", "id": 2, "name": "color", "queueSize": 8, "type": 0, "waitForMessage": false}], [["", "temperature"], {"blocking": false, "group": "", "id": 1, "name": "temperature", "queueSize": 8, "type": 0, "waitForMessage": false}], [["", "inputConfig"], {"blocking": true, "group": "", "id": 0, "name": "inputConfig", "queueSize": 3, "type": 3, "waitForMessage": false}]], "logLevel": 3, "name": "Thermal", "parentId": -1, "properties": {"boardSocket": -1, "fps": 25.0, "initialConfig": {"ambientParams": {"atmosphericTemperature": null, "atmosphericTransmittance": null, "distance": null, "gainMode": null, "reflectionTemperature": null, "targetEmissivity": null}, "ffcParams": {"antiFallProtectionThresholdHighGainMode": null, "antiFallProtectionThresholdLowGainMode": null, "autoFFC": null, "autoFFCTempThreshold": null, "closeManualShutter": null, "fallProtection": null, "maxFFCInterval": null, "minFFCInterval": null, "minShutterInterval": null}, "imageParams": {"brightnessLevel": null, "contrastLevel": null, "digitalDetailEnhanceLevel": null, "orientation": null, "spatialNoiseFilterLevel": null, "timeNoiseFilterLevel": null}}, "numFramesPool": 4}}]]}}
```

## Source code

#### Python

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

mouseX, mouseY = 0, 0

def onMouse(event, x, y, *args):
    global mouseX, mouseY
    mouseX = x
    mouseY = y

# Thermal camera

with dai.Pipeline(True) as pipeline:
    thermal = pipeline.create(dai.node.Thermal)
    # Output raw: FP16 temperature data (degrees Celsius)
    qTemperature = thermal.temperature.createOutputQueue()
    # Output color: YUV422i image data
    qColor = thermal.color.createOutputQueue()
    pipeline.start()
    MAGMA_WINDOW_NAME = "Colorized Temperature"
    IMAGE_WINDOW_NAME = "Thermal image"
    # Scale 4x and position one next to another
    cv2.namedWindow(MAGMA_WINDOW_NAME, cv2.WINDOW_NORMAL)
    cv2.namedWindow(IMAGE_WINDOW_NAME, cv2.WINDOW_NORMAL)
    initialRescaleAndPositionDone = False

    while True:
        inTemperature = qTemperature.get()
        inColor = qColor.get()

        thermalData = (
            inTemperature.getData()
            .view(dtype=np.float16)
            .reshape((inTemperature.getHeight(), inTemperature.getWidth()))
            .astype(np.float32)
        )
        normalizedThermalData = cv2.normalize(thermalData, None, 0, 1, cv2.NORM_MINMAX)
        normalizedThermalData = (normalizedThermalData * 255).astype(np.uint8)
        colormappedFrame = cv2.applyColorMap(normalizedThermalData, cv2.COLORMAP_MAGMA)
        if not initialRescaleAndPositionDone:
            cv2.moveWindow(MAGMA_WINDOW_NAME, 0, 0)
            width, height = colormappedFrame.shape[1], colormappedFrame.shape[0]
            cv2.resizeWindow(MAGMA_WINDOW_NAME, width * 4, height * 4)
            cv2.moveWindow(IMAGE_WINDOW_NAME, width * 4, 0)
            cv2.resizeWindow(IMAGE_WINDOW_NAME, width * 4, height * 4)
            cv2.setMouseCallback(MAGMA_WINDOW_NAME, onMouse)
            cv2.setMouseCallback(IMAGE_WINDOW_NAME, onMouse)
            initialRescaleAndPositionDone = True
        colormappedFrame = cv2.applyColorMap(colormappedFrame, cv2.COLORMAP_MAGMA)
        if (
            mouseX < 0
            or mouseY < 0
            or mouseX >= thermalData.shape[1]
            or mouseY >= thermalData.shape[0]
        ):
            mouseX = max(0, min(mouseX, thermalData.shape[1] - 1))
            mouseY = max(0, min(mouseY, thermalData.shape[0] - 1))
        textColor = (255, 255, 255)
        # Draw crosshair
        cv2.line(
            colormappedFrame,
            (mouseX - 10, mouseY),
            (mouseX + 10, mouseY),
            textColor,
            1,
        )
        cv2.line(
            colormappedFrame,
            (mouseX, mouseY - 10),
            (mouseX, mouseY + 10),
            textColor,
            1,
        )
        # Draw deg C
        text = f"{thermalData[mouseY, mouseX]:.2f} deg C"
        putTextLeft = mouseX > colormappedFrame.shape[1] / 2
        cv2.putText(
            colormappedFrame,
            text,
            (mouseX - 100 if putTextLeft else mouseX + 10, mouseY - 10),
            cv2.FONT_HERSHEY_SIMPLEX,
            0.5,
            textColor,
            1,
        )

        cv2.imshow(MAGMA_WINDOW_NAME, colormappedFrame)
        cv2.imshow(IMAGE_WINDOW_NAME, inColor.getCvFrame())

        if cv2.waitKey(1) == ord("q"):
            break
```

#### C++

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

#include "depthai/depthai.hpp"

volatile int mouseX = 0, mouseY = 0;

void onMouse(int event, int x, int y, int flags, void* userdata) {
    mouseX = x;
    mouseY = y;
}

int main() {
    // Create pipeline
    dai::Pipeline pipeline(true);

    // Create nodes
    auto thermal = pipeline.create<dai::node::Thermal>();
    // Output raw: FP16 temperature data (degrees Celsius)
    auto qTemperature = thermal->temperature.createOutputQueue();
    // Output color: YUV422i image data
    auto qColor = thermal->color.createOutputQueue();

    // Start pipeline
    pipeline.start();

    const char* MAGMA_WINDOW_NAME = "Colorized Temperature";
    const char* IMAGE_WINDOW_NAME = "Thermal image";

    // Scale 4x and position one next to another
    cv::namedWindow(MAGMA_WINDOW_NAME, cv::WINDOW_NORMAL);
    cv::namedWindow(IMAGE_WINDOW_NAME, cv::WINDOW_NORMAL);
    bool initialRescaleAndPositionDone = false;

    while(true) {
        auto inTemperature = qTemperature->get<dai::ImgFrame>();
        auto inColor = qColor->get<dai::ImgFrame>();

        // temperature data is float16: convert it to float32
        cv::Mat thermalData(inTemperature->getHeight(), inTemperature->getWidth(), CV_32F);
        inTemperature->getCvFrame().convertTo(thermalData, CV_32F);

        // Normalize thermal data
        cv::Mat normalizedThermalData;
        cv::normalize(thermalData, normalizedThermalData, 0, 1, cv::NORM_MINMAX);
        normalizedThermalData.convertTo(normalizedThermalData, CV_8U, 255.0);

        // Apply color map
        cv::Mat colormappedFrame;
        cv::applyColorMap(normalizedThermalData, colormappedFrame, cv::COLORMAP_MAGMA);

        if(!initialRescaleAndPositionDone) {
            cv::moveWindow(MAGMA_WINDOW_NAME, 0, 0);
            int width = colormappedFrame.cols;
            int height = colormappedFrame.rows;
            cv::resizeWindow(MAGMA_WINDOW_NAME, width * 4, height * 4);
            cv::moveWindow(IMAGE_WINDOW_NAME, width * 4, 0);
            cv::resizeWindow(IMAGE_WINDOW_NAME, width * 4, height * 4);
            cv::setMouseCallback(MAGMA_WINDOW_NAME, onMouse);
            cv::setMouseCallback(IMAGE_WINDOW_NAME, onMouse);
            initialRescaleAndPositionDone = true;
        }

        // Clamp mouse coordinates
        mouseX = std::max(0, std::min(static_cast<int>(mouseX), thermalData.cols - 1));
        mouseY = std::max(0, std::min(static_cast<int>(mouseY), thermalData.rows - 1));

        // Draw crosshair
        cv::Scalar textColor(255, 255, 255);
        cv::line(colormappedFrame, cv::Point(mouseX - 10, mouseY), cv::Point(mouseX + 10, mouseY), textColor, 1);
        cv::line(colormappedFrame, cv::Point(mouseX, mouseY - 10), cv::Point(mouseX, mouseY + 10), textColor, 1);

        // Draw temperature text
        std::string text = std::to_string(thermalData.at<float>(mouseY, mouseX)) + " deg C";
        bool putTextLeft = mouseX > colormappedFrame.cols / 2;
        cv::putText(colormappedFrame, text, cv::Point(mouseX - (putTextLeft ? 100 : -10), mouseY - 10), cv::FONT_HERSHEY_SIMPLEX, 0.5, textColor, 1);

        cv::imshow(MAGMA_WINDOW_NAME, colormappedFrame);
        cv::imshow(IMAGE_WINDOW_NAME, inColor->getCvFrame());

        if(cv::waitKey(1) == 'q') {
            break;
        }
    }

    return 0;
}
```

### Need assistance?

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