Multiframe Ultrasound DICOM file creation - c++

I am new at DICOM and DCMTK. I have a set of BITMAP Ultrasound data that I would like to save as a lossless DICOM file.
After loadding the data in a queue frame_queue and setting all the mandatory DICOM necessary parameters for Ultrasound Multiframe images I realise a loop to add the images in the DcmDataset as follows:
std::unique_ptr<DcmDataset> dataset;
/***
DICOM parameters A setted not added here as many parameters are set
***/
std::shared_ptr<unsigned char[]> frame;
std::shared_ptr<RGBQUAD[]> colorTable;
DJEncoderRegistration::registerCodecs();
DJ_RPLossless params; // codec parameters, we use the defaults
// this causes the lossless JPEG version of the m_dataset to be created
/**/
E_TransferSyntax xfer = DcmXfer(EXS_JPEGProcess14).getXfer();
DcmPixelSequence* sequence = new DcmPixelSequence(DcmTag(DCM_PixelData, EVR_OB));
DcmPixelData* newPixelData = new DcmPixelData(DCM_PixelData);
dataset->insert(sequence, OFTrue);
OFstatic_cast(DcmPixelData*, newPixelData)->putOriginalRepresentation(xfer, NULL, sequence);
while (frame_queue.popFrame(frame, colorTable)) // we extract a new frame from the queue
{
DcmPixelItem* newItem = new DcmPixelItem(DcmTag(DCM_Item, EVR_OB));
if (newItem != NULL)
{
sequence->insert(newItem);
/* put pixel data into the item */
std::shared_ptr<Uint8[]> newDCMFrame;
colorTableToRGB(frame, colorTable, newDCMFrame, m_frameWidth, m_frameHeight);
result = newItem->putUint8Array((Uint8*)newDCMFrame.get(), m_frameHeight * m_frameWidth * 3).good(); // this returns true in my test
frame.reset();
colorTable.reset();
}
}
dataset->chooseRepresentation(EXS_JPEGProcess14, &params);
dataset->saveFile(path.c_str());
The call of dataset->chooseRepresentation(EXS_JPEGProcess14, &params); raises the error:
DcmDataset: Wrong class for pixel data element, cannot change representation
A DICOM File is saved but it is empty.
Does anyone know what's causing this error?

Just a guess but you are creating the dataset in uncompressed representation. In this case, the pixel data must NOT be represented as a sequence of frames. You should try calling putUint8Array() directly on the DcmPixelData element instead of inserting sequences to it.
DCMTK will change that way of encoding to the one you want to achieve directly, when you call DcmDataset::chooseRepresentation().
For uncompressed pixel data, it is not allowed to used the "encapsulated" pixel data encoding with sequences, and I suspect that this is why DcmDataset::chooseRepresentation() complains about "not finding what was expected".

