binary.h 11.7 KB

//make sure that this header file is only loaded once
#ifndef RTS_BINARY_H
#define RTS_BINARY_H

#include "../envi/envi_header.h"
#include "../math/vector.h"
#include <fstream>
#include <sys/stat.h>

namespace stim{

/** This class manages the streaming of large multidimensional binary files.
 *  Generally these are hyperspectral files with 2 spatial and 1 spectral dimension. However, this class supports
 *  other dimensions via the template parameter D.
 *
 *  @param T is the data type used to store data to disk (generally float or double)
 *  @param D is the dimension of the data (default 3)
 */
template< typename T, unsigned int D = 3 >
class binary{

protected:
	std::fstream file;		//file stream used for reading and writing
	std::string name;		//file name

	unsigned long long R[D];		//resolution
	unsigned long long header;	//header size (in bytes)
	unsigned char* mask;	//pointer to a character array: 0 = background, 1 = foreground (or valid data)

	double progress;		//stores the progress on the current operation (accessible using a thread)


	/// Private initialization function used to set default parameters in the data structure.
	void init(){
		memset(R, 0, sizeof(unsigned long long) * D);		//initialize the resolution to zero
		header = 0;									//initialize the header size to zero
		mask = NULL;

		progress = 0;
	}

	/// Private helper function that returns the size of the file on disk using system functions.
	long long int get_file_size(){
#ifdef _WIN32
		struct _stat64 results;
		if(_stat64(name.c_str(), &results) == 0)
			return results.st_size;
#else
		struct stat results;
		if(stat(name.c_str(), &results) == 0)
			return results.st_size;
#endif
		else return 0;
	}

	/// Private helper function that tests to make sure that the calculated data size specified by the structure is the same as the data size on disk.
	bool test_file_size(){
		long long int npts = 1;				//initialize the number of data points to 1
		for(unsigned int i = 0; i<D; i++)	//iterate over each dimension
			npts *= R[i];					//compute the total number of data points in the file
		long long int datasize = npts * sizeof(T);//multiply the sum by the size of the template parameter

		if(datasize + header == get_file_size()) return true;	//if the byte size matches the file size, we're golden
		else return false;					//otherwise return an error

	}

	/// Private helper function that resets the file pointer to the beginning of the data

	void reset(){
		file.seekg(header, std::ios_base::beg);
	}

	/// Private helper file that opens a specified binary file.

	/// @param filename is the name of the binary file to stream
	bool open_file(std::string filename){
		//open the file as binary for reading and writing
		file.open(filename.c_str(), std::ios::in | std::ios::out | std::ios::binary);

		//if the file isn't open, the user may only have read access
		if(!file.is_open()){
			std::cout<<"class STIM::BINARY - failed to open file, trying for read only"<<std::endl;
			file.open(filename.c_str(), std::ios::in | std::ios::binary);
			if(!file.is_open()){
				std::cout<<"               still unable to load the file"<<std::endl;
				return false;
			}
		}

		//if the file is successful
		if(file){
			name = filename;		//set the name
			if(test_file_size())	//test the file size
				return true;
		}

		return false;
	}





public:

	double get_progress(){
		return progress;
	}

	void reset_progress(){
		progress = 0;
	}

	/// Open a binary file for streaming.

	/// @param filename is the name of the binary file
	/// @param r is a STIM vector specifying the size of the binary file along each dimension
	/// @param h is the length (in bytes) of any header file (default zero)
	bool open(std::string filename, vec<unsigned long long> r, unsigned long long h = 0){

		for(unsigned long long i = 0; i < D; i++)		//set the dimensions of the binary file object
			R[i] = r[i];

		header = h;				//save the header size

		if(!open_file(filename)) return false;	//open the binary file

		//reset();

		return test_file_size();
	}

	/// Creates a new binary file for streaming

	/// @param filename is the name of the binary file to be created
	/// @param r is a STIM vector specifying the size of the file along each dimension
	/// @offset specifies how many bytes to offset the file (used to leave room for a header)
	bool create(std::string filename, vec<unsigned long long> r, unsigned long long offset = 0){

		std::ofstream target(filename.c_str(), std::ios::binary);

		//initialize binary file
		T p = 0;
		for(unsigned long long i =0; i < r[0] * r[1] * r[2]; i++){
			target.write((char*)(&p), sizeof(T));
		}

		for(unsigned long long i = 0; i < D; i++)		//set the dimensions of the binary file object
			R[i] = r[i];

		header = offset;				//save the header size

		if(!open_file(filename)) return false;	//open the binary file

		return test_file_size();
	}

