vpiImageCreateView() crop failed

Hi,

I work on my Jetson with VPI version 2.2.4. I try to CROP ROI from an image that exists at the device (CUDA backend) and create a new image that is a sub-image of the original one.

Here is my method:

/**
 * @brief Copy a device image ROI into a device-owned cache (optionally format-convert).
 *
 * @param src     Device CUDA backend VPIImage (input)
 * @param roi     ROI in pixels; clamped to src bounds (local copy, caller’s ROI not mutated).
 * @param sync    If true, waits and destroys the temporary view before return.
 * @return VPIImage handle to class-owned roiCache_ with the ROI contents.
 */
VPIImage VPI_Preprocess::roiFromVPI(VPIImage src,
                                    cv::Rect& roi,
                                    bool sync)
{
    if (!src) throw std::runtime_error("roiFromVPI: src is null");
    int W=0, H=0;
    VPI_THROW_IF(vpiImageGetSize(src, &W, &H));

    // Ensure ROI in valid Range
    roi.x = roi.x > 0 ? roi.x : 0;
    roi.y = roi.y > 0 ? roi.y : 0;
    roi.width = std::min(roi.width, W - roi.x);
    roi.height  = std::min(roi.height , H - roi.y);

    // Determine formats
    VPIImageFormat srcFmt = VPI_IMAGE_FORMAT_INVALID;
    VPI_THROW_IF(vpiImageGetFormat(src, &srcFmt));

    // Create a zero-copy view over ROI
    VPIRectangleI r{roi.x, roi.y, roi.width, roi.height};
    VPIImage view = nullptr;

    VPI_THROW_IF(vpiImageCreateView(src, &r, 0, &view));

    // Ensure destination device buffer
    ensureImage(roi.width, roi.height, srcFmt, roiCache_);

    // Device-side blit/convert view → roiCache_
    VPI_THROW_IF(vpiSubmitConvertImageFormat(stream_, backendConvert_, view, roiCache_, nullptr));

    if (sync) {
        VPI_THROW_IF(vpiStreamSync(stream_));
        vpiImageDestroy(view);  // destroy temporary header
    } else {
        // If async, you must keep 'view' alive until you later sync; otherwise it will dangle.
        // Either store it and provide a flush() that syncs and destroys, or just use sync=true.
        // pendingViews_.push_back(view);  // if you add bookkeeping
        // TODO: keep 'view' alive until a later sync, then destroy it.
        pendingWraps_.push_back(view);
    }

    return roiCache_;  // class-owned, reused on next call
}

The method crash on the line:

VPI_THROW_IF(vpiImageCreateView(srcWrap, &r, 0, &srcView));

And the error that I get is:

VPI error VPI_ERROR_INVALID_OPERATION @/home/uvison/VPI_examples/VPI_Preprocess.cpp:159

When I run the same concept where the matrix came from host it’s work fine for example:

/**
 * @brief Crop a region from a host cv::Mat and materialize it as a device-owned VPIImage.
 *
 * This method wraps the input OpenCV image in a VPI header (zero-copy), creates a VPI
 * view on the requested ROI, and then enqueues a device-side copy/convert of that ROI
 * into an internal cached device image (roiCache_). 
 * @param bgr     Input frame in host memory (OpenCV). Expected 8-bit depth
 *                (e.g., CV_8UC1/3/4). The input data is never modified.
 * @param roi     Region of interest in pixel coordinates. The rectangle is clamped
 *                to the input image bounds before processing.
 * @param sync    When true, waits for the GPU work to complete and destroys the
 *                temporary wrappers before returning. When false, the copy/convert
 *                is enqueued on @c stream_; the caller must synchronize later and
 *                ensure the wrapped input (and its VPI view) remain alive until done.
 *
 * @return VPIImage handle to the class-owned cached device image containing the ROI.
 *         Ownership remains with VPI_Preprocess; subsequent calls may reuse/overwrite
 *         the same buffer, and it will be reallocated if size/format changes.
 *
*/
VPIImage VPI_Preprocess::roiFromMat(const cv::Mat& bgr,
                                    cv::Rect& roi,
                                    bool sync)
{
    if (bgr.empty())
        throw std::runtime_error("roiFromMat: src is empty");
    
    // Ensure ROI in valid Range
    roi.x = roi.x > 0 ? roi.x : 0;
    roi.y = roi.y > 0 ? roi.y : 0;
    roi.width = std::min(roi.width, bgr.cols - roi.x);
    roi.height  = std::min(roi.height , bgr.rows - roi.y);

    // Wrap the OpenCV Mat (no copy)
    VPIImage srcWrap = nullptr;
    VPI_THROW_IF(vpiImageCreateWrapperOpenCVMat(bgr, 0, &srcWrap));

    // Discover the VPI format of the wrapped image
    VPIImageFormat srcFmt = VPI_IMAGE_FORMAT_INVALID;
    VPI_THROW_IF(vpiImageGetFormat(srcWrap, &srcFmt));

    // ROI view crop ROI get pointer view
    VPIImage srcView = nullptr;
    VPIRectangleI r{roi.x, roi.y, roi.width, roi.height};
    VPI_THROW_IF(vpiImageCreateView(srcWrap, &r, 0, &srcView));

    // Ensure destination (same format as source → device "clone" of ROI)
    ensureImage(roi.width, roi.height, srcFmt, roiCache_);
    
    // Device-side copy (formats equal) or convert (if VPI decides to normalize layout)
    VPI_THROW_IF(vpiSubmitConvertImageFormat(stream_, backendConvert_, srcView, roiCache_, nullptr));

    if (sync) {
        VPI_THROW_IF(vpiStreamSync(stream_));
        vpiImageDestroy(srcView);
        vpiImageDestroy(srcWrap);
    } else {
        // If async, keep 'img', srcWrap and srcView alive until work finishes.
        // (You can add an event system to defer destruction.)
        pendingWraps_.push_back(srcView);
        pendingWraps_.push_back(srcWrap);
        
    }

    return roiCache_;
}