So I finaly got it to work:
std::unique_ptr<DcmDataset> dataset;
/***
DICOM parameters A setted not added here as many parameters are set
***/
std::shared_ptr<unsigned char[]> frame;
std::shared_ptr<RGBQUAD[]> colorTable;
DJEncoderRegistration::registerCodecs();
DJ_RPLossless params; // codec parameters, we use the defaults
// this causes the lossless JPEG version of the m_dataset to be created
/**/
E_TransferSyntax xfer = DcmXfer(EXS_JPEGProcess14).getXfer();
DcmPixelData* newPixelData = new DcmPixelData(DCM_PixelData);
dataset->insert(newPixelData , OFTrue);
bool result = true;
static const int imageSize = m_frameWidth * m_frameHeight * 3;
std::shared_ptr<Uint8[]> imagesBufferPtr(new Uint8[imageSize * m_nbFrames], array_deleter<Uint8>());
std::vector<std::vector<Uint8>> dataImages(m_nbFrames, std::vector<Uint8>(imageSize));
int frameId = 0;
while (this->popFrame(frame, colorTable) == cwlibdicom::frameBufferStatus::BUFFER_SUCCESS && result)
{
Uint8 *img = imagesBufferPtr.get();
img = &img[frameId];
colorTableToRGB(frame, colorTable, img, m_frameWidth, m_frameHeight);
std::memcpy(&imagesBufferPtr.get()[frameId * imageSize], img.data(), imageSize);
frameId++;
frame.reset();
colorTable.reset();
}
result = newPixelData->putUint8Array(imagesBufferPtr.get(), imageSize * m_nbFrames).good();
header->setProperty(DCM_NumberOfFrames, nb_frame);
header->setProperty(DCM_SamplesPerPixel, "3");
header->setProperty(DCM_PhotometricInterpretation, "RGB");
header->setProperty(DCM_Rows, m_imageHeight);
header->setProperty(DCM_Cols, m_imageWidth);
header->setProperty(DCM_BitsAllocated, "8");
header->setProperty(DCM_BitsStored, "8");
header->setProperty(DCM_HighBit, "7");
header->setProperty(DCM_PixelRepresentation, "0");
header->setProperty(DCM_FrameIncrementPointer, "(0018, 1065)\\(F9E4, 3317)"); // Found this in a multiframe image that already existed.
dataset->chooseRepresentation(EXS_JPEGProcess14, &params);
dataset->saveFile(path.c_str(), EXS_JPEGProcess14);

Related

Getting audio sound level from FLTP audio stream

I need to get audio level or even better, EQ data from NDI audio stream in C++. Here's the struct of a audio packet:
// This describes an audio frame.
typedef struct NDIlib_audio_frame_v3_t {
// The sample-rate of this buffer.
int sample_rate;
// The number of audio channels.
int no_channels;
// The number of audio samples per channel.
int no_samples;
// The timecode of this frame in 100-nanosecond intervals.
int64_t timecode;
// What FourCC describing the type of data for this frame.
NDIlib_FourCC_audio_type_e FourCC;
// The audio data.
uint8_t* p_data;
union {
// If the FourCC is not a compressed type and the audio format is planar, then this will be the
// stride in bytes for a single channel.
int channel_stride_in_bytes;
// If the FourCC is a compressed type, then this will be the size of the p_data buffer in bytes.
int data_size_in_bytes;
};
// Per frame metadata for this frame. This is a NULL terminated UTF8 string that should be in XML format.
// If you do not want any metadata then you may specify NULL here.
const char* p_metadata;
// This is only valid when receiving a frame and is specified as a 100-nanosecond time that was the exact
// moment that the frame was submitted by the sending side and is generated by the SDK. If this value is
// NDIlib_recv_timestamp_undefined then this value is not available and is NDIlib_recv_timestamp_undefined.
int64_t timestamp;
#if NDILIB_CPP_DEFAULT_CONSTRUCTORS
NDIlib_audio_frame_v3_t(
int sample_rate_ = 48000, int no_channels_ = 2, int no_samples_ = 0,
int64_t timecode_ = NDIlib_send_timecode_synthesize,
NDIlib_FourCC_audio_type_e FourCC_ = NDIlib_FourCC_audio_type_FLTP,
uint8_t* p_data_ = NULL, int channel_stride_in_bytes_ = 0,
const char* p_metadata_ = NULL,
int64_t timestamp_ = 0
);
#endif // NDILIB_CPP_DEFAULT_CONSTRUCTORS
} NDIlib_audio_frame_v3_t;
Problem is that unlike video frames I have absolutely no idea how binary audio is packed and there's much less information about it online. The best information I found so far is this project:
https://github.com/gavinnn101/fishing_assistant/blob/7f5fcd73de1e39336226b5969cd1c5ca84c8058b/fishing_main.py#L124
It uses PyAudio however which I'm not familiar with and they use 16 bit audio format while mine seems to be 32bit and I can't figure out the struct.unpack stuff either because "%dh"%(count) is telling it some number then h for short which I don't understand how it would interpret.
Is there any C++ library that can take pointer to the data and type then has functions to extract sound level, sound level at certain hertz etc?
Or just some good information on how I would extract this myself? :)
I've searched the web a lot while finding very little. I've placed a breakpoint when the audio frame is populated but given up once I realize there's too many variables to think of that I don't have a clue about like sample rate, channels, sample count etc.
Got it working using
// This function calculates the RMS value of an audio frame
float calculateRMS(const NDIlib_audio_frame_v2_t& frame)
{
// Calculate the number of samples in the frame
int numSamples = frame.no_samples * frame.no_channels;
// Get a pointer to the start of the audio data
const float* data = frame.p_data;
// Calculate the sum of the squares of the samples
float sumSquares = 0.0f;
for (int i = 0; i < numSamples; ++i)
{
float sample = data[i];
sumSquares += sample * sample;
}
// Calculate the RMS value and return it
return std::sqrt(sumSquares / numSamples);
}
called as
// Keep receiving audio frames and printing their RMS values
NDIlib_audio_frame_v2_t audioFrame;
while (true)
{
// Wait for the next audio frame to be received
if (NDIlib_recv_capture_v2(pNDI_recv, NULL, &audioFrame, NULL, 0) != NDIlib_frame_type_audio)
continue;
// Print the RMS value of the audio frame
std::cout << "RMS: " << calculateRMS(audioFrame) << std::endl;
NDIlib_recv_free_audio_v2(pNDI_recv, &audioFrame);
}
Shoutout to chatGPT for explaining and feeding me with possible solutions until I managed to get a working solution :--)