	/// Writes a single page of data to disk. A page consists of a sequence of data of size R[0] * R[1] * ... * R[D-1].

	/// @param p is a pointer to the data to be written
	/// @param page is the page number (index of the highest-numbered dimension)
	bool write_page( T * p, unsigned long long page){

		if(p == NULL){
			std::cout<<"ERROR: unable to write into file, empty pointer"<<std::endl;
			exit(1);
		}

		file.seekg(R[1] * R[0] * page * sizeof(T) + header, std::ios::beg);   //seek to the desired location on disk
		file.write((char *)p, R[0] * R[1] * sizeof(T));				 //write binary data

		return true;
	}

	/// Reads a page from disk. A page consists of a sequence of data of size R[0] * R[1] * ... * R[D-1].

	/// @param p is a pointer to pre-allocated memory equal to the page size
	/// @param page is the index of the page
	bool read_page( T * p, unsigned long long page, bool PROGRESS = false){

		if(PROGRESS) progress = 0;

		if (page >= R[2]){										//make sure the bank number is right
			std::cout<<"ERROR: page out of range"<<std::endl;
			return false;
		}

		file.seekg(R[1] * R[0] * page * sizeof(T) + header, std::ios::beg);   //write into memory from the binary file
		file.read((char *)p, R[0] * R[1] * sizeof(T));
		if(PROGRESS) progress = 100;
		return true;
	}



	///Reads a line Z (slowest dimension) for a given XY value

	/// @param p is a pointer to pre-allocated memory equal to the line size R[2]
	/// @param x is the x coordinate
	/// @param y is the y coordinate
	bool read_line_2( T * p, unsigned long long x, unsigned long long y, bool PROGRESS = false){
		unsigned long long i;

		if(PROGRESS) progress = 0;

		if ( x >= R[0] || y >= R[1]){							//make sure the sample and line number is right
			std::cout<<"ERROR: sample or line out of range"<<std::endl;
			return false;
		}

		file.seekg((x + y * R[0]) * sizeof(T), std::ios::beg);           //point to the certain sample and line
		for (i = 0; i < R[2]; i++)
		{
			file.read((char *)(p + i), sizeof(T));
			file.seekg((R[1] * R[0] - 1) * sizeof(T), std::ios::cur);    //go to the next band
			if(PROGRESS) progress = (double)i / (double)R[2] * 100;
		}
		if(PROGRESS) progress = 100;

		return true;
	}

	///Reads a line X (fastest dimension) for a given YZ value

	/// @param p is a pointer to pre-allocated memory equal to the line size R[2]
	/// @param x is the y coordinate
	/// @param y is the z coordinate
	bool read_line_0(T * p, unsigned long long y, unsigned long long z, bool PROGRESS = false){
		//test to make sure the specified value is within range
		if( y >= R[1] || z >= R[2] ){
			std::cout<<"ERROR: sample or line out of range"<<std::endl;
			return false;
		}

		file.seekg((z * R[0] * R[1] + y * R[0]) * sizeof(T), std::ios::beg);	//seek to the start of the line
		file.read((char *)p, sizeof(T) * R[0]);									//read the line
		if(PROGRESS) progress = 100;
		return true;
	}

	///Reads a line Y (second fastest dimension) for a given XZ value

	/// @param p is a pointer to pre-allocated memory equal to the line size R[2]
	/// @param x is the y coordinate
	/// @param z is the z coordinate
	bool read_line_1(T * p, unsigned long long x, unsigned long long z, bool PROGRESS = false){
		if(PROGRESS) progress = 0;
		//test to make sure the specified value is within range
		if( x >= R[0] || z >= R[2] ){
			std::cout<<"ERROR: sample or line out of range"<<std::endl;
			return false;
		}

		file.seekg((z * R[0] * R[1] + x) * sizeof(T), std::ios::beg);           //seek to the start of the line
		for (unsigned long long i = 0; i < R[1]; i++){									//for each pixel in the line
			file.read((char *)(p + i), sizeof(T));					//read the pixel
			file.seekg((R[0] - 1) * sizeof(T), std::ios::cur);		//seek to the next pixel in the line
			if(PROGRESS) progress = (double)i / (double)R[1] * 100;
		}
		if(PROGRESS) progress = 100;
		return true;
	}