I don’t succeed to understand what I am doing wrong. I will be glad to receive any help.

Hi,

You can find an example below:

/opt/nvidia/vpi2/samples/15-image_view

Please check it first to see if everything is aligned.

Thanks.

Hi, I read the sample and I succeeded in fixing the problem when I changed the code from

(vpiImageCreate(w, h, fmt, 0, &slot)

to

(vpiImageCreate(w, h, fmt, VPI_BACKEND_CUDA | VPI_BACKEND_CPU, &slot)

But I did not really succeed in understanding why the code crashes in flag = 0 as recommended in other tutorials. In general, from the documentation it’s mentioned that Backends are only chosen when you submit an algorithm, so why in this case I need to specify the backend when I create image?

I would be glad to get clarification because I’m not sure if I really succeeded in solving the problem or if I missed something else.

Hi,

When you submit an algorithm, you can choose which backend (hardware) you want to use for running the algorithm.

But when you create an image, the handler will prepare the memory for the specified backend.
Using flag=0 indicates the data can be accessed for all the backends.

We didn’t find the slot variable in the source you shared above.
Do you create an ROI from that image?

Thanks.

Hi,

Attached the method that create image this method call ensure image which check if the relevant image exists in the memory if not the method create one still not clear why when I use createView() the vpiImageCreate() work only with specifying CUDA and CPU backends? Why I can’t createImage with flag 0 in that case?

In addition the createView() doesn’t work when I give only CUDA or CPU flag. only when I specify VPI_BACKEND_CUDA | VPI_BACKEND_CPU the createView() with ROI sucssed.

if flag = 0 in vpiImageCreate() that mean I create memory in all backends together?

/**
 * @brief Sure the destination buffer (slot) exists and matches the required 
 * size/format (U8 = grayscale). If a cached buffer of the right shape already exists, 
 * reuse it; if not, create it (and destroy any mismatched previous one).
 * @param (w, h) img size
 * @param fmt format except VPI_IMAGE_FORMAT_U8
 * @param slot VPIImage most of the time is gray_cach_ 
*/
void VPI_Preprocess::ensureImage(int w, int h, VPIImageFormat fmt, VPIImage &slot) {
    if (slot) {
        int cw = 0, ch = 0;
        VPIImageFormat cfmt = VPI_IMAGE_FORMAT_INVALID;
        VPI_THROW_IF(vpiImageGetSize(slot, &cw, &ch));
        VPI_THROW_IF(vpiImageGetFormat(slot, &cfmt));
        if (cw == w && ch == h && cfmt == fmt)
        return;
        vpiImageDestroy(slot);
        slot = nullptr;
    }
    VPI_THROW_IF(vpiImageCreate(w, h, fmt, VPI_BACKEND_CUDA | VPI_BACKEND_CPU /*0 ?*/, &slot));
}

Hi,

Would you mind sharing the complete source code so we can know more about the issue?
Thanks.

VPI_preprocess.zip (16.1 KB)

Hi,

Thanks for your patience.
We test your source, and it can run normally on the Xavier with r35.6.1.

$ ./VPI_EXAMPLES --image_path Images/lena.jpeg 
Hello World.. 

Attached output edge.png for your reference:

Thanks.

again I know that my code work after I change the following line:

void VPI_Preprocess::ensureImage(int w, int h, VPIImageFormat fmt, VPIImage &slot) {
    if (slot) {
        int cw = 0, ch = 0;
        VPIImageFormat cfmt = VPI_IMAGE_FORMAT_INVALID;
        VPI_THROW_IF(vpiImageGetSize(slot, &cw, &ch));
        VPI_THROW_IF(vpiImageGetFormat(slot, &cfmt));
        if (cw == w && ch == h && cfmt == fmt)
        return;
        vpiImageDestroy(slot);
        slot = nullptr;
    }
    VPI_THROW_IF(vpiImageCreate(w, h, fmt, VPI_BACKEND_CUDA | VPI_BACKEND_CPU /*0 ?*/, &slot));
}

My question is when I use

vpiImageCreate()

Why do I need to specify both VPI_BACKEND_CUDA | VPI_BACKEND_CPU instead of just 0?

In addition, when I use vpiImageCreate behind the scenes, VPI allocates memory for all backends that specify this flag.

Hi,

You can see more error info when updating the VPI_THROW_IF function as below:

#define VPI_THROW_IF(STMT)                                    \
    do                                                        \
    {                                                         \
        VPIStatus status = (STMT);                            \
        if (status != VPI_SUCCESS)                            \
        {                                                     \
            char buffer[VPI_MAX_STATUS_MESSAGE_LENGTH];       \
            vpiGetLastStatusMessage(buffer, sizeof(buffer));  \
            std::ostringstream ss;                            \
            ss << vpiStatusGetName(status) << ": " << buffer; \
            throw std::runtime_error(ss.str());               \
        }                                                     \
    } while (0);
$ ./VPI_EXAMPLES --image_path Images/lena.jpeg 
terminate called after throwing an instance of 'std::runtime_error'
  what():  VPI_ERROR_INVALID_OPERATION: It is not possible to create an image view for the given parent image
Aborted (core dumped)

Thanks.