Transpose image planes

Hi,

I’m currently working on a project, where I have to transpose my image with n planes and I’m looking for the fastest way possbile to do that. My image has the dimensions: [width, height, n_planes]. Is there a CVB specific way to get my image in the following order: [width, n_planes, height]

Cheers

transpose_yz

Hi! To what extent do you know/have control of the memory block in which the data is stored? If e.g. it is just a block of x * y * planes * sizeof(pixel) bytes then the easiest way would be to rewrap it into a new image where just the view is transposed but the memory block remains untouched (by means of CreateImageFromPointer). That’s by far the fastest solution and you can even use this approach to wrap an existing image similar to what CreateImageMap does…

Thanks for the advice. Currently my data is stored in an IMG object with the dimensions [width, height, numPlanes]. I just tried to transpose my image using the function CreateImageFromPointer but it seems that I somewhere went wrong.
My approach was:

  • get the pointer to the memory block from the VPAT
  • create an image from that pointer and swaping the PitchY (= width*pixelSize) and PitchZ (= width*height*pixelSize)
  • save the image in the mio-format. This is where an access violation occurs.

I’m I right in assuming that I can get access to the whole memory block (including all planes) of an IMG handle using the base pointer of the VPA?
Or could you point out the place where I went wrong?

IMG transpose_YZ(IMG srcImg)
{
  intptr_t srcPtr = NULL;
  PVPAT vpat = NULL;

  cvbres_t res0 = GetImageVPA(
    srcImg,
    0,
    reinterpret_cast<void**>(&srcPtr),
    &vpat);
  cvbval_t szPixel = BytesPerPixel(ImageDatatype(srcImg, 0));

  IMG tgtImg = NULL;

  cvbres_t res1 = CreateImageFromPointer(
    (void*)srcPtr,
    ImageWidth(srcImg) * ImageHeight(srcImg) * ImageDimension(srcImg) * szPixel,
    ImageWidth(srcImg),
    ImageDimension(srcImg),
    ImageHeight(srcImg),
    ImageDatatype(srcImg, 0),
    szPixel, // Pitch X
    szPixel * ImageWidth(srcImg) * ImageHeight(srcImg), // Pitch Y
    szPixel * ImageWidth(srcImg), // Pitch Z
    NULL,
    &nop_release,
    NULL,
    tgtImg);
  return tgtImg;
}