	/// Reads a plane given a coordinate along the 0-axis (YZ plane)

	/// @param p is a pointer to pre-allocated memory of size R[1] * R[2] * sizeof(T)
	/// @param n is the 0-axis coordinate used to retrieve the plane
	bool read_plane_0(T* p, unsigned long long n, bool PROGRESS = false){
		if(PROGRESS) progress = 0;
		if (n >= R[0]){										//make sure the number is within the possible range
			std::cout<<"ERROR read_plane_0: page out of range"<<std::endl;
			return false;
		}
		unsigned long long jump = (R[0] - 1) * sizeof(T);		//number of bytes to skip between samples

		//seek to the start of the plane
		file.seekg(n * sizeof(T), std::ios::beg);

		unsigned long long N = R[1] * R[2];
		for(unsigned long long i = 0; i<N; i++){
			file.read((char*)(p+i), sizeof(T));
			file.seekg(jump, std::ios::cur);
			if(PROGRESS) progress = (double)(i+1) / N * 100;
		}

		return true;


	}

	/// Reads a plane given a coordinate along the 1-axis (XZ plane)

	/// @param p is a pointer to pre-allocated memory of size R[0] * R[2] * sizeof(T)
	/// @param n is the 1-axis coordinate used to retrieve the plane
	bool read_plane_1(T* p, unsigned long long n, bool PROGRESS = false){
		if(PROGRESS) progress = 0;
		unsigned long long L = R[0] * sizeof(T);		//caculate the number of bytes in a sample line
		unsigned long long jump = R[0] * (R[1] - 1) * sizeof(T);

		if (n >= R[1]){										//make sure the bank number is right
			std::cout<<"ERROR read_plane_1: page out of range"<<std::endl;
			return false;
		}

		file.seekg(R[0] * n * sizeof(T), std::ios::beg);
		for (unsigned long long i = 0; i < R[2]; i++){
			if(PROGRESS) progress = (double)i / R[2] * 100;
			file.read((char *)(p + i * R[0]), L);
			file.seekg( jump, std::ios::cur);
			std::cout<<i<<"    ";
		}

		if(PROGRESS) progress = 100;
		return true;
	}

	/// Reads a plane given a coordinate along the 2-axis (XY plane)

	/// @param p is a pointer to pre-allocated memory of size R[0] * R[1] * sizeof(T)
	/// @param n is the 2-axis coordinate used to retrieve the plane
	bool read_plane_2(T* p, unsigned long long n, bool PROGRESS = false){
		return read_page(p, n, PROGRESS);
	}

	/// Reads a single pixel, treating the entire data set as a linear array

	/// @param p is a pointer to pre-allocated memory of size sizeof(T)
	/// @param i is the index to the pixel using linear indexing
	bool read_pixel(T* p, unsigned long long i){
		if(i >= R[0] * R[1] * R[2]){
			std::cout<<"ERROR read_pixel: n is out of range"<<std::endl;
			return false;
		}

		file.seekg(i * sizeof(T), std::ios::cur);
		file.read((char*)p, sizeof(T));

	}

	/// Reads a single pixel, given an x, y, z coordinate

	/// @param p is a pointer to pre-allocated memory of size sizeof(T)
	/// @param x is the x (0) axis coordinate
	/// @param y is the y (1) axis coordinate
	/// @param z is the z (2) axis coordinate
	bool read_pixel(T* p, unsigned long long x, unsigned long long y, unsigned long long z){

		if(x < 0 || x >= R[0] || y < 0 || y >= R[1] || z < 0 || z > R[2]){
			std::cout<<"ERROR read_pixel: (x,y,z) is out of range"<<std::endl;
			return false;
		}

		unsigned long long i = z * R[0] * R[1] + y * R[0] + z;
		return read_pixel(p, i);
	}

};

}

#endif