# ToF

This RVC2 example shows a minimal ToF pipeline and visualizes depth with a colormap.
Unlike classic color + mono stereo-pair examples, ToF depth is produced through dai.node.ToF.
For architecture and settings, see the [ToF node
docs](https://docs.luxonis.com/software-v3/depthai/depthai-components/nodes/tof.md).
For a full runtime-tuning and pointcloud workflow, see [ToF pointcloud + runtime filter controls
(oak-examples)](https://github.com/luxonis/oak-examples/tree/bbc446232ad9378e03e9cb89b04dbb9f99f2448f/depth-measurement/3d-measurement/tof-pointcloud).

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

## Pipeline

### examples/tof.pipeline.json

```json
{"pipeline": {"connections": [{"node1Id": 0, "node1Output": "depth", "node1OutputGroup": "", "node2Id": 1, "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": [[1, {"alias": "", "id": 1, "ioInfo": [[["", "in"], {"blocking": true, "group": "", "id": 5, "name": "in", "queueSize": 3, "type": 3, "waitForMessage": false}]], "logLevel": 3, "name": "XLinkOut", "parentId": -1, "properties": {"maxFpsLimit": -1.0, "metadataOnly": false, "streamName": "__x_0_depth"}}], [0, {"alias": "", "id": 0, "ioInfo": [[["", "intensity"], {"blocking": false, "group": "", "id": 3, "name": "intensity", "queueSize": 8, "type": 0, "waitForMessage": false}], [["", "amplitude"], {"blocking": false, "group": "", "id": 2, "name": "amplitude", "queueSize": 8, "type": 0, "waitForMessage": false}], [["", "phase"], {"blocking": false, "group": "", "id": 4, "name": "phase", "queueSize": 8, "type": 0, "waitForMessage": false}], [["", "depth"], {"blocking": false, "group": "", "id": 1, "name": "depth", "queueSize": 8, "type": 0, "waitForMessage": false}], [["", "inputConfig"], {"blocking": true, "group": "", "id": 0, "name": "inputConfig", "queueSize": 3, "type": 3, "waitForMessage": false}]], "logLevel": 3, "name": "ToF", "parentId": -1, "properties": {"boardSocket": 0, "cameraName": "", "fps": 30.0, "imageOrientation": -1, "initialConfig": {"enableBurstMode": false, "enableDistortionCorrection": true, "enableFPPNCorrection": null, "enableOpticalCorrection": null, "enablePhaseShuffleTemporalFilter": true, "enablePhaseUnwrapping": null, "enableTemperatureCorrection": null, "enableWiggleCorrection": null, "median": 0, "phaseUnwrapErrorThreshold": 100, "phaseUnwrappingLevel": 4}, "numFramesPool": 4, "numFramesPoolRaw": 3, "numShaves": 1, "warpHwIds": []}}]]}}
```

## Source code

#### Python

```python
#!/usr/bin/env python3

import cv2
import depthai as dai
import numpy as np

def colorizeDepth(frameDepth: np.ndarray) -> np.ndarray:
    invalidMask = frameDepth == 0  # zero depth is invalid

    # Log the depth, minDepth and maxDepth
    try:
        minDepth = np.percentile(frameDepth[frameDepth != 0], 3)
        maxDepth = np.percentile(frameDepth[frameDepth != 0], 95)
        logDepth = np.log(frameDepth, where=frameDepth != 0)
        logMinDepth = np.log(minDepth)
        logMaxDepth = np.log(maxDepth)
        np.nan_to_num(logDepth, copy=False, nan=logMinDepth)
        # Clip the values to be in the 0-255 range
        logDepth = np.clip(logDepth, logMinDepth, logMaxDepth)

        # Interpolate only valid logDepth values, setting the rest based on the mask
        depthFrameColor = np.interp(logDepth, (logMinDepth, logMaxDepth), (0, 255))
        depthFrameColor = np.nan_to_num(depthFrameColor)
        depthFrameColor = depthFrameColor.astype(np.uint8)
        depthFrameColor = cv2.applyColorMap(depthFrameColor, cv2.COLORMAP_JET)
        # Set invalid depth pixels to black
        depthFrameColor[invalidMask] = 0
    except IndexError:
        # Frame is likely empty
        depthFrameColor = np.zeros(
            (frameDepth.shape[0], frameDepth.shape[1], 3), dtype=np.uint8
        )
    except Exception as e:
        raise e
    return depthFrameColor

def main():
    pipeline = dai.Pipeline()

    # ToF node
    socket, preset_mode = dai.CameraBoardSocket.AUTO, dai.ImageFiltersPresetMode.TOF_MID_RANGE
    tof = pipeline.create(dai.node.ToF).build(socket, preset_mode)

    # Output queues
    depthQueue = tof.depth.createOutputQueue()
    depthRawQueue = tof.rawDepth.createOutputQueue()

    with pipeline as p:
        p.start()
        while p.isRunning():
            ## Visualize raw depth (unfiltered depth directly from the ToF sensor)
            depthRaw: dai.ImgFrame = depthRawQueue.get()
            depthRawImage = colorizeDepth(depthRaw.getFrame())
            cv2.imshow("depthRaw", depthRawImage)

            ## Visualize depth (which is filtered depthRaw)
            depth: dai.ImgFrame = depthQueue.get()
            depthImage = colorizeDepth(depth.getFrame())
            cv2.imshow("depth", depthImage)

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

if __name__ == "__main__":
    main()
```

#### C++

```cpp
#include <iostream>
#include <opencv2/opencv.hpp>
#include <xtensor/containers/xadapt.hpp>
#include <xtensor/containers/xarray.hpp>

#include "depthai/depthai.hpp"

cv::Mat colorizeDepth(const cv::Mat& frameDepth) {
    // -----------------------------------------------------------------------
    // 1.  Basic checks & convert to CV_32F
    // -----------------------------------------------------------------------
    if(frameDepth.empty() || frameDepth.channels() != 1) return cv::Mat::zeros(frameDepth.size(), CV_8UC3);

    cv::Mat depth32f;
    frameDepth.convertTo(depth32f, CV_32F);  // safe for any input type

    // -----------------------------------------------------------------------
    // 2.  Build mask of valid (non-zero) pixels
    // -----------------------------------------------------------------------
    const cv::Mat nonZeroMask = depth32f != 0.0f;
    const int nz = cv::countNonZero(nonZeroMask);
    if(nz == 0) return cv::Mat::zeros(frameDepth.size(), CV_8UC3);

    // -----------------------------------------------------------------------
    // 3.  3 % / 95 % percentiles (identical to Python version)
    // -----------------------------------------------------------------------
    std::vector<float> values;
    values.reserve(nz);
    for(int r = 0; r < depth32f.rows; ++r) {
        const float* d = depth32f.ptr<float>(r);
        const uchar* m = nonZeroMask.ptr<uchar>(r);
        for(int c = 0; c < depth32f.cols; ++c)
            if(m[c]) values.push_back(d[c]);
    }

    std::sort(values.begin(), values.end());
    auto pct = [&](double p) {
        size_t idx = static_cast<size_t>(std::round((p / 100.0) * (values.size() - 1)));
        return values[idx];
    };

    const float minDepth = pct(3.0);
    const float maxDepth = pct(95.0);

    // -----------------------------------------------------------------------
    // 4.  Logarithm (zeros replaced by minDepth to avoid -inf)
    // -----------------------------------------------------------------------
    cv::Mat logDepth;
    depth32f.copyTo(logDepth);
    logDepth.setTo(minDepth, ~nonZeroMask);  // overwrite zeros
    cv::log(logDepth, logDepth);

    const float logMinDepth = std::log(minDepth);
    const float logMaxDepth = std::log(maxDepth);

    // -----------------------------------------------------------------------
    // 5.  Clip & linearly scale to [0,255]  (same as np.interp)
    // -----------------------------------------------------------------------
    cv::min(logDepth, logMaxDepth, logDepth);
    cv::max(logDepth, logMinDepth, logDepth);
    logDepth = (logDepth - logMinDepth) * (255.0f / (logMaxDepth - logMinDepth));

    cv::Mat depth8U;
    logDepth.convertTo(depth8U, CV_8U);

    // -----------------------------------------------------------------------
    // 6.  Colour map + set invalid pixels to black
    // -----------------------------------------------------------------------
    cv::Mat depthFrameColor;
    cv::applyColorMap(depth8U, depthFrameColor, cv::COLORMAP_JET);
    depthFrameColor.setTo(cv::Scalar::all(0), ~nonZeroMask);

    return depthFrameColor;
}

int main() {
    dai::Pipeline pipeline;

    // ToF node
    dai::CameraBoardSocket socket = dai::CameraBoardSocket::AUTO;
    dai::ImageFiltersPresetMode presetMode = dai::ImageFiltersPresetMode::TOF_MID_RANGE;
    std::shared_ptr<dai::node::ToF> tof = pipeline.create<dai::node::ToF>()->build(socket, presetMode);

    // Output queues
    std::shared_ptr<dai::MessageQueue> depthQueue = tof->depth.createOutputQueue();
    std::shared_ptr<dai::MessageQueue> depthRawQueue = tof->rawDepth.createOutputQueue();

    pipeline.start();
    while(pipeline.isRunning()) {
        // Visualize raw depth (unfiltered depth directly from the ToF sensor)
        std::shared_ptr<dai::ImgFrame> depthRaw = depthRawQueue->get<dai::ImgFrame>();
        cv::Mat depthRawImage = colorizeDepth(depthRaw->getCvFrame());
        cv::imshow("depthRaw", depthRawImage);

        // Visualize depth (which is filtered depthRaw)
        std::shared_ptr<dai::ImgFrame> depth = depthQueue->get<dai::ImgFrame>();
        cv::Mat depthImage = colorizeDepth(depth->getCvFrame());
        cv::imshow("depth", depthImage);

        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.
