# Thermal

The example streams thermal sensor data, displaying a colorized temperature image with a mouse-controlled crosshair showing
real-time temperature values, and provides keyboard controls to adjust sensor settings like brightness, orientation, noise
filtering, and shutter operation.

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

## Pipeline

### examples/thermal.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

THERMAL_IMAGE_BRIGHTNESS_STEP = 10

with dai.Pipeline(True) as pipeline:
    thermal = pipeline.create(dai.node.Thermal)
    thermalImgOut = thermal.color.createOutputQueue()
    thermalConfIn = thermal.inputConfig.createInputQueue()
    thermalConf = dai.ThermalConfig()

    pipeline.start()
    WINDOW_NAME = "thermal"
    cv2.namedWindow(WINDOW_NAME, cv2.WINDOW_NORMAL)
    initialRescaleDone = False
    while True:
        thermalImg = thermalImgOut.get()
        if not initialRescaleDone:
            cv2.resizeWindow(WINDOW_NAME, thermalImg.getWidth(), thermalImg.getHeight())
            initialRescaleDone = True
        cv2.imshow(WINDOW_NAME, thermalImg.getCvFrame())
        key = cv2.waitKey(1)
        changed = False
        if key == ord("q"):
            break
        elif key == ord("n"):
            if thermalConf.imageParams.brightnessLevel is None:
                thermalConf.imageParams.brightnessLevel = THERMAL_IMAGE_BRIGHTNESS_STEP
            if (
                thermalConf.imageParams.brightnessLevel - THERMAL_IMAGE_BRIGHTNESS_STEP
            ) <= THERMAL_IMAGE_BRIGHTNESS_STEP:
                thermalConf.imageParams.brightnessLevel = THERMAL_IMAGE_BRIGHTNESS_STEP
            thermalConf.imageParams.brightnessLevel -= THERMAL_IMAGE_BRIGHTNESS_STEP
            print(
                "Set image brightness level to ",
                thermalConf.imageParams.brightnessLevel,
            )
            changed = True
        elif key == ord("m"):
            if thermalConf.imageParams.brightnessLevel is None:
                thermalConf.imageParams.brightnessLevel = 0
            if (
                thermalConf.imageParams.brightnessLevel + THERMAL_IMAGE_BRIGHTNESS_STEP
            ) >= 255:
                thermalConf.imageParams.brightnessLevel = (
                    255 - THERMAL_IMAGE_BRIGHTNESS_STEP
                )
            thermalConf.imageParams.brightnessLevel += THERMAL_IMAGE_BRIGHTNESS_STEP
            print(
                "Set image brightness level to ",
                thermalConf.imageParams.brightnessLevel,
            )
            changed = True
        elif key == ord("a"):
            if thermalConf.ffcParams.autoFFC is None:
                thermalConf.ffcParams.autoFFC = True
            thermalConf.ffcParams.autoFFC = not thermalConf.ffcParams.autoFFC
            print(
                "Set auto shutter to",
                "on." if thermalConf.ffcParams.autoFFC else "off.",
            )
            changed = True
        elif key == ord("s"):
            if thermalConf.ffcParams.closeManualShutter is None:
                thermalConf.ffcParams.closeManualShutter = False
            thermalConf.ffcParams.closeManualShutter = (
                not thermalConf.ffcParams.closeManualShutter
            )
            print(
                "Closing" if thermalConf.ffcParams.closeManualShutter else "Opening",
                "manual shutter.",
            )
            changed = True

        if changed:
            thermalConfIn.send(thermalConf)
```

#### C++

```cpp
/*
    Thermal camera example.
    Streams the temperature image from a thermal sensor and
    displays it with a crosshair with the temperature value at that point.
*/
#include <iostream>
#include <opencv2/opencv.hpp>

#include "depthai/depthai.hpp"
#include "depthai/pipeline/InputQueue.hpp"
#include "depthai/pipeline/datatype/ThermalConfig.hpp"

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

const cv::Scalar WHITE(255, 255, 255);

constexpr int THERMAL_IMAGE_BRIGHTNESS_STEP = 10;