I think there are too many implicit assumptions inside the code that you should verify before making use of them. For example you are just using GetImageVPA on the first plane - you don’t verify whether the increments in x and y are linear and the same for all planes, you don’t verify whether there are padding bytes somehwere, you don’t verify whether the increment from plane to plane is identical for all planes - and if just one of these assumptions is not met by the block of memory you are dealing with, then an access violation is the inevitable consequence (that’s in fact one of the downsides of CreateImageFromPointer - you can easily end up with an access violation…

Please consider this little project (sorry for the unimaginative name :wink:):ConsoleApplication25.zip (5.8 KB)

To make sure we have something that we can work with, the main() routine generates an image with defined (and benign) memory layout:

void __stdcall nop_release(void*, void*) {}

int main()
{
  static const int w = 128;
  static const int h = 192;
  static const int dim = 256;
  // generate a test image where each pixel of a given plane has been set to 
  // the plane number; if we swap y <-> z we should see a grey wedge
  std::vector<uint8_t> buffer(w * h * dim);
  for (int x = 0; x < w; ++x)
    for (int y = 0; y < h; ++y)
      for (int z = 0; z < dim; ++z)
      {
        int cell = x + w * y + w * h * z;
        buffer[cell] = z;
      }
  IMG imgBuffer = nullptr;
  CreateImageFromPointer(buffer.data(), buffer.size(), w, h, dim, 8, 1, w, w * h, nullptr, nop_release, nullptr, imgBuffer);
  // for debugging: WriteImageFile(imgBuffer, "D:\\test.mio");

  try
  {
    auto imgTransposed = TransposeImage(imgBuffer);
    // for debugging: WriteImageFile(imgTransposed, "D:\\transposed.mio");
    ReleaseObject(imgTransposed);
  }
  catch (std::exception& e)
  {
    std::cout << e.what() << std::endl;
  }
  ReleaseObject(imgBuffer);
  return 0;
}

With the TransposeImage() function invoked in the try/catch block implemented like this:

IMG TransposeImage(IMG imgOriginal)
{
  // assumptions:
  // imgOriginal is an IMG handle to the x-y-planes arranged input image
  // - for simplicity it is assumed that there are no padding bytes in any 
  // direction and that the pixel at coordinate (0,0,0) lies at the lowest 
  // address...
  auto w = ImageWidth(imgOriginal);
  auto h = ImageHeight(imgOriginal);
  auto dim = ImageDimension(imgOriginal);

  // determine the increments and check whether linearity expectations
  // are met
  intptr_t xInc = 0;
  intptr_t yInc = 0;
  intptr_t zInc = 0;
  intptr_t pBasePrev = 0;
  intptr_t pBase = 0;
  intptr_t pBaseOut = 0;
  if ((dim > 1) && (!GetLinearAccess(imgOriginal, 1, reinterpret_cast<void**>(&pBase), &xInc, &yInc)))
    throw std::exception(ErrNotAllPlanesLinear);
  if (!GetLinearAccess(imgOriginal, 0, reinterpret_cast<void**>(&pBasePrev), &xInc, &yInc))
    throw std::exception(ErrNotAllPlanesLinear);
  if (dim > 1)
    zInc = pBase - pBasePrev;
  pBaseOut = pBasePrev;
  for (int i = 1; i < dim; ++i)
  {
    intptr_t xIncTmp = 0;
    intptr_t yIncTmp = 0;
    if (!GetLinearAccess(imgOriginal, i, reinterpret_cast<void**>(&pBase), &xIncTmp, &yIncTmp))
      throw std::exception(ErrNotAllPlanesLinear);
    if ((xIncTmp != xInc) || (yIncTmp != yInc))
      throw std::exception(ErrVariableIncrements);
    auto zIncTmp = pBase - pBasePrev;
    if (zIncTmp != zInc)
      throw std::exception(ErrVariableIncrements);
    std::swap(pBase, pBasePrev);
  }

  // now build a new image that shares the memory of the input image but
  // has a transposed VPAT view on that image
  IMG retval = nullptr;
  if (SUCCEEDED(CreateImageFromPointer(reinterpret_cast<void*>(pBaseOut), w * h * dim, w, dim, h, 8, xInc, zInc, yInc, nullptr, TransposedRelease, imgOriginal, retval)))
    ShareObject(imgOriginal);
  return retval;
}

The result looks as expected and no access violation occurs. Please mind that there are still two assumptions made and not verified by that code (see initial comment in TransposeImage():

  • Pixel (0,0) on plane 0 lies at the lowest address.
  • no padding bytes have to be taken into account.

It should be fairly easy to extend this example to pixels with more than 8 bits per pixel (simply add * sizeof(<pixeltype>) where appropriate). Adapting to padding bytes should also be straightforward (in fact, now that I think about it, the code might already work in the presence of padding bytes).

:warning: The following advice should be dealt with care; it requires a firm grasp of pointer arithmetics and a superhuman amount of patience to implement and debug! You have been warned…:warning:

Adaption to a situation where the lowest address is at some more or less random coordinate or planes are not contiguous is a lot more difficult. If you have to deal with such a slightly weird layout then the first thing to investigate is whether or not the memory layout is at all compatible with the VPAT model used by :cvb:. For that to be the case the x offset must be independent of the y coordinate in the transposed image and the y offset must be independent of the x coordinate in the transposed image. If that requirement is met, you can create the result image with CreateImageFromPointer (and latch its lifetime to that of the input image as shown in my sample project) and then manually overwrite the VPATs of all planes as needed (simply retrieve the VPAT pointers via GetImageVPA and write to the tables - the written data ends up inside the image’s VPAT and the changes will persist).
But be warned: Implementing and debugging something like that can be extremely painful and frustrating. In fact, if your memory layout is not compatible with the assumptions made in the sample code I sent you, you might be better off rearranging the data in memory…

3 Likes

Very fast and nice solution. But there is a double free hidden: the CreateImageFromPointer inside main needs a release callback as otherwise free is called on the vector's memory:

void __stdcall nop_release(void *, void *) {} 
2 Likes

right you are… good catch! Post is updated…

Thanks alot, this should do the trick :ok_hand:

Hi again,
I tried that solution but unfortunately I ran into another obstacle:
The assumption of having a constant increment zInc from plane to plane was not met. zInc was changing all the time. In contrast the increment in x and y directions were constant over all planes.
The source data I’m using is an IMG which I created using CreateGenericImageDT.

So is there an alternative way of creating an image with a constant zInc over all planes?

With the functions CreateGenericImage and CreateGenericImageDT you do not have any control over the memory and VPAT layout of the images that are generated. When creating multi-planar images, the memory is allocated per plane, making the start of each plane’s memory block unpredictable (the only exception are 3-planar images which are always created with an RGB-interleaved memory layout).

If you need control over the memory layout - and it sounds like you do - then there is no way around CreateImageFromPointer. Look at the example I posted earlier - this should be easily adaptable to different dimensions and/or layouts. In that situation I recommend to use malloc/free or new/delete rather than a vector for simplicity (in my sample main() the ownership of the std::vector was not tied to the result of CreateImageFromPointer to keep the sample code simple).