ASanch ASanch - 1 month ago 16
C++ Question

Adding a BMP grayscale header

I would firstly like to warn you that I'm just a physicist who is trying to get things to work, my knowledge of c++ is basically non-existent.

I am currently working on a simulation of a CT scanner with GATE and I need to transform the output into bmp files.

Gate makes a series of file_xxx.dat where xxx ranges from 000 to the number of projections you make and each of them contains an array of 32bit float, one for each pixel your detector has.

I need to take each one of them and add a grayscale bmp header so I can reconstruct them with another program. In the process I'd like to preserve the 32bit precision so that I don't lose any of the information from the pixels so it would be a 32bit grayscale, if that's even possible.

I have been working with another of the outputs Gate has, a root file, to try to get the bmp header working. I'd rather avoid using the root file because it forces me to go through a huge amount of information I don't need and it slows down the process.

I have written some parts of this code, I have copy-pasted others and some parts have been done by my co-workers, I barely know what I'm doing most of the time.

#include <iostream>
#include <fstream>
#include <ostream>
#include <cerrno>
#include <cstdlib>
#include <set>
#include <cmath>
#include "TApplication.h"
#include "TFile.h"
#include "TTree.h"
using namespace std;

#define MAX_ANGLES 180
#define PIXELS_X 100
#define PIXELS_Y 100
#define MAX_PIXELS 10000

struct SDataA
{
float A[MAX_ANGLES][MAX_PIXELS];

void Reset()
{
for (int a=0; a<MAX_ANGLES; a++)
{
for (int p=0; p<MAX_PIXELS; p++)
{
A[a][p] = 0.0;
}
}
}
};

int main( int argc, char* argv[] )
{

if( argc < 2 )
{
cerr << "arguments missing" << endl;
cerr << "Usage : AnalyzeCT myFile.root " << endl;
exit( EXIT_FAILURE );
}

// Store the root file name in 'fileName' variable
char* const FILENAME = argv[ 1 ];

// parameters for the histograms

float a;

SDataA *dataA=NULL;
dataA = new SDataA;
dataA->Reset();

int maxangles=MAX_ANGLES;
int pixelsx=PIXELS_X;

TApplication app( "Application", &argc, argv );

// Open (check) and read the root file
TFile* file = new TFile( FILENAME );
if( !file->IsOpen() )
{
cerr << "problem opening the root file : '" << FILENAME << "'" << endl;
cerr << strerror( errno ) << endl;
exit( EXIT_FAILURE );
}

// Take the single tree, where is the position, the energy and the runID
TTree* singlesTree = (TTree*)file->Get( "Singles" );
Int_t runID, pixelID;
singlesTree->SetBranchAddress( "runID", &runID );
singlesTree->SetBranchAddress( "pixelID", &pixelID );

// Number of entries in the single tree
Int_t entriesSingleTree = (Int_t)singlesTree->GetEntries();
cout << "Number of detected photons : " << entriesSingleTree << endl;

for( Int_t i = 0; i != entriesSingleTree; ++i )
{
singlesTree->GetEntry( i );

if ((runID < MAX_ANGLES) && (pixelID < MAX_PIXELS))
{
dataA->A[runID][pixelID] += 1;
a=dataA->A[runID][pixelID];
// cout << "A[" << runID <<"]["<< pixelID<<"] ="<< a << endl;
}

}

std::ofstream ofile("Slice.bin", std::ios::binary);
int currangle=0;
short BM=19778;
short bfReserved=0,biPlanes=1,biBitCount=32;
int bfSize=54+40000, bfOffBits=54, biSize=40, biWidth=PIXELS_X, biHeight=PIXELS_Y,Zero=0,biClrUsed=1;

ofile.write((char*) &BM, sizeof(short));
ofile.write((char*) &bfSize, sizeof(int));
ofile.write((char*) &bfReserved, sizeof(short));
ofile.write((char*) &bfReserved, sizeof(short));
ofile.write((char*) &bfOffBits, sizeof(int));

ofile.write((char*) &biSize, sizeof(int));
ofile.write((char*) &biWidth, sizeof(int));
ofile.write((char*) &biHeight, sizeof(int));
ofile.write((char*) &biPlanes, sizeof(short));
ofile.write((char*) &biBitCount, sizeof(short));
ofile.write((char*) &Zero, sizeof(int));
ofile.write((char*) &Zero, sizeof(int));
ofile.write((char*) &Zero, sizeof(int));
ofile.write((char*) &Zero, sizeof(int));
ofile.write((char*) &biClrUsed, sizeof(int));
ofile.write((char*) &Zero, sizeof(int));

for ( Int_t k =currangle ; k<currangle+1; k++){
for (Int_t j=0; j<MAX_PIXELS; j++){
a=dataA->A[k][j];
//cout<< dataA->A[k][j]<< endl;
ofile.write((char*) &a, sizeof(float)); //s1
}

}
ofile.close();

cout << "Done" << endl;

delete singlesTree;
delete gateTree;

app.Run();


return 0;
}


There may be some chunks which seem incomplete but that's because I've been removing some parts that in the original code have been commented out and it's really Frankenstein's monster right now.

The thing is that I'm actually getting a file which is 40054 bytes long (54 from the header and 40000 from my 10000 floats) and I'm renaming it into .bmp afterwards to see if it can be read but there's no way, I guess there may be some inconsistent definitions on the header but I have no clue at all.

I have also tried to use opencv to read my dat files but my machine is messed up and all I can get out of it are errors

EDIT:1)
Here's a dat file https://ufile.io/86210
I open them with amide, they contain one of the projections generated with the simulations. I guess a float may be too much for the numbers it contains right now, but I may have to go into simulations with higher number of counts.

Here is the root file https://ufile.io/a073

EDIT:2)
True, the biSize was not set, now biSize=40 works The image is all red and black but it looks like what it should. the link would be like the previous ending in /0a111 (I need more reputation to post it) I'm sorry it was such an stupid thing, I've been struggling for days and I lost focus

If there's any better way of linking the files please let me know

Best regards,

ASanch

Answer

You can convert this data to a conventional image format without writing any software if you use ImageMagick which is installed on most Linux distros and is available for macOS and Windows.

If we assume that your 10,000 floats corresponds to an image of 100x100 pixels each of which is a floating point number, and is in a file called image.bin, you can type this at the command line to get a normalised floating point TIF file:

convert -size 100x100 -depth 32 -define quantum:format=floating-point gray:image.bin -normalize result.tif

enter image description here

  • If you don't want the data normalised to fill the full range, omit the -normalize.

  • If you want a PNG file rather than a TIF just change result.tif to result.png

  • If your data is little/big endian, add in -endian lsb or -endian msb.

I can't seem to download your larger file, but if you have a 54 byte header to remove, you can either change the command to something like this:

convert -size 100x100+54 -depth 32 -define quantum:format=floating-point gray:image.bin -normalize result.tif

or use an external utility such as dd to lop off the 54 byte header:

dd if=image.bin bs=54 skip=1 | convert -size 100x100 -depth 32 -define quantum:format=floating-point gray:-  -normalize result.tif
Comments