int main(int argc, char** args) {
    dai::Pipeline pipeline(true);
    auto thermal = pipeline.create<dai::node::Thermal>();
    auto outTemp = thermal->temperature.createOutputQueue();
    auto inConfig = thermal->inputConfig.createInputQueue();
    auto thermalConfig = std::make_shared<dai::ThermalConfig>();
    pipeline.start();

    const char* TEMP_WINDOW = "Thermal";
    cv::namedWindow(TEMP_WINDOW, cv::WINDOW_NORMAL);
    cv::setMouseCallback(TEMP_WINDOW, mouseCallback);
    const int WINDOW_SCALE_MULTIPLIER = 4;
    bool initialWindowResizeDone = false;
    while(true) {
        auto tempImgFrame = outTemp->get<dai::ImgFrame>();
        // On OAK-T the thermal stream is rather tiny, so we scale it up for better visualization.
        if(!initialWindowResizeDone) {
            cv::resizeWindow(TEMP_WINDOW, tempImgFrame->getWidth() * WINDOW_SCALE_MULTIPLIER, tempImgFrame->getHeight() * WINDOW_SCALE_MULTIPLIER);
            initialWindowResizeDone = true;
        }
        // Convert the FP16 image frame to CV_32F.
        cv::Mat frameFp32(tempImgFrame->getHeight(), tempImgFrame->getWidth(), CV_32F);
        tempImgFrame->getCvFrame().convertTo(frameFp32, CV_32F);
        // Colorize the temperature frame.
        cv::Mat normalized;
        cv::normalize(frameFp32, normalized, 0, 255, cv::NORM_MINMAX, CV_8UC1);
        cv::Mat colormapped(tempImgFrame->getHeight(), tempImgFrame->getWidth(), CV_8UC3);
        cv::applyColorMap(normalized, colormapped, cv::COLORMAP_MAGMA);
        if(mouseX < 0 || mouseY < 0 || mouseX >= colormapped.cols || mouseY >= colormapped.rows) {
            mouseX = std::max(0, std::min(static_cast<int>(mouseX), colormapped.cols - 1));
            mouseY = std::max(0, std::min(static_cast<int>(mouseY), colormapped.rows - 1));
        }
        double min, max;
        cv::minMaxLoc(frameFp32, &min, &max);
        const cv::Scalar textColor(255, 255, 255);
        ;
        // Draw crosshair
        cv::line(colormapped, cv::Point(mouseX - 10, mouseY), cv::Point(mouseX + 10, mouseY), textColor, 1);
        cv::line(colormapped, cv::Point(mouseX, mouseY - 10), cv::Point(mouseX, mouseY + 10), textColor, 1);
        // Draw deg C
        char text[32];
        snprintf(text, sizeof(text), "%.1f deg C", frameFp32.at<float>(mouseY, mouseX));
        bool putTextLeft = mouseX > colormapped.cols / 2;
        cv::putText(colormapped, text, cv::Point(putTextLeft ? mouseX - 100 : mouseX + 10, mouseY - 10), cv::FONT_HERSHEY_SIMPLEX, 0.5, textColor, 1);
        cv::imshow(TEMP_WINDOW, colormapped);
        // Can add controls for the sensor in the future here.
        int key = cv::waitKey(1);
        bool confUpdated = false;
        if(key == 'q') {
            break;
        } else if(key == 'a') {
            if(!thermalConfig->ffcParams.autoFFC) {
                thermalConfig->ffcParams.autoFFC = false;
            }
            thermalConfig->ffcParams.autoFFC = !*thermalConfig->ffcParams.autoFFC;
            confUpdated = true;
        } else if(key == 'n') {
            if(!thermalConfig->imageParams.brightnessLevel) {
                thermalConfig->imageParams.brightnessLevel = 1;
            }
            if(*thermalConfig->imageParams.brightnessLevel >= 1) {
                *thermalConfig->imageParams.brightnessLevel -= 1;
            } else {
                thermalConfig->imageParams.brightnessLevel = 0;
            }
            confUpdated = true;
        } else if(key == 'm') {
            if(!thermalConfig->imageParams.brightnessLevel) {
                thermalConfig->imageParams.brightnessLevel = 0;
            }
            if(thermalConfig->imageParams.brightnessLevel >= 3) {
                thermalConfig->imageParams.brightnessLevel = 3;
            }
            *thermalConfig->imageParams.brightnessLevel += 1;
            confUpdated = true;
        } else if(key == 'r') {
            if(!thermalConfig->imageParams.orientation) {
                thermalConfig->imageParams.orientation = dai::ThermalConfig::ThermalImageOrientation::Normal;
            }
            auto orientation = static_cast<uint8_t>(*thermalConfig->imageParams.orientation) + 1;
            if(orientation > 3) {
                orientation = 0;
            }
            thermalConfig->imageParams.orientation = static_cast<dai::ThermalConfig::ThermalImageOrientation>(orientation);
            confUpdated = true;
        } else if(key == 't') {
            if(!thermalConfig->imageParams.timeNoiseFilterLevel) {
                thermalConfig->imageParams.timeNoiseFilterLevel = 1;
            }
            if(*thermalConfig->imageParams.timeNoiseFilterLevel == 0) {
                thermalConfig->imageParams.timeNoiseFilterLevel = 0;
            } else {
                *thermalConfig->imageParams.timeNoiseFilterLevel -= 1;
            }
            confUpdated = true;
        } else if(key == 'z') {
            if(!thermalConfig->imageParams.timeNoiseFilterLevel) {
                thermalConfig->imageParams.timeNoiseFilterLevel = 0;
            }
            if(*thermalConfig->imageParams.timeNoiseFilterLevel == 3) {
                thermalConfig->imageParams.timeNoiseFilterLevel = 3;
            } else {
                *thermalConfig->imageParams.timeNoiseFilterLevel += 1;
            }
            confUpdated = true;
        } else if(key == 's') {
            if(!thermalConfig->ffcParams.closeManualShutter.has_value()) {
                thermalConfig->ffcParams.closeManualShutter = false;
            }
            *thermalConfig->ffcParams.closeManualShutter = !*thermalConfig->ffcParams.closeManualShutter;
            std::cout << (*thermalConfig->ffcParams.closeManualShutter ? "Closing" : "Opening") << " manual shutter." << std::endl;
            confUpdated = true;
        }
        if(confUpdated) {
            inConfig->send(thermalConfig);
        }
    }

    return 0;
}
```

### Need assistance?

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