//#include "lapacke.h" //#include "cblas.h" //#include #include "linalg.h" #include #include #include #include #include "stim/parser/arguments.h" #include "stim/envi/envi_header.h" #include "stim/envi/envi.h" #include "stim/visualization/colormap.h" #include #include #include #include #include #include #include //LAPACKE support for Visual Studio //#include //#ifndef LAPACK_COMPLEX_CUSTOM //#define LAPACK_COMPLEX_CUSTOM //#define lapack_complex_float std::complex //#define lapack_complex_double std::complex //#endif void baseline(std::string infile, std::string outfile, std::string headerfile, std::vector points, unsigned char* mask); void normalize(std::string infile, std::string outfile, std::string headerfile, double band, unsigned char* mask); void convert(std::string infile, std::string outfile, std::string headerfile, stim::envi_header::interleaveType type); std::vector< std::vector > read_metric_list(std::string filename); void create_metric_file(std::vector< std::vector > metrics, std::string outfile); stim::colormapType str2cmap(std::string str); void bandimage(std::string infile, std::string headername, double band, std::string filename, stim::colormapType cmap, unsigned char* MASK); void bandimage(std::string infile, std::string headername, double band, std::string filename, double minVal, double maxVal, stim::colormapType cmap, unsigned char* MASK); //void mosaic_agilent(std::string directory, std::string outfile, bool create_header); void mosaic_agilent_interferogram(std::string filemask, std::string outfile, double ELWN, int UDR); void mosaic_spero(std::string directory, std::string outfile); void mnf(std::string outfile, int keptComponents, std::string NoiseFractions, int cuda_device); void progress_thread_envi(stim::envi* e); //progress bar threaded function void progress_thread_double(double* e); //progress bar threaded function //CUDA externs //void gpu_bsq2bip(); # define MAX_CLASSES 20 # define per 20000 unsigned char* MASK = NULL; //pointer to an input file mask if one is provided stim::envi ENVI; //input ENVI file to be processed unsigned long long X, Y, B; //registers to quickly access the image size bool progressbar = true; bool verbose = false; bool optimization = true; void advertisement(){ std::cout< image(maskfile); //create an image from the file stim::image mask = image.channel(0); //grab the first channel of the image if (mask.width() != X || mask.height() != Y) { std::cout << "ERROR - mask size doesn't match image size: mask = [" << mask.width() << " x " << mask.height() << "], input = [" << X << " x " << Y << "]" << std::endl; exit(1); } if(N == 0){ //if only the mask name is given, use all pixels in the mask for(size_t xy = 0; xy < X*Y; xy++) //for each pixel in the image if(mask.data()[xy]) MASK[xy] = mask.data()[xy]; //copy it to the MASK pointer } else{ //if a number of values is also specified N = (std::min)(N, mask.nnz()); //calculate the minimum (this odd notation is used to defeat a macro implemented in Visual Studio) std::vector idx = image.sparse_idx(); //get the indices for the nonzero values in the image std::random_device rd; //create a random number generation function std::mt19937 g(rd()); std::shuffle(idx.begin(), idx.end(), g); //shuffle the index values for(size_t i = 0; i < N; i++){ //for each of the first n random pixels MASK[idx[i]] = mask.data()[idx[i]]; //store the pixel value from the image into the mask } } } int main(int argc, char** argv){ //create an argument list stim::arglist args; #ifdef _WIN32 args.set_ansi(false); #endif //basic arguments args.add("help", "prints this help"); args.section("Binary File Manipulation"); args.add("convert", "convert file to bip, bsq, or bil", "", "[bsq], [bip], or [bil]"); args.add("sift", "create a matrix of masked spectra in lexicographic order", "", "[mask_filename]"); args.add("unsift", "creates a 2D image from a matrix of pixels in lexicographic order", "", "[mask_filename]"); args.add("crop", "crop part of original file, use '-' to specify the limit, spectral coordinates are given in wavelength by default", "", "[x nx y ny w nw]"); args.add("subimages", "extract a subimage at each mask point", "", "[maskfile width height], if no height is given, height = width"); args.add("trim", "remove bands", "", "[b0 b1 b2-b3], removes b0, b1, and all bands between b2 and b3"); args.add("select", "selects a set of bands from the input image and uses them to create a new output image", "", "list of wavelengths or bands, ex: 1250 1650 3200 ..."); args.add("combine", "combines two images by placing the second at a specified position relative to the first", "", "[filename px py], if (px, py) isn't provided, combining is done along Y"); args.add("append", "appends the bands from an image to the input image", "", "[filename] - the input and append file must be the same size in X and Y"); args.section("Arithmetic"); args.add("multiply", "multiply values in the image by some number n", "", "[n] where n is any real value"); args.add("add", "add values in the image by some number n", "", "any real value"); args.section("Data Retrieval / Analysis"); args.add("image", "wavelength and filename for a band image (.raw files are X x Y float32)", "", "[wavelength] or [wavelength min max]"); args.add("colormap", "specify the colormap for an image", "brewer", "brewer, grey"); args.add("spectrum", "saves a CSV spectrum", "", "[x y]"); args.add("mean", "saves the mean spectrum and variance (sigma^2) as a CSV file - mask can (and should) be applied"); args.add("median", "saves the median spectrum as a CSV file - mask can be applied, only works for BSQ files"); args.add("covariance", "calculate the covariance matrix of the spectrum and saves it as a CSV file (BIP recommended)", "", "[mean.csv], optional CSV file name storing the mean spectrum"); args.add("deriv", "approximate the d-th derivative at order n (default n=2) using finite differences", "", "[d n]"); args.section("Convolution (assumes equally spaced points)"); args.add("convolve", "convolve the spectra with a given set of coefficients", "", "[c1 c2 c3...cn]"); args.add("sg", "applies a Savitzky-Golay filter of width w and order n, where w is odd and n < w/2", "", "[w n]"); args.section("Hyperspectral Image Correction"); args.add("baseline", "piecewise linear baseline correction", "", "[baseline-file.txt] or [p0 p1 p2 p3 ...]"); args.add("mnf", "Generate a basis for MNF noise removal. Use --project to apply the basis projection to a noisy file", "", "[keptComponents noisefile] , ex. --mnf 7 , in most cases: 3 < keptComponents < 23"); args.add("matcond", "Specify the threshold for the MNF transform condition number c - a warning will be given if c is below this threshold.", "0.01"); args.add("normalize", "normalize spectra using vector normalization (or a band ratio of a band is specified)", "", "[band]"); args.section("Masking"); args.add("threshold", "create a mask that includes all pixel in band such that min < val < max", "", "[band min max]"); args.add("mask-finite", "create a mask - all pixels without inf or NaN values are masked"); args.add("apply-mask","apply a mask (or set of masks) to an image (any false value in a mask is set to 0)", "", "[image_1 image_2 image_3]"); args.add("mask", "limit clustering to a masked region specified by an image file", "", "[filename n], where n (optional) limits the mask to n random samples of the image"); args.section("Dimension Reduction"); args.add("pca", "calculate and save the PCA basis transform (BIP recommended)"); args.add("project", "perform a forward projection given a translation and set of basis vectors", "", "[stats_file #vectors], ex. PCA: [pca.sta 30]"); args.add("inverse", "perform an inverse projection given a translation and set of basis vectors", "", "[stats_file #vectors]"); args.add("metrics", "calculate metrics based on Bhargava et al. metric file", "", "[metric_file]\n\t\t\t" "metric format:\n\t\t\t" "peak height ratio: \t1 LB1 RB1 P1 LB2 RB2 P2 0 0\n\t\t\t" "peak area ratio: \t2 LB1 RB1 LP1 RP1 LB2 RB2 P2 0\n\t\t\t" "area to area ratio: \t3 LB1 RB1 LP1 RP1 LB2 RB2 LP2 RP2\n\t\t\t" "center of gravity: \t4 LB1 RB1 LP1 RP1 0 0 0 0"); args.section("Hardware-Specific Processing"); args.add("create-header", "create a basic header file to represent the mosaic"); args.section("Debugging Parameters"); args.add("verbose", "provide verbose debug messages and output"); args.add("noprogress", "removes the progress bar from the output"); args.add("nooptimization", "turns off batch optimization, max batch size is used"); args.add("mempct", "percentage of available memory to use for processing", "40", "values over 90% are not recommended"); args.add("memraw", "specify a raw amount of memory (in MB) to use for processing (caution)"); args.add("cuda", "selects the default CUDA device used for calculations", "0", "integer ID, (-1 prevents CUDA use even if available)"); //parse the command line arguments args.parse(argc, argv); if(args["help"].is_set()){ //display the help text if requested advertisement(); std::cout<::max(); //set the default maximum value to the maximum possible double value if(args["threshold"].nargs() == 3){ upper = args["threshold"].as_float(2); } unsigned long long N = X * Y; //get the mask size (samples * lines) unsigned char* mask = (unsigned char*)malloc(N * sizeof(unsigned char)); //allocate memory for the mask std::thread t1(progress_thread_envi, &ENVI); //start the progress bar thread std::cout<<"Creating a mask at band "< mask_image(mask, X, Y); mask_image.save(outfile.c_str()); //save the mask image //count the number of pixels that are masked unsigned long long P = 0; for(unsigned long long i = 0; i < N; i++) if(mask[i] != 0) P++; std::cout< mask_image(mask, X, Y); mask_image.save(outfile.c_str()); //save the mask image //count the number of pixels that are masked unsigned long long P = 0; for(unsigned long long i = 0; i < N; i++) if(mask[i] != 0) P++; std::cout< blpts; //create a vector for the baseline points std::ifstream blpt_file; //open the file containing the baseline points blpt_file.open(args["baseline"].as_string().c_str()); //get each baseline point and push it into the vector double pt; while(blpt_file>>pt){ blpts.push_back(pt); } baseline(infile, outfile, headername, blpts, MASK); } //otherwise assume the points are provided individually else{ std::vector blpts; //create a vector for the baseline points size_t npts = args["baseline"].nargs(); //get the number of baseline points for(unsigned p = 0; p < npts; p++){ //insert each point into the baseline point vector blpts.push_back(args["baseline"].as_float(p)); } baseline(infile, outfile, headername, blpts, MASK); } } else if(args["normalize"].is_set()){ //if two parameters are provided for normalization, the second is a mask value // in that case, normalize to a temporary file and then apply a mask if(args["normalize"].nargs() == 0){ std::cout<<"Calculating vector norm..."< bandlist(nb); //allocate an array to store the bands selected from the input file for (size_t b = 0; b < nb; b++) //for each band given by the user bandlist[b] = args["select"].as_float(b); //store that band in the bandlist array std::thread t1(progress_thread_envi, &ENVI); //start the progress bar thread ENVI.select(outfile, bandlist, MASK, true); //call an ENVI function to t1.join(); //wait for the progress bar thread to finish (it probably already is) } else if(args["apply-mask"].is_set()){//can mostly copy/paste this code MASK = (unsigned char*) malloc(X * Y * sizeof(unsigned char)); //allocate space for the mask memset(MASK, 255, X*Y*sizeof(unsigned char)); for(size_t m = 0; m < args["apply-mask"].nargs(); m++){ std::string maskfile = args["apply-mask"].as_string(m); //mask file name stim::image mask_image(maskfile); for(size_t xy = 0; xy < X*Y; xy++) if(!mask_image.data()[xy]) MASK[xy] = 0; } //run the function to apply the mask // this outputs a new binary file called 'outfile' with the mask applied std::cout<<"Applying mask(s) to image..."< mask(maskname); //run the function to apply the mask and save sifted spectra // this outputs a new binary file called 'outfile' (and associated header) with the mask applied. std::cout<<"Sifting to a [ P x B ] = [ "< mask(maskname); std::thread t1(progress_thread_envi, &ENVI); //start the progress bar thread //unsift the mask, creating the image outfile ENVI.unsift(outfile, mask.data(), mask.width(), mask.height(), true); t1.join(); //wait for the progress bar thread to finish (it probably already is) } else if(args["convert"].is_set()){ //get the orientation of the destination file std::string interleave_name = args["convert"].as_string(); stim::envi_header::interleaveType interleave_type; if(interleave_name == "bip") interleave_type = stim::envi_header::BIP; else if(interleave_name == "bil") interleave_type = stim::envi_header::BIL; else if(interleave_name == "bsq") interleave_type = stim::envi_header::BSQ; else{ std::cout<<"ERROR - unrecognized interleave format: "< > metrics; //create a vector to store metrics if(args["metrics"].is_num(0)){ //if the first argument of the metrics option is a number, a metric is specified metrics.resize(1); //create one metric metrics[0].resize(9, 0); //the allocate space for the entire metric for(size_t i = 0; i < 9; i++) metrics[0][i] = args["metrics"].as_float(i); //store the metric in the vector } else{ std::string metric_file = args["metrics"].as_string(0); metrics = read_metric_list(metric_file); std::cout<<"Computing "< > mu_vector = mu_table.get_vector(); //get the table contents as an STL vector for(size_t b = 0; b < B; b++){ mu[b] = mu_vector[0][b]; } } else{ t1 = std::thread(progress_thread_envi, &ENVI); //start the progress bar thread std::cout<<"Calculating mean spectrum..."< coef(args["convolve"].nargs()); //allocate an array of values to store the coefficients for(size_t c = 0; c < coef.size(); c++){ //for each coefficient std::string s = args["convolve"].as_string(c); if(s.find('/') != std::string::npos){ size_t slash = s.find('/'); double a = atof(s.substr(0, slash).c_str()); double b = atof(s.substr(slash+1, s.length() - slash+1).c_str()); std::cout< 0) N = args["sg"].as_int(0); int D = 3; if(args["sg"].nargs() > 1) D = args["sg"].as_int(1); if(N % 2 == 0 || N <= 0){ std::cout<<"ERROR: number of points in a Savitzky-Golay filter must be odd and positive"< J(N, D+1); //create the J matrix int r = -N / 2; for(int n = 0; n < N; n++){ for(int d = 0; d < D + 1; d++){ J(n, d) = pow(n + r, d); } } stim::matrix Jt = J.transpose(); //calculate the matrix transpose Jt stim::matrix JtJ = Jt * J; //multiply the transpose by J stim::matrix JtJi = JtJ; //allocate space for the matrix inverse int* piv = (int*) malloc(JtJi.rows() * sizeof(int)); //allocate space to store the LU decomposition pivot indices //LAPACKE_dgetrf(LAPACK_COL_MAJOR, (int)JtJi.rows(), (int)JtJi.cols(), JtJi.data(), (int)JtJi.rows(), piv); //use LAPACK for LU decomposition //LAPACKE_dgetri(LAPACK_COL_MAJOR, (int)JtJi.rows(), JtJi.data(), (int)JtJi.rows(), piv); //use LAPACK to solve the inverse // REPLACED THE ABOVE LAPACKE functions with new CLAPACK functions in linalg.cpp LINALG_dgetrf((int)JtJi.rows(), (int)JtJi.cols(), JtJi.data(), (int)JtJi.rows(), piv); //use LAPACK for LU decomposition LINALG_dgetri((int)JtJi.rows(), JtJi.data(), (int)JtJi.rows(), piv); //use LAPACK to solve the inverse stim::matrix C = JtJi * Jt; //calculate C std::vector coef(N); //initialize the coefficient list for(int c = 0; c < N; c++) coef[c] = C(0, c); std::thread t1(progress_thread_envi, &ENVI); //start the progress bar thread ENVI.convolve(outfile, coef, 0, ENVI.header.bands - coef.size(), coef.size()/2, MASK, true); t1.join(); } else if(args["deriv"].is_set()){ size_t d = 1; if(args["deriv"].nargs() > 0) d = args["deriv"].as_int(0); size_t order = 2; if(args["deriv"].nargs() > 1) order = args["deriv"].as_int(1); std::thread t1(progress_thread_envi, &ENVI); //start the progress bar thread ENVI.deriv(outfile, d, order, MASK, true); t1.join(); //wait for the progress bar thread to finish (it probably already is) } //do PCA and save PCs into a file else if (args["pca"].is_set()){ if(ENVI.header.interleave != stim::envi_header::BIP){ std::cout<<"Error: Calculating PCA using BSQ or BIL files is impractical. Convert to a BIP format using --convert bip."< 1) //if the number of PCs is specified N = args["project"].as_int(1); //retrieve the specified number of PCs std::ifstream csv(pcfile.c_str(), std::ios::in); //open the statistics file if(!csv){ std::cout<<"ERROR reading statistics file: "< pcs(N); if (N == B) pcs = ENVI.header.wavelength; else { for (size_t n = 0; n < N; n++) pcs[n] = (double)(n + 1); } int cuda_device = args["cuda"].as_int(); ENVI.project(outfile, mu, basis, N, pcs, MASK, cuda_device, true); t1.join(); } else if (args["inverse"].is_set()){ std::string pcfile = args["inverse"].as_string(0); //recommend user to convert file to BIP file before do rotation if (ENVI.header.interleave == stim::envi_header::BSQ || ENVI.header.interleave == stim::envi_header::BIL){ std::cout << "ERROR: Rotation is only practical for a BIP image; convert the image to BIP" << std::endl; exit(1); } unsigned long long XY = ENVI.header.lines * ENVI.header.samples; //calculate the size of the image unsigned long long M = ENVI.header.bands; //calculate the number of bands unsigned long long C = M; //set the default number of coefficients to the maximum if(args["inverse"].nargs() > 1) //if the number of coefficients to be used is specified C = args["inverse"].as_int(1); //retrieve the specified number of coefficients std::ifstream csv(pcfile.c_str(), std::ios::in); //open the statistics file std::string line, token; std::getline(csv, line); unsigned long long B = 1; //count the number of spectral components that will be produced for(unsigned long long c = 0; c < line.size(); c++){ //for each character in the first line if(line[c] == ',') B++; //count the number of commas } double* basis = (double*)malloc(sizeof(double) * B * C); //allocate space for the basis matrix double *mu = (double*)malloc(sizeof(double) * B); //allocate space for the mean feature vector std::stringstream ss(line); for(unsigned long long i = 0; i < B; i++){ //load the mean feature vector std::getline(ss, token, ','); mu[i] = atof(token.c_str()); } for (unsigned long long n = 0; n < C; n++){ //for each component std::getline(csv, line); //ss = std::stringstream(line); std::stringstream ss1(line); for (unsigned long long b = 0; b < B; b++){ //for each feature std::getline(ss1, token, ','); basis[n * B + b] = atof(token.c_str()); } } std::thread t1(progress_thread_envi, &ENVI); //start the progress bar thread ENVI.inverse(outfile, mu, basis, B, C, true); t1.join(); } else if (args["crop"].is_set()){ //initialize the values to the maximum extents unsigned long long x = 0; unsigned long long y = 0; unsigned long long b = 0; unsigned long long nx = X; unsigned long long ny = Y; unsigned long long nb = B; // Define the default behavior for the parameters // (I'm trying to be intuitive) if(args["crop"].as_string(0)[0] != '-'){ x = args["crop"].as_int(0); nx = X - x; } if(args["crop"].nargs() > 1 && args["crop"].as_string(1)[0] != '-') nx = args["crop"].as_int(1); if(args["crop"].nargs() > 2 && args["crop"].as_string(2)[0] != '-'){ y = args["crop"].as_int(2); ny = Y - y; } if(args["crop"].nargs() > 3 && args["crop"].as_string(3)[0] != '-') ny = args["crop"].as_int(3); if(args["crop"].nargs() > 4 && args["crop"].as_string(4)[0] != '-'){ if (ENVI.header.wavelength.size()) { //if wavelength values are specified in the file double wavelength = args["crop"].as_float(4); //assume that the coordinates are given in wavelength size_t low, high; ENVI.band_bounds(wavelength, low, high); b = low; } else { //if wavelength values aren't given, assume the user parameter is in bands b = args["crop"].as_int(4); } nb = B - b; } if (args["crop"].nargs() > 5 && args["crop"].as_string(5)[0] != '-') { if (ENVI.header.wavelength.size()) { //if wavelength values are specified in the file double nw = args["crop"].as_float(5); //assume that the coordinates are given in wavelength double wavelength = args["crop"].as_float(4) + nw; size_t low, high; ENVI.band_bounds(wavelength, low, high); nb = high - b; } else { //if wavelength values aren't given, assume the user parameter is in bands b = args["crop"].as_int(5); } } if(x + nx > X || y + ny > Y || b + nb > B){ std::cout<<"ERROR: specified image is out of bounds."<= 2) nx = args["subimages"].as_int(1); //get the width of the subimages size_t ny = nx; //assume that the subimages are square if(args["subimages"].nargs() >= 3) //if the image height is specified ny = args["subimages"].as_int(2); //set the height ENVI.subimages(outfile, nx, ny, MASK, true); t1.join(); //wait for the progress bar thread to finish (it probably already is) } else if(args["trim"].is_set()){ std::vector bands; //create a list of band indices to trim std::vector idx; //std::vector< std::pair > trim_ranges; //create an array of pairs double w0, w1; //create a pair of wavelength values size_t i; for(size_t p = 0; p < args["trim"].nargs(); p++){ std::string s = args["trim"].as_string(p); //get the first value as a string i = s.find('-'); //get the index of the dash (if it exists) if(i != std::string::npos && i != 0){ //if the string contains a dash (-) and that dash isn't the first value (negative number) s[i] = ' '; //replace the dash with a space std::stringstream ss(s); //create a string stream ss>>w0; //store the first and second values as a range ss>>w1; idx = ENVI.header.band_indices(w0, w1); //get the band indices in this range bands.insert(bands.end(), idx.begin(), idx.end()); //insert them into the band array } else{ //otherwise w0 = args["trim"].as_float(p); //otherwise store the single wavelength idx = ENVI.header.band_index(w0); //get the band(s) associated with that wavelength if(idx.size() == 1) //if only one band exists bands.push_back(idx[0]); //add it to the band array (if there are two indices, there isn't a single band associated with the wavelength) } } std::cout<<"Trimming "< 1){ //if one argument is provided, assume it is X px = args["combine"].as_int(1); if(px < (long long)ENVI.header.samples) py = ENVI.header.samples; //if no Y is given, and x is less than the maximum X, concatenate along Y } if(args["combine"].nargs() > 2) py = args["combine"].as_int(2); if( (-px < (long long)C.header.samples) && ( px < (long long)ENVI.header.samples) && (-py < (long long)C.header.lines) && ( py < (long long)ENVI.header.lines)){ std::cout<<"ERROR: The files to be combined are overlapping. The extent for the input image is: ["<