How to copy an RGBA image to Windows' Clipboard

How might one copy a 32bit (per pixel) RGBA image to Windows' Clipboard? I've arrived to this function after a lot of trial, but no luck in having my image data "paste" at all. It does not appear in the Clipboard's history either.
Slightly editing it to use CF_DIB and the BITMAPINFOHEADER header has yielded a "copy" entry in that history and an image of the correct size when pasted, though sticking a png on the back of a CF_DIB has caused programs to glitch out in incredibly interesting and non-benign ways.
My goal is to copy an image with an alpha channel to the Clipboard, and to have the colors not be multiplied against this alpha during the hand-off. What am I be doing wrong..?
bool copyBitmapIntoClipboard(Window & window, const Bitmap & in) {
// this section is my code for creating a png file
StreamWrite stream = StreamWrite::asBufferCreate();
in.savePng(stream);
uint64 bufSize = 0;
char * buf = stream._takeBuffer(bufSize, false);
// "buf" <-- contains the PNG payload
// "bufSize" <-- is the size of this payload
// beyond this point, it's just standard windows' stuff that doesn't rely on my code
BITMAPV5HEADER header;
header.bV5Size = sizeof(BITMAPV5HEADER);
header.bV5Width = in.getX(); // <-- size of the bitmap in pixels, width and height
header.bV5Height = in.getY();
header.bV5Planes = 1;
header.bV5BitCount = 0;
header.bV5Compression = BI_PNG;
header.bV5SizeImage = bufSize;
header.bV5XPelsPerMeter = 0;
header.bV5YPelsPerMeter = 0;
header.bV5ClrUsed = 0;
header.bV5ClrImportant = 0;
header.bV5RedMask = 0xFF000000;
header.bV5GreenMask = 0x00FF0000;
header.bV5BlueMask = 0x0000FF00;
header.bV5AlphaMask = 0x000000FF;
header.bV5CSType = LCS_sRGB;
header.bV5Endpoints; // ignored
header.bV5GammaRed = 0;
header.bV5GammaGreen = 0;
header.bV5GammaBlue = 0;
header.bV5Intent = 0;
header.bV5ProfileData = 0;
header.bV5ProfileSize = 0;
header.bV5Reserved = 0;
HGLOBAL gift = GlobalAlloc(GMEM_MOVEABLE, sizeof(BITMAPV5HEADER) + bufSize);
if (gift == NULL)
return false;
HWND win = window.getWindowHandle();
if (!OpenClipboard(win)) {
GlobalFree(gift);
return false;
}
EmptyClipboard();
void * giftLocked = GlobalLock(gift);
if (giftLocked) {
memcpy(giftLocked, &header, sizeof(BITMAPV5HEADER));
memcpy((char*)giftLocked + sizeof(BITMAPV5HEADER), buf, bufSize);
}
GlobalUnlock(gift);
SetClipboardData(CF_DIBV5, gift);
CloseClipboard();
return true;
}
At least in my experience, trying to transfer png data with a BITMAPV5HEADER is nearly a complete loss, unless you're basically planning on using it strictly as an internal format.
One strategy that does work at least for a fair number of applications, is to register the PNG clipboard format, and just put the contents of a PNG file into the clipboard (with no other header). Code would look something like this:
bool copyBitmapIntoClipboard(Window & window, const Bitmap & in) {
// this section is my code for creating a png file
StreamWrite stream = StreamWrite::asBufferCreate();
in.savePng(stream);
uint64 bufSize = 0;
char * buf = stream._takeBuffer(bufSize, false);
// "buf" <-- contains the PNG payload
// "bufSize" <-- is the size of this payload
HGLOBAL gift = GlobalAlloc(GMEM_MOVEABLE, bufSize);
if (gift == NULL)
return false;
HWND win = window.getWindowHandle();
if (!OpenClipboard(win)) {
GlobalFree(gift);
return false;
}
EmptyClipboard();
auto fmt = RegisterClipboardFormat("PNG"); // or `L"PNG", as applicable
void * giftLocked = GlobalLock(gift);
if (giftLocked) {
memcpy((char*)giftLocked, buf, bufSize);
}
GlobalUnlock(gift);
SetClipboardData(fmt, gift);
CloseClipboard();
return true;
}
I've used code like this with, and successfully pasted the contents into recent versions of at least LibreOffice Write and Calc, MS Word, and Paint.Net.
This is also the format Chrome (for one example) will produce as the first (preferred) format if you tell it to copy a bitmap.
On the other hand, FireFox produces a whole plethora of formats, but not this one. It will produce a CF_DIBV5, but at least if memory serves, it has pre-multiplied alpha (or maybe it loses alpha completely--I don't remember for sure. Doesn't preserve it as you'd want anyway).
Gimp will accept 32-bit RGB format DIB, with alpha in the left-over byte, and make use of that alpha. For better or worse, as far as I've been able to figure out that's about the only thing that works to paste something into Gimp with its alpha preserved (not pre-multiplied).
Notes
As versions are updated, the formats they support may well change, so even though (for example) PNG didn't work with Gimp the last time I tried, it might now.
You can add the same data into the clipboard in different formats. You want to start from the "best" format (the one that preserves the data most faithfully), and work your way down to the worst. So when you do a copy, you might want to do PNG, then RGB with an alpha channel, then CF_BITMAP (which will pre-multiply alpha, but may still be better than nothing).

how to get image shape after decode in Tensorflow's C++ API

I follow the Tensorflow tutorial of inception label_image, I can compile and run the demo c++ code successfully.
I want to adapt this demo to my own project, the input images to my own Network is height fixed, while width varies accordingly, for example, the original image is size of 64x100, and I want to resize it to 32x50, as I said 32 is the new_height, and I want to know original image size after reading from the file, how can I get width=100 and height=64? then I can get new_width = new_height/height x width=32/64x100=50
one possible way is first use opencv to load the image, and resize it, then copy the elements to tensor like this example pixel by pixel, but the performance is the main problem and it seems hard to compile tensorflow along with opencv. Any one knows some methods using tensorflow's API?
the following is a small piece of the image_recognition tutorial C++ codes, resize is hard coded to a pre-define size, I try float_caster.shape(), tensor(), float_caster.dimension(0), etc, all failed(float_caster, file_reader are all not Tensor, I don't know why Google design like this, really slow down the development, and I find no documentation about this), is there any easy way to get the image size? or cast the tensorflow::Ouput type to Tensor?
Thanks in advance!
// Given an image file name, read in the data, try to decode it as an image,
// resize it to the requested size, and then scale the values as desired.
Status ReadTensorFromImageFile(string file_name, const int input_height,
const int input_width, const float input_mean,
const float input_std,
std::vector<Tensor>* out_tensors) {
auto root = tensorflow::Scope::NewRootScope();
using namespace ::tensorflow::ops; // NOLINT(build/namespaces)
string input_name = "file_reader";
string output_name = "normalized";
auto file_reader =
tensorflow::ops::ReadFile(root.WithOpName(input_name), file_name);
// Now try to figure out what kind of file it is and decode it.
const int wanted_channels = 3;
tensorflow::Output image_reader;
if (tensorflow::StringPiece(file_name).ends_with(".png")) {
image_reader = DecodePng(root.WithOpName("png_reader"), file_reader,
DecodePng::Channels(wanted_channels));
} else if (tensorflow::StringPiece(file_name).ends_with(".gif")) {
image_reader = DecodeGif(root.WithOpName("gif_reader"), file_reader);
} else {
// Assume if it's neither a PNG nor a GIF then it must be a JPEG.
image_reader = DecodeJpeg(root.WithOpName("jpeg_reader"), file_reader,
DecodeJpeg::Channels(wanted_channels));
}
// Now cast the image data to float so we can do normal math on it.
auto float_caster =
Cast(root.WithOpName("float_caster"), image_reader, tensorflow::DT_FLOAT);
// The convention for image ops in TensorFlow is that all images are expected
// to be in batches, so that they're four-dimensional arrays with indices of
// [batch, height, width, channel]. Because we only have a single image, we
// have to add a batch dimension of 1 to the start with ExpandDims().
auto dims_expander = ExpandDims(root, float_caster, 0);
// Bilinearly resize the image to fit the required dimensions.
auto resized = ResizeBilinear(
root, dims_expander,
Const(root.WithOpName("size"), {input_height, input_width}));
// Subtract the mean and divide by the scale.
Div(root.WithOpName(output_name), Sub(root, resized, {input_mean}),
{input_std});
// This runs the GraphDef network definition that we've just constructed, and
// returns the results in the output tensor.
tensorflow::GraphDef graph;
TF_RETURN_IF_ERROR(root.ToGraphDef(&graph));
std::unique_ptr<tensorflow::Session> session(
tensorflow::NewSession(tensorflow::SessionOptions()));
TF_RETURN_IF_ERROR(session->Create(graph));
TF_RETURN_IF_ERROR(session->Run({}, {output_name}, {}, out_tensors));
return Status::OK();
}
As you said
I want to know original image size after reading from the file
So I suppose you don't mind get the height and width using the output tensor:
Status read_tensor_status =
ReadTensorFromImageFile(image_path, input_height, input_width, input_mean,
input_std, &resized_tensors);
if (!read_tensor_status.ok()) {
LOG(ERROR) << read_tensor_status;
return -1;
}
// #resized_tensor: the tensor storing the image
const Tensor &resized_tensor = resized_tensors[0];
auto resized_tensor_height = resized_tensor.shape().dim_sizes()[1];
auto resized_tensor_width = resized_tensor.shape().dim_sizes()[2];
std::cout << "resized_tensor_height:\t" << resized_tensor_height
<< "\nresized_tensor_width:\t" << resized_tensor_width << std::endl;
And the output is (for me)
resized_tensor_height: 636
resized_tensor_width: 1024

Drawing Bitmap on to screen using CRenderTarget::DrawBitmap and Byte Array (instead of file) in VC++

I am working with COSMCtrl in order to display maps on to the viewing window.
In the COSMCtrl, a file name is passed on to the CD2DBitmap constructor along with CRenderTarget object. But my application doesnt have image file. It will receive image data (in the form of byte array) from a database.
Could any one please help me in finding out the solution ?
The sample code is below:
BOOL COSMCtrl::DrawTile(CRenderTarget* pRenderTarget, const CD2DRectF& rTile, int nTileX, int nTileY)
{
//What will be the return value from this function (assume the worst)
BOOL bSuccess = FALSE;
//Form the path to the cache file which we want to draw
int nZoom = static_cast<int>(m_fZoom);
CString sFile(GetTileCachePath(m_sCacheDirectory, nZoom, nTileX, nTileY, FALSE));
//Get the fractional value of the zoom
double fInt = 0;
double fFractionalZoom = modf(m_fZoom, &fInt);
//Try to obtain the standard tile
CD2DBitmap bitmap(pRenderTarget, sFile);
// I have a Byte Array. I should pass the byte array instead of file
//Determine how the tile should be draw
BOOL bStandardTile = FALSE;
if (fFractionalZoom == 0 && SUCCEEDED(bitmap.Create(pRenderTarget)))
bStandardTile = TRUE;
//Load up the image from disk and display it if we can
if (bStandardTile)
{
//Draw the image to the screen at the specified position
pRenderTarget->DrawBitmap(&bitmap, rTile, 1.0);
bSuccess = TRUE;
}
return bSuccess;
}
I am not allowed to save the byte array to disk (as image).
I have tried using the other constructor of CD2DBitmap which accepts CRenderTarget and HBITMAP. but of no use

Forcing to WIC to treat image data as RGB

I have a image as a BYTE array(in RGB format) and I needed to encode it. I did, but colors have changed.
/*Create stream and initialized*/
hr = piFactory->CreateStream(&piStream);
hr = piStream->InitializeFromFilename(L"..\\test.jpg",GENERIC_WRITE);
/*created an encoder. I want to save JPG*/
hr = piFactory->CreateEncoder(GUID_ContainerFormatJpeg, NULL, &piEncoder);
did some manipulations, also tried to set pixel format.
hr = piBitmapFrame->SetPixelFormat(&formatGUID);
but it always set to "GUID_WICPixelFormat24bppBGR"
and i write image data as here.
/*pBitmap contains image data*/
hr = piBitmapFrame->WritePixels(lHeight, cbStride, cbBufferSize, pBitmap);
the problem was colors of the image has changed, and I found that if i change RGB to BGR then the output looks fine. so i did this
for(int i = 0; i < DataSize; i += 3)
{
inBuff[i] = pBitmap[i+2];
inBuff[i + 1] = pBitmap[i+1];
inBuff[i + 2] = pBitmap[i];
}
pBitmap = inBuff
But i dont want to expend more time here looping through whole image.
I need to tell WIC "treat the data as RGB(or BGR)".
is that possible? if it is then how?
You can create a bitmap first. at that time you could say the format it needs to be. Here is "24bppRGB" format:
hr = piFactory->CreateBitmapFromMemory(
lWidth,
lHeight,
GUID_WICPixelFormat24bppRGB,
cbStride,
cbBufferSize,
pBitmap,
&piBitmapSrc
);
This is how You write image data into the frame:
hr = piBitmapFrame->WritePixels(lHeight, cbStride, cbBufferSize, pBitmap);
Instead write image to the frame like this (because WritePixels() doesn't accept bitmap):
hr = piBitmapFrame->WriteSource(
piBitmapSrc,
NULL
);
And that help to avoid the loop. I'm not sure what happens inside the CreateBitmapFromMemory() though. Since you are concerned with time, I don't know if this will help (it still makes your code better).
Found a way.
This forum says,
This is covered on MSDN
The JPEG encoder encodes three formats:
GUID_WICPixelFormat8bppGray
GUID_WICPixelFormat24bppBGR
GUID_WICPixelFormat32bppCMYK
If your source data is in a different format, you need to use IWICFormatConverter as part of your chain.