Source code: C++ Spectrometer Irradiation Calculation 🛠

C++ source code for calculating irradiation information and the daily light integrals from spectrometer derived datasets. These classes build upon and utilizes the previously posted classes. It is a rather specialized set of source code for those with access to a spectrometer or raw irradiance data. As such, part of the value presented here is within the methods utilized to generate the irradiation information.
For more information on spectrometer captured irradiance, see the following thread:


Also, see other source code using the tag: https://overgrow.com/tags/sourcecode

The classes presented here are able to read spectrometer data, dark values, and calibration data from data files that follow the Stellarnet SpectraWiz file formats (which are straightforward) and calculate various useful data such as PAR and DLI.

This is for the DIY types that have some software background. Questions/comment are great but I’d like to ask forum members to avoid the “what’s this good for” type questions within this thread to avoid clutter.

There are several classes and header files, as follows:
spectrum.cpp

//
// spectrum.cpp
// Version timestamp: 9-28-2019, 10:57 AM
//
// Attribution : Copyright (c) 2018 Northern_Loki (sha256::6F290BF833967127BE26C92C8F6B1C1A3949C55A7EABCEF3ECC785CD2D38D30D)
// License is granted under the Creative Commons Attribution-ShareAlike 4.0 International.  https://creativecommons.org/licenses/by-sa/4.0/
//
# include "spectrum.h"

//Radiant flux 	W(J/s) The amount of radiant energy emitted, transmitted or received per unit time 
//Radiant flux density 	(W / m^2) Radiant flux per unit area of a surface traversed by the radiation 
//Irradiance(or emittance) 	(W / m^2) Radiant flux density incident on(or emitted by) a surface 
//Spectral irradiance(or emittance) (W m^2 / µm) The radiant flux density incident on(or emitted by) a surface per unit wavelength interval 
//Radiant intensity (W/sr) Radiant flux emanating from a surface per unit solid angle 
//Radiance (W m^2/sr1) The radiant flux density emanating from a surface per unit solid angle 
//Spectral radiance (W / m^2 / sr µm) The radiant flux density emanating from a surface per unit solid angle per unit wavelength interval 
//Photon flux density (µmol / m^2 s) Number of micromoles of photons emitted, transmitted or received per unit area per unit time(usually within a specified wavelength such as the photosynthetically active region(400–700 nm)) 
//Emissivity – The ratio of the thermally generated radiance emitted by a body to the radiance that would be emitted by a black body(or perfect emitter) at the same temperature 

/* Constructor. */
spectrum::spectrum()
{
}

/* Constructor. */
/* filename is the path of the CSV formatted spectrometer data. */
/* cal_filename is the path of the ICF formatted spectrometer calibration data. */
/* dark_filename is the path of the CSV formatted dark spectrometer data. */
spectrum::spectrum(std::string filename, std::string cal_filename, std::string dark_filename)
{
//	float ICF_integration_time = 46.0;
//	float Sample_integration_time = 12.0;
    
    float ICF_integration_time = 44.0;
    float Sample_integration_time = 44.0;
    
	std::string RQE_filename("../Spectrums/RQE.csv"); /* Relative quantum efficiency */
    std::string BLHF_filename("../Spectrums/BLHF.csv"); /* Blue light hazard function */
    std::string AHF_filename("../Spectrums/AHF.csv"); /* Aphakic hazard function */
    std::string RTHF_filename("../Spectrums/RTHF.csv"); /* Retinal thermal hazard function */
	std::string sigmaR_filename("../Spectrums/sigmaR.csv"); /* Red phytochrome conversion */
	std::string sigmaFR_filename("../Spectrums/sigmaFR.csv"); /* Far red phytochrome conversion */
    
	csv(filename);
	cal_icf(cal_filename);
	dark_csv(dark_filename);
	RQE(RQE_filename);
    BLHF(BLHF_filename);
    AHF(BLHF_filename);
    RTHF(RTHF_filename);
	sigmaR(sigmaR_filename);
	sigmaFR(sigmaFR_filename);
    
	spectrum_calculate(ICF_integration_time, Sample_integration_time);
}


spectrum::~spectrum()
{
}

void spectrum::spectrum_calculate(float ICF_integration_time, float Sample_integration_time)
{
    /* Derives umols of irradiant photons from irradiant watts */
	double NAhc = (1.0 / ((NA) * (SPEED_LIGHT) * (PLANKS_CONSTANT))) / 1000.0;
    
    /* Derives radiance from irradiance using the maximum solid angle of 0.1 radians, for estimating maximum eye biological exposure */
    /* See, http://www.olino.org/blog/us/articles/2011/09/13/blue-light-hazard-for-the-human-eye */
    /* See, https://www.icnirp.org/cms/upload/publications/ICNIRPVisible_Infrared2013.pdf */
    double L = (4 / (M_PI)) * (1 / 0.1);
    
	this->sigmaR_energy.value = 0.0;
	this->sigmaFR_energy.value = 0.0;
    
	/* Calculate the actual reading by removing the dark offset. */
	/* Dark offset is the spectrometer reading with no incident light energy on the sensor. */
	spectral_data corrected_spectra = this->csv.ordered_spectra;
	spectral_data dark_spectra = this->dark_csv.ordered_spectra;
	corrected_spectra -= dark_spectra;			
    
	/* Correct the readings according to the calibration factors. */
	float ICF_calibration_ratio = ICF_integration_time / Sample_integration_time;
	spectral_data icf_spectra = this->cal_icf.ordered_spectra;
	corrected_spectra *= ICF_calibration_ratio;
	corrected_spectra *= icf_spectra;	
    
	/* Calculate the spectral irradiance. */
	float previous_wavelength, previous_intensity;
	for (std::pair<float, float> element : corrected_spectra)
	{
		static bool initial = true;
        
		if (initial)
		{
			previous_wavelength = element.first;
			previous_intensity = element.second;
			initial = false;
			continue;
		}
        
		float delta_wavelength = element.first - previous_wavelength; 
		float irradiant_energy = delta_wavelength * ((previous_intensity + element.second) / 2);
        
		irradiance_energy.insert(std::pair<float, float>(previous_wavelength + (delta_wavelength / 2), irradiant_energy)); 
        
		if ((element.first <= 700) && (element.first >= 400))
		{
			irradiance += delta_wavelength * ((previous_intensity + element.second) / 2);
		}
        
		previous_wavelength = element.first;
		previous_intensity = element.second;	
	}
    
    
	/* Calculate irradiation PAR in umols */	
	for (std::pair<float, float> element : irradiance_energy)
	{	
		float local_PAR = element.first * element.second * (NAhc);
		par_energy.insert(std::pair<float, float>(element.first, local_PAR)); 
    	
		float local_ypf = local_PAR * this->RQE.get_intensity(element.first);
		ypf_energy.insert(std::pair<float, float>(element.first, local_ypf)); 
    	
    	float local_blhf = L * element.second * this->BLHF.get_intensity(element.first);
    	float local_ahf = L * element.second * this->AHF.get_intensity(element.first);
    	float local_rthf = L * element.second * this->RTHF.get_intensity(element.first);
        
		if ((element.first <= this->par_regions_bugbee.spectrum_max) && (element.first >= this->par_regions_bugbee.spectrum_min))
		{
			/* Standard PAR regions. */
			int i = 0;
			while (!this->par_regions.region[i].name.empty())
			{
				if ((element.first >= par_regions.region[i].starting_wavelength) && (element.first < par_regions.region[i].ending_wavelength))
				{
					this->par_regions.region[i].value += local_PAR;
					break;
				} 
				i++;
			}
            
			/* Standard YPF regions. */
			i = 0;
			while (!this->ypf_regions.region[i].name.empty())
			{
				if ((element.first >= ypf_regions.region[i].starting_wavelength) && (element.first < ypf_regions.region[i].ending_wavelength))
				{
					this->ypf_regions.region[i].value += local_ypf;
					break;
				} 
				i++;
			}
            
			/* Bugbee defined PAR regions. */
			i = 0;
			while (!this->par_regions_bugbee.region[i].name.empty())
			{
				if ((element.first >= par_regions_bugbee.region[i].starting_wavelength) && (element.first < par_regions_bugbee.region[i].ending_wavelength))
				{
					this->par_regions_bugbee.region[i].value += local_PAR;
					break;
				} 
				i++;
			}
            
			/* YPF over Bugbee defined PAR regions. */
			i = 0;
			while (!this->ypf_regions_bugbee.region[i].name.empty())
			{
				if ((element.first >= ypf_regions_bugbee.region[i].starting_wavelength) && (element.first < ypf_regions_bugbee.region[i].ending_wavelength))
				{
					this->ypf_regions_bugbee.region[i].value += local_ypf;
					break;
				} 
				i++;
			}
            
			if ((element.first <= this->par_regions.spectrum_max) && (element.first >= this->par_regions.spectrum_min))
			{
				this->par += local_PAR;
			}
            
			if ((element.first <= this->ypf_regions.spectrum_max) && (element.first >= this->ypf_regions.spectrum_min))
			{
				this->ypf += local_ypf;
			}
            
			if ((element.first <= this->sigmaFR_energy.spectrum_max) && (element.first >= this->sigmaFR_energy.spectrum_min))
			{
				this->sigmaR_energy.value += local_PAR * this->sigmaR.get_intensity(element.first);
				this->sigmaFR_energy.value += local_PAR * this->sigmaFR.get_intensity(element.first);
			}
    		
    		if ((element.first <= this->blhf_regions.spectrum_max) && (element.first >= this->blhf_regions.spectrum_min))
    		{
        		this->blhf += local_blhf;
    		}
    		
    		if ((element.first <= this->ahf_regions.spectrum_max) && (element.first >= this->ahf_regions.spectrum_min))
    		{
        		this->ahf += local_ahf;
    		}
    		
    		if ((element.first <= this->rthf_regions.spectrum_max) && (element.first >= this->rthf_regions.spectrum_min))
    		{
        		this->rthf += local_rthf;
    		}
            
			/* Total PAR from 287-850nm */
			this->par_287_850 += local_PAR;
			/* Total ypf from 287-850nm */
			this->ypf_287_850 += local_ypf;
		}
	}
}

spectrum spectrum::set(std::string filename, std::string cal_filename, std::string dark_filename)
{
	float ICF_integration_time = 46.0;
	float Sample_integration_time = 12.0;
    
	std::string RQE_filename("../Spectrums/RQE.csv");
	std::string sigmaR_filename("../Spectrums/sigmaR.csv");
	std::string sigmaFR_filename("../Spectrums/sigmaFR.csv");
    
	csv(filename);
	cal_icf(cal_filename);
	dark_csv(dark_filename);
	RQE(RQE_filename);
	sigmaR(sigmaR_filename);
	sigmaFR(sigmaFR_filename);
    
	spectrum_calculate(ICF_integration_time, Sample_integration_time);
    
	return (*this);
}


spectrum spectrum::operator()(std::string filename, std::string cal_filename, std::string dark_filename)
{

	this->set(filename, cal_filename, dark_filename);
    
	return (*this);
}


double spectrum::calculate_rayleigh_scattering(double wavelength)
{

}

/* Erythema action spectrum */
/* Komhyr, W.D. and L. Machta, "The relative response of erythema",
 * in: The Perturbed Troposphere of 1990 and 2020 Vol. IV. CIAP, Dept. of Transportation, 
 * Washington. DC., 1973. 
 * # Parameterization by A.E.S. Green, T. Sawada, and E.P Shettle, 
 * The middle ultraviolet reaching the ground, Photochemistry and Photobiology, 19, 251-259, 1974.
 * */
/* https://www.esrl.noaa.gov/gmd/grad/antuv/docs/version2/descVersion2Database3.html */
/* Integration range: 286 - 400 nm */
/* Output units : µW/cm2 */
double spectrum::calculate_komhyr_action_spectra(double wavelength)
{
	double exp1 = exp((wavelength - 296.5) / 2.692);
	double exp2 = exp((wavelength - 311.4) / 3.13);
	double exp3 = (1.0 + exp1) * (1.0 + exp1);
    
	double action = (0.04485 /  (1 + exp2)) +  ((4 * 0.9949*exp1) / exp3);
    
	return (action);
}

/* Erythema action spectrum */
/* B.L. Diffey, "A comparison of dosimeters used for solar ultraviolet radiometry", Photochemistry and Photobiology, 46, 55-60, 1987. */
/* https://www.esrl.noaa.gov/gmd/grad/antuv/docs/version2/descVersion2Database3.html */
/* Integration range: 286 - 400 nm */
/* Output units : µW/cm2 */
double spectrum::calculate_diffey_action_spectra(double wavelength)
{

}

/* Erythema action spectrum */
/* A.F. McKinlay, A.F. and B.L. Diffey, "A reference action spectrum for ultraviolet induced erythema in human skin", CIE Research Note, 6(1), 17-22, 1987 */
/* https://www.esrl.noaa.gov/gmd/grad/antuv/docs/version2/descVersion2Database3.html */
/* Integration range: 286 - 400 nm */
/* Output units : UV Index, see http://uv.biospherical.com/Solar_Index_Guide.pdf */
double spectrum::calculate_mckinlay_action_spectra(double wavelength)
{
	double action;
	if (wavelength < 298.0)
	{
		action = 1.0;
	}
	else if (wavelength < 328.0)
	{
		action = pow(10, (0.094 * (298.0 - wavelength)));
	}
	else
	{
		action = pow(10, (0.015 * (139.0 - wavelength)));
	}
    
    
	return (action);
}

/* Action spectrum for growth responses of plants */
/* S. D. Flint and M. M. Caldwell
 * "A biological spectral weighting function for ozone depletion research with higher plants", Physiologia Plantarum, 2003 */
/* https://www.esrl.noaa.gov/gmd/grad/antuv/docs/version2/descVersion2Database3.html */
/* Integration range: 286 - 390 nm. Wavelength in nm. */
/* Output units : µW/cm2 */
double spectrum::calculate_flint_action_spectra(double wavelength)
{
	double exp1 = -1.0 * exp((0.1703411)*(wavelength - 307.867) / 1.15);
	double exp2 = 4.688272 * exp(exp1);
    
	double action = exp(exp2 + ((390.0 - wavelength) / 121.7557 - 4.183832));
    
	return (action);
}

/* Generalized plant action spectrum  */
/* M.M. Caldwell,
 * "Solar UV irradiation and the growth and development of higher plants",Photophysiology, 
 * edited by A.C. Giese, Volume 6, Chapter 4, pp. 131 - 177, 1971 */
/* https://www.esrl.noaa.gov/gmd/grad/antuv/docs/version2/descVersion2Database3.html */
/* Integration range: 286 - 313 nm */
/* Output units : µW/cm2 */
double spectrum::calculate_caldwell_action_spectra(double wavelength)
{
	double exp1 = exp((300.0 - wavelength) / 31.08);
	double pow2 = 1.0 - ((wavelength / 313.3) * (wavelength / 313.3));
    
	double action = 2.618*exp1*pow2;
    
	return (action);
}

The “RQE.csv”, “sigmaR.csv”, and “sigmaFR.csv” are generated files for calculating YPF and the red/far-red ratios. If you need these, PM your email address and request these files. Or, if you need example spectrum files, as well.

CC BY-SA 4.0

4 Likes

spectrum.h

//
// spectrum.h
// Version timestamp: 9-28-2019, 10:58 AM
//
// Attribution : Copyright (c) 2018 Northern_Loki (sha256::6F290BF833967127BE26C92C8F6B1C1A3949C55A7EABCEF3ECC785CD2D38D30D)
// License is granted under the Creative Commons Attribution-ShareAlike 4.0 International.  https://creativecommons.org/licenses/by-sa/4.0/
//
# pragma once
# include <math.h>
# include "Conversion_Constants.h"
# include "Parse_Spectrum_CSV.h"
# include "Parse_Calibration_ICF.h"

typedef struct Spectrum_Region
{
	std::string name;
	float starting_wavelength;
	float ending_wavelength;
	float value;
} Spectrum_Region_t, *Spectrum_Region_p;

typedef struct PAR_Regions
{
	const float spectrum_min = 400.0;
	const float spectrum_max = 700.0;
	Spectrum_Region_t region[4] = 
	{
		{ "Blue", 400.0, 500.0, 0.0 }, 
		{ "Green", 500.0, 600.0, 0.0 }, 
		{ "Red", 600.0, 700.0, 0.0 }, 
		{ "", 0.0, 0.0, 0.0 }
	}; 
} PAR_Regions_t, *PAR_Regions_p;

typedef struct PAR_Regions_Bugbee
{
	const float spectrum_min = 287.0;
	const float spectrum_max = 850.0;
	Spectrum_Region_t region[7] = 
	{
		{ "UVB", 287.0, 320.0, 0.0 }, 
		{ "UVA", 320.0, 400, 0.0 }, 
		{ "Violet_Blue", 400.0, 475.0, 0.0 }, 
		{ "Cyan_Green", 475.0, 550.0, 0.0 }, 
		{ "Green_Yellow_Red", 550.0, 700.0, 0.0 }, 
		{ "FarRed_NearIR", 700.0, 850.0, 0.0 }, 
		{ "", 0.0, 0.0, 0.0 }
	}; 
} PAR_Regions_Bugbee_t, *PAR_Regions_Bugbee_p;

typedef struct YPF_Regions
{
	const float spectrum_min = 360.0;
	const float spectrum_max = 760.0;
	Spectrum_Region_t region[4] = 
	{
		{ "Blue", 400.0, 500.0, 0.0 }, 
		{ "Green", 500.0, 600.0, 0.0 }, 
		{ "Red", 600.0, 700.0, 0.0 }, 
		{ "", 0.0, 0.0, 0.0 }
	}; 
} YPF_Regions_t, *YPF_Regions_p;

typedef struct YPF_Regions_Bugbee
{
	const float spectrum_min = 287.0;
	const float spectrum_max = 850.0;
	Spectrum_Region_t region[7] = 
	{
		{ "UVB", 287.0, 320.0, 0.0 }, 
		{ "UVA", 320.0, 400, 0.0 }, 
		{ "Violet_Blue", 400.0, 475.0, 0.0 }, 
		{ "Cyan_Green", 475.0, 550.0, 0.0 }, 
		{ "Green_Yellow_Red", 550.0, 700.0, 0.0 }, 
		{ "FarRed_NearIR", 700.0, 850.0, 0.0 }, 
		{ "", 0.0, 0.0, 0.0 }
	}; 
} YPF_Regions_Bugbee_t, *YPF_Regions_Bugbee_p;

typedef struct PPS_Regions
{
	const float spectrum_min = 300.0;
	const float spectrum_max = 800.0;
	float value;
} PPS_Regions_t, *PPS_Regions_p;

typedef struct BLHF_Regions
{
    const float spectrum_min = 300.0;
    const float spectrum_max = 1400.0;
    float value;
} BLHF_Regions_t, *BLHF_Regions_p;

typedef struct AHF_Regions
{
    const float spectrum_min = 300.0;
    const float spectrum_max = 1400.0;
    float value;
} AHF_Regions_t, *AHF_Regions_p;

typedef struct RTHF_Regions
{
    const float spectrum_min = 300.0;
    const float spectrum_max = 1400.0;
    float value;
} RTHF_Regions_t, *RTHF_Regions_p;

class spectrum
{
public:
	spectrum();
	spectrum(std::string filename, std::string cal_filename, std::string dark_filename);
	~spectrum();
    
	spectrum set(std::string filename, std::string cal_filename, std::string dark_filename);
	//void spectrum_calculate(float ICF_integration_time, float Sample_integration_time, const Parse_Spectrum_CSV *csv, Parse_Calibration_ICF *cal_icf, Parse_Spectrum_CSV *dark_csv, Parse_Spectrum_CSV *RQE, Parse_Spectrum_CSV *sigmaR, Parse_Spectrum_CSV *sigmaFR);
	void spectrum_calculate(float ICF_integration_time, float Sample_integration_time);
	double calculate_rayleigh_scattering(double wavelength);
	double calculate_komhyr_action_spectra(double wavelength);
	double calculate_diffey_action_spectra(double wavelength);
	double calculate_mckinlay_action_spectra(double wavelength);
	double calculate_flint_action_spectra(double wavelength);
	double calculate_caldwell_action_spectra(double wavelength);
    
	virtual spectrum operator()(std::string filename, std::string cal_filename, std::string dark_filename);
    
	spectral_data irradiance_energy;
	spectral_data par_energy;
	spectral_data ypf_energy;
	float irradiance = 0.0;
	float par = 0.0;
	float par_287_850 = 0.0;
	PAR_Regions_t par_regions;
	PAR_Regions_Bugbee_t par_regions_bugbee;
	PPS_Regions_t sigmaR_energy;
	PPS_Regions_t sigmaFR_energy;
    BLHF_Regions_t blhf_regions;
    AHF_Regions_t ahf_regions;
    RTHF_Regions_t rthf_regions;
    float blhf = 0.0;
    float ahf = 0.0;
    float rthf = 0.0;
	float ypf = 0.0;
	float ypf_287_850 = 0.0;
	YPF_Regions_t ypf_regions;
	YPF_Regions_Bugbee_t ypf_regions_bugbee;
	Parse_Spectrum_CSV csv;
	Parse_Calibration_ICF cal_icf;
	Parse_Spectrum_CSV dark_csv;
	Parse_Spectrum_CSV RQE; /* Relative quantum efficiency */
        Parse_Spectrum_CSV BLHF; /* Blue light hazard function */
        Parse_Spectrum_CSV AHF; /* Aphakic hazard function */    
        Parse_Spectrum_CSV RTHF; /* Retinal thermal hazard function */        
	Parse_Spectrum_CSV sigmaR;
	Parse_Spectrum_CSV sigmaFR;
};

Parse_Calibration_ICF.cpp

//
// Parse_Calibration_ICF.cpp
// Version timestamp: 12-11-2018, 4:26 PM
//
// Attribution : Copyright (c) 2018 Northern_Loki (sha256::6F290BF833967127BE26C92C8F6B1C1A3949C55A7EABCEF3ECC785CD2D38D30D)
// License is granted under the Creative Commons Attribution-ShareAlike 4.0 International.  https://creativecommons.org/licenses/by-sa/4.0/
//
# include "Parse_Calibration_ICF.h"

/* This class is a simple class to read and parse spectrometer ICF calibration files. */
/* First column is the wavelength. */
/* Second column is the data associated with the wavelength column in an ordered fashion. */
/* That is, each row of the data in the remaining columns is associated with the wavelength on the same row. */
/* For example, an ICF containing the wavelength and the "counts" file may be formatted as follows: */
/* , Live Spectrum 8 / 25 / 2018 9 : 11 : 41 PM, */
/* Wavelength[nm], Intensity, */
/* " File: SW1.icf
215.17  0.0000E+000
215.71  0.0000E+000
216.25  0.0000E+000
216.79  0.0000E+000
217.32  0.0000E+000
217.86  0.0000E+000
218.40  0.0000E+000
218.94  0.0000E+000
219.48  0.0000E+000
220.02  0.0000E+000 */
/* ... continued ... */
/* This is the Spectrawiz ICF format for calibration data. */

/* Constructor. */
Parse_Calibration_ICF::Parse_Calibration_ICF()
{
    
}

/* filepath is the operating system path to the CSV file. */
Parse_Calibration_ICF::Parse_Calibration_ICF(std::string &filepath)
{
	this->set(filepath);
}


Parse_Calibration_ICF::~Parse_Calibration_ICF()
{
}

void Parse_Calibration_ICF::operator()(std::string &filepath)
{
	this->set(filepath);
}

void Parse_Calibration_ICF::set(std::string &filepath)
{
	if (!filepath.empty())
	{
		boost::escaped_list_separator<char> separators('\\', ' ', '\"');
		boost::char_separator<char> sep(" ");
    
		std::ifstream csv_input(filepath.c_str());
    
		if (csv_input.is_open())
		{
			std::vector<std::string> vec;
			std::string line;
			std::string buffer;
			bool inside_quotes(false);
			size_t last_quote(0);
			float first_value = 0.0;
			float second_value = 0.0;
        
			/* Read and parse the CSV file. */
			while (getline(csv_input, buffer))
			{
				line.append(buffer);

				boost::tokenizer< boost::char_separator<char> > tok(line, sep);
				vec.assign(tok.begin(), tok.end());
				if (vec.size() < 2) continue;
            
				this->ordered_spectra.insert(vec[0], vec[1]);
            
				line.clear(); // clear here, next check could fail

			}
        
			csv_input.close();
		}
		else
		{
			std::cout << "Could not open file :" << filepath << std::endl;
		}
	}
	else
	{
		std::cout << "Filepath NULL." << std::endl;
	}
}

/* Get the itensity value at specified wavelength. */
float Parse_Calibration_ICF::get_intensity(float wavelength)
{
	float result = 0.0;

	result = this->ordered_spectra.get_intensity(wavelength);
    
	return (result);
}

/* Find the maximum intensity and return the intensity and wavelength pair. */
std::pair<float, float> Parse_Calibration_ICF::get_max_intensity()
{
	return (this->max());
}

/* Find the minimum intensity and return the intensity and wavelength pair. */
std::pair<float, float> Parse_Calibration_ICF::get_min_intensity()
{
	return (this->min());
}

/* Return the wavelength range contained in the dataset. */
std::pair<float, float> Parse_Calibration_ICF::get_range()
{
	return (this->range());
}

/* Return the number of elements in the dataset. */
unsigned int Parse_Calibration_ICF::size()
{
	return (this->ordered_spectra.size());
}

/* Iterator support. */
std::map<float, float>::iterator Parse_Calibration_ICF::begin()
{
	return (this->ordered_spectra.ordered_spectra.begin());
}

/* Iterator support. */
std::map<float, float>::iterator Parse_Calibration_ICF::end()
{
	return (this->ordered_spectra.ordered_spectra.end());
}

Parse_Spectrum_CSV.h

//
// Parse_Spectrum_CSV.h
// Version timestamp: 12-11-2018, 4:27 PM
//
// Attribution : Copyright (c) 2018 Northern_Loki (sha256::6F290BF833967127BE26C92C8F6B1C1A3949C55A7EABCEF3ECC785CD2D38D30D)
// License is granted under the Creative Commons Attribution-ShareAlike 4.0 International.  https://creativecommons.org/licenses/by-sa/4.0/
//
# pragma once
# include <fstream>      
# include <vector>
# include <string>
# include <map>
# include <iostream>  
# include <boost/tokenizer.hpp>
# include <boost/unordered_map.hpp>
# include "spectral_data.h"

class Parse_Spectrum_CSV : public spectral_data
{
public:
	Parse_Spectrum_CSV();
	Parse_Spectrum_CSV(std::string &filepath);
	~Parse_Spectrum_CSV();
	void set(std::string &filepath);
	float get_intensity(float wavelength);
	std::pair<float, float> get_max_intensity();
	std::pair<float, float> get_min_intensity();
	std::pair<float, float> get_range();
	unsigned int size();
	std::map<float, float>::iterator begin();
	std::map<float, float>::iterator end();
	virtual void operator()(std::string &filepath);
	
	spectral_data ordered_spectra;
};

CC BY-SA 4.0

2 Likes

Parse_Calibration_ICF.h

//
// Parse_Calibration_ICF.h
// Version timestamp: 12-11-2018, 4:27 PM
//
// Attribution : Copyright (c) 2018 Northern_Loki (sha256::6F290BF833967127BE26C92C8F6B1C1A3949C55A7EABCEF3ECC785CD2D38D30D)
// License is granted under the Creative Commons Attribution-ShareAlike 4.0 International.  https://creativecommons.org/licenses/by-sa/4.0/
//
# pragma once
# include <fstream>      
# include <vector>
# include <string>
# include <map>
# include <iostream>  
# include <boost/tokenizer.hpp>
# include <boost/unordered_map.hpp>
# include "spectral_data.h"

class Parse_Calibration_ICF : public spectral_data
{
public:
    Parse_Calibration_ICF();
    Parse_Calibration_ICF(std::string &filepath);
    ~Parse_Calibration_ICF();
	void set(std::string &filepath);
    float get_intensity(float wavelength);
    std::pair<float, float> get_max_intensity();
    std::pair<float, float> get_min_intensity();
    std::pair<float, float> get_range();
    unsigned int size();
    std::map<float, float>::iterator begin();
    std::map<float, float>::iterator end();
	virtual void operator()(std::string &filepath);
    
    spectral_data ordered_spectra;
};

Parse_Spectrum_CSV.cpp

//
// Parse_Spectrum_CSV.cpp
// Version timestamp: 12-11-2018, 4:26 PM
//
// Attribution : Copyright (c) 2018 Northern_Loki (sha256::6F290BF833967127BE26C92C8F6B1C1A3949C55A7EABCEF3ECC785CD2D38D30D)
// License is granted under the Creative Commons Attribution-ShareAlike 4.0 International.  https://creativecommons.org/licenses/by-sa/4.0/
//
# include "Parse_Spectrum_CSV.h"


/* This class is a simple class to read and parse spectrum data as delimeted Microsoft Excel CSV formatted files. */
/* First column is the wavelength. */
/* Second column is the data associated with the wavelength column in an ordered fashion. */
/* That is, each row of the data in the remaining columns is associated with the wavelength on the same row. */
/* For example, a csv containing the wavelength and the "counts" file may be formatted as follows: */
/* , Live Spectrum 8 / 25 / 2018 9 : 11 : 41 PM, */
/* Wavelength[nm], Intensity, */
/* 222.167, 5.4694, */
/* 222.599, 8.6868, */
/* 223.031, 5.8164, */
/* 223.464, 2.8493, */
/* 223.896, 1.9258, */
/* ... continued ... */
/* This is the Spectrawiz CSV export format for spectral captures. */

/* Constructor. */
Parse_Spectrum_CSV::Parse_Spectrum_CSV()
{
    
}

/* filepath is the operating system path to the CSV file. */
Parse_Spectrum_CSV::Parse_Spectrum_CSV(std::string &filepath)
{
	this->set(filepath);
}


Parse_Spectrum_CSV::~Parse_Spectrum_CSV()
{
}

void Parse_Spectrum_CSV::operator()(std::string &filepath)
{
	this->set(filepath);
}

void Parse_Spectrum_CSV::set(std::string &filepath)
{
	if (!filepath.empty())
	{
		boost::escaped_list_separator<char> separators('\\', ',', '\"');
		std::ifstream csv_input(filepath.c_str());
    
		if (csv_input.is_open())
		{
			std::vector<std::string> vec;
			std::string line;
			std::string buffer;
			bool inside_quotes(false);
			size_t last_quote(0);
			float first_value = 0.0;
			float second_value = 0.0;
        
			/* Read and parse the CSV file. */
			while (getline(csv_input, buffer))
			{
				line.append(buffer);

				boost::tokenizer< boost::escaped_list_separator<char> > tok(line, separators);
				vec.assign(tok.begin(), tok.end());
            
				this->ordered_spectra.insert(vec[0], vec[1]);
            
				line.clear(); 
			}
        
			csv_input.close();
		}
		else
		{
			std::cout << "Could not open file :" << filepath << std::endl;
		}
	}
	else
	{
		std::cout << "Filepath NULL." << std::endl;
	}
}

/* Get the itensity value at specified wavelength. */
float Parse_Spectrum_CSV::get_intensity(float wavelength)
{
	float result = 0.0;

	result = this->ordered_spectra.get_intensity(wavelength);
    
	return (result);
}

/* Find the maximum intensity and return the intensity and wavelength pair. */
std::pair<float, float> Parse_Spectrum_CSV::get_max_intensity()
{
	return (this->max());
}

/* Find the minimum intensity and return the intensity and wavelength pair. */
std::pair<float, float> Parse_Spectrum_CSV::get_min_intensity()
{
	return (this->min());
}

/* Return the wavelength range contained in the dataset. */
std::pair<float, float> Parse_Spectrum_CSV::get_range()
{
	return (this->range());
}

/* Return the number of elements in the dataset. */
unsigned int Parse_Spectrum_CSV::size()
{
	return (this->ordered_spectra.size());
}

/* Iterator support */
std::map<float, float>::iterator Parse_Spectrum_CSV::begin()
{
	return (this->ordered_spectra.ordered_spectra.begin());
}

/* Iterator support */
std::map<float, float>::iterator Parse_Spectrum_CSV::end()
{
	return (this->ordered_spectra.ordered_spectra.end());
}

CC BY-SA 4.0

3 Likes

spectral_data.cpp

//
// spectral_data.cpp
// Version timestamp: 9-28-2019, 10:56 AM
//
// Attribution : Copyright (c) 2018 Northern_Loki (sha256::6F290BF833967127BE26C92C8F6B1C1A3949C55A7EABCEF3ECC785CD2D38D30D)
// License is granted under the Creative Commons Attribution-ShareAlike 4.0 International.  https://creativecommons.org/licenses/by-sa/4.0/
//
# include "spectral_data.h"

/* Constructor */
spectral_data::spectral_data()
{
}

/* Copy constructor */
spectral_data::spectral_data(const spectral_data &p)
{
	this->ordered_spectra = p.ordered_spectra;
}

/* Copy constructor */
spectral_data::spectral_data(std::map<float, float> map)
{
	this->ordered_spectra = map;
}

spectral_data::~spectral_data()
{
}


bool spectral_data::map_value_comparator(std::pair<float, float> i, std::pair<float, float> j)
{
	if (isnanf(i.second)) i.second = 0.0;
	if (isnanf(j.second)) j.second = 0.0;
	return (i.second < j.second);
}

/* Find the maximum intensity and return the intensity and wavelength pair. */
std::pair<float, float> spectral_data::max()
{
	return (*std::max_element(this->ordered_spectra.begin(), this->ordered_spectra.end(), spectral_data::map_value_comparator));
}

/* Find the maximum intensity in a range and return the intensity and wavelength pair. */
std::pair<float, float> spectral_data::max(float wavelength_min, float wavelength_max)
{
	std::map<float, float>::iterator low = this->ordered_spectra.lower_bound(wavelength_min);
	std::map<float, float>::iterator high = this->ordered_spectra.upper_bound(wavelength_max);
    
	return (*std::max_element(low, high, spectral_data::map_value_comparator));
}

/* Find the minimum intensity and return the intensity and wavelength pair. */
std::pair<float, float> spectral_data::min()
{
	return (*std::min_element(this->ordered_spectra.begin(), this->ordered_spectra.end(), spectral_data::map_value_comparator));
}

/* Find the minimum intensity in a range and return the intensity and wavelength pair. */
std::pair<float, float> spectral_data::min(float wavelength_min, float wavelength_max)
{
	std::map<float, float>::iterator low = this->ordered_spectra.lower_bound(wavelength_min);
	std::map<float, float>::iterator high = this->ordered_spectra.upper_bound(wavelength_max);
    
	return (*std::min_element(low, high, spectral_data::map_value_comparator));
}

/* Return the wavelength range. */
std::pair<float, float> spectral_data::range()
{
	return (std::pair<float, float> (this->ordered_spectra.begin()->first, this->ordered_spectra.rbegin()->first));
}

/* Return the number of elements. */
unsigned int spectral_data::size()
{
	return (this->ordered_spectra.size());
}

void spectral_data::insert(std::pair<float, float> pair)
{
	this->ordered_spectra.insert(pair);
}

void spectral_data::insert(std::string wavelength, std::string intensity)
{
	float first_value = atof(wavelength.c_str());
    
	if (first_value > 0)
	{
		float second_value = atof(intensity.c_str());
		this->ordered_spectra.insert(std::pair<float, float> (first_value, second_value));
	}
}

std::pair<float, float> spectral_data::current(float wavelength)
{
    float current_value = 0.0;
    
    try
    {
        current_value = this->ordered_spectra.at(wavelength);
    }
    catch(const std::out_of_range& oor) 
    {
        wavelength = 0.0;
    }
    
    return (std::pair<float, float> (wavelength, current_value));
}

std::pair<float, float> spectral_data::lower_bound(float wavelength)
{
	std::map<float, float>::iterator low = this->ordered_spectra.lower_bound(wavelength);
    
	if (low == this->ordered_spectra.end()) 
	{
		// No values.
	}
	else if (low == this->ordered_spectra.begin()) 
	{
		// No other value prior to this value.
	}
	else 
	{
		low = std::prev(low);
	}
    
	return (std::pair<float, float> (low->first, low->second));
}

std::pair<float, float> spectral_data::upper_bound(float wavelength)
{
	std::map<float, float>::iterator high = this->ordered_spectra.upper_bound(wavelength);
    
	return (std::pair<float, float> (high->first, high->second));
}

std::map<float, float>::iterator spectral_data::begin()
{
	return (this->ordered_spectra.begin());
}

std::map<float, float>::iterator spectral_data::end()
{
	return (this->ordered_spectra.end());
}

/* The following returns the data value for an input wavelength. The value is */
/* interpolated using a straight line approximation if an exact wavelength is not in the dataset. */
float spectral_data::get_intensity(float wavelength)
{
	float result = 0.0;

	if (this->ordered_spectra.size() > 0)
	{
		std::pair<float, float> current, low, high, prev;
        
    	/* Find the current value, if it exists. */
    	current = this->current(wavelength);
   	
    	if (current.first == wavelength)
    	{
        	result = current.second;
    	}
    	else
    	{
        	/* Otherwise, we attempt to interpolate the value. */
        	
        	/* Find nearest lower bound. */
        	low = this->lower_bound(wavelength);
            
        	/* Find nearest upper bound. */
        	high = this->upper_bound(wavelength);

        	if (low.first == wavelength)
        	{
            	result = low.second;
        	}
        	else if (high.first == wavelength)
        	{
            	result = high.second;
        	}
        	else if (high.first == this->ordered_spectra.end()->first)
        	{
            	/* end of data */
        	}
        	else
        	{
            	/* Striaght line interpolation. */
            	double d_x = high.first  - low.first; 
            	double d_y = high.second  - low.second;
            	double slope = (d_y / d_x);
            	double b = high.second - slope*high.first;
        
            	result = slope*(wavelength) + b;	
        	}
    	} 	

	}
    
	return (result);
}

spectral_data spectral_data::operator-(spectral_data temp2)
{
	spectral_data data; 
	for (std::pair<float, float> element : this->ordered_spectra)
	{
		element.second = element.second - temp2.get_intensity(element.first);
		data.insert(element);
	}
    
	return (data);
}

spectral_data spectral_data::operator-=(spectral_data temp2)
{
	spectral_data data; 
	for (std::pair<float, float> element : this->ordered_spectra)
	{
		element.second = element.second - temp2.get_intensity(element.first);
		data.insert(element);
	}
    
	this->ordered_spectra = data.ordered_spectra;
    
	return (*this);
}

spectral_data spectral_data::operator*(float temp2)
{
	spectral_data data; 
	for (std::pair<float, float> element : this->ordered_spectra)
	{
		element.second = element.second * temp2;
		data.insert(element);
	}
    
	return (data);
}


spectral_data spectral_data::operator*=(spectral_data &temp2)
{
	spectral_data data; 
	for (std::pair<float, float> element : this->ordered_spectra)
	{
		element.second = element.second * temp2.get_intensity(element.first);
		data.insert(element);
	}
    
	this->ordered_spectra = data.ordered_spectra;
    
	return (data);
}

spectral_data spectral_data::operator*=(float temp2)
{
	spectral_data data; 
	for (std::pair<float, float> element : this->ordered_spectra)
	{
		element.second = element.second * temp2;
		data.insert(element);
	}
    
	this->ordered_spectra = data.ordered_spectra;
    
	return (data);
}

spectral_data spectral_data::operator=(spectral_data temp2)
{
	spectral_data data; 
        
	data.ordered_spectra = temp2.ordered_spectra;

	return (data);
}

spectral_data.h

//
// spectral_data.h
// Version timestamp: 9-28-2019, 10:58 AM
//
// Attribution : Copyright (c) 2018 Northern_Loki (sha256::6F290BF833967127BE26C92C8F6B1C1A3949C55A7EABCEF3ECC785CD2D38D30D)
// License is granted under the Creative Commons Attribution-ShareAlike 4.0 International.  https://creativecommons.org/licenses/by-sa/4.0/
//
# pragma once     
# include <vector>
# include <string>
# include <map>
# include <math.h>
# include <boost/unordered_map.hpp>

class spectral_data
{
public:
	spectral_data();
	spectral_data(const spectral_data &p);
	spectral_data(std::map<float, float> map);
	~spectral_data();
    
	static bool map_value_comparator(std::pair<float, float> i, std::pair<float, float> j);
	std::pair<float, float> max();
	std::pair<float, float> max(float wavelength_min, float wavelength_max);
	std::pair<float, float> min();
	std::pair<float, float> min(float wavelength_min, float wavelength_max);
	std::pair<float, float> range();
	unsigned int size();
	void insert(std::pair<float, float> pair);
	void insert(std::string wavelength, std::string intensity);
    std::pair<float, float> current(float wavelength);
	std::pair<float, float> lower_bound(float wavelength);
	std::pair<float, float> upper_bound(float wavelength);
	float get_intensity(float wavelength);
	std::map<float, float>::iterator begin();
	std::map<float, float>::iterator end();
    
	virtual spectral_data operator-(spectral_data temp2);
	virtual spectral_data operator=(spectral_data temp2);
	virtual spectral_data operator-=(spectral_data temp2);
	virtual spectral_data operator*(float temp2);
	virtual spectral_data operator*=(spectral_data &temp2);
	virtual spectral_data operator*=(float temp2);
    
	std::map<float, float> ordered_spectra;
	boost::unordered::unordered_map<float, float> spectra;
};


CC BY-SA 4.0

2 Likes

Example

The following serves as an example utilization of these classes to generate PAR, DLI, and other metrics from spectrometer data:

	std::string filename("../Spectrums/601C_Full_1000_Spectrum.csv");
	std::string cal_filename("../Spectrums/SW1.icf");
	std::string dark_filename("../Spectrums/601C_Full_0_Spectrum.csv");
	std::string RQE_filename("../Spectrums/RQE.csv");
	std::string sigmaR_filename("../Spectrums/sigmaR.csv");
	std::string sigmaFR_filename("../Spectrums/sigmaFR.csv");
	
	spectrum spectral_results(filename, cal_filename, dark_filename);
	
	int i = 0;
	float par_total = 0.0; 
	while (!spectral_results.par_regions.region[i].name.empty())
	{
		float par_value_percent = (spectral_results.par_regions.region[i].value / spectral_results.par) * 100.0;
		par_total +=  par_value_percent;
		std::cout << 
			spectral_results.par_regions.region[i].name << 
			"(" << spectral_results.par_regions.region[i].starting_wavelength << " - " << spectral_results.par_regions.region[i].ending_wavelength << "nm)" <<
			": " <<  par_value_percent << 
			"%" << std::endl;
		i++;
	}
	std::cout << "Total :" << par_total << "%" << std::endl << std::endl;
	
	i = 0;
	par_total = 0.0; 
	while (!spectral_results.par_regions_bugbee.region[i].name.empty())
	{
		float par_value_percent = (spectral_results.par_regions_bugbee.region[i].value / spectral_results.par_287_850) * 100.0;
		par_total +=  par_value_percent;
		std::cout << 
			spectral_results.par_regions_bugbee.region[i].name << 
			"(" << spectral_results.par_regions_bugbee.region[i].starting_wavelength << "-" << spectral_results.par_regions_bugbee.region[i].ending_wavelength << "nm)" <<
			": " << par_value_percent << 
			"%" << std::endl;
		i++;
	}
	std::cout << "Total :" << par_total << "%" << std::endl << std::endl;
	
	std::cout << " Irradiant Energy : " << spectral_results.irradiance << std::endl;
	std::cout << " PAR Energy (400-700nm): " << spectral_results.par << std::endl;
	std::cout << " PAR Energy (287-850nm): " << spectral_results.par_287_850 << std::endl;
	/* DLI is the number of PAR delivered over the daytime. It is an integral measured in Mmols */
	float DLI = spectral_results.par * 12.0 * (SECONDS_HOUR) / 1000000.0;
	std::cout << " DLI : " << DLI << std::endl;
	std::cout << " YPF Energy (360-760nm): " << spectral_results.ypf << std::endl;
	std::cout << " YPF/PFD: " << spectral_results.ypf / spectral_results.par << std::endl;
	std::cout << " PPS (phytochrome photostationary state): " << spectral_results.sigmaR_energy.value / (spectral_results.sigmaR_energy.value + spectral_results.sigmaFR_energy.value) << std::endl;

	std::cout << " PAR Max :" << spectral_results.par_energy.max().second << " @" << spectral_results.par_energy.max().first << " nm." << std::endl;
	std::cout << " PAR Min :" << spectral_results.par_energy.min().second << " @" << spectral_results.par_energy.min().first << " nm." << std::endl;

Produces the following from a dataset captured for a Heliospectra LX601C:

Blue: 19.8775%
Green: 13.4355%
Red: 66.687%
.
UVB: 0.00293306 % 
UVA: 0.0858871 % 
Violet-Blue: 15.5883 % 
Cyan-Green: 6.27506 % 
Green-Yellow-Red: 62.8103 % 
Far Red / Near IR: 15.2375 % 
.
Irradiant Energy : 154.758
PAR Energy (400-700nm): 760.196
PAR Energy (287-850nm): 897.795
DLI : 32.8405
YPF Energy (360-760nm): 692.932
YPF/PFD: 0.911517
PPS (phytochrome photostationary state): 0.821386
PAR Max :7.84745 @661.626 nm.
PAR Min :-0.00012346 @316.63 nm.

Produces the following from a dataset captured from a Fluence UV only fixture:

Blue: 99.7512%
Green: 0.219891%
Red: 0.0289442%
.
UVB: 0.00750941 % 
UVA: 15.3058 % 
Violet-Blue: 84.3777 % 
Cyan-Green: 0.225106 % 
Green-Yellow-Red: 0.100399 % 
Far Red / Near IR: -0.0163641 % 
.
Irradiant Energy : 29.4916
PAR Energy (400-700nm): 101.639
PAR Energy (287-850nm): 119.995
DLI : 4.39082
YPF Energy (360-760nm): 83.7059
YPF/PFD: 0.823558
PPS (phytochrome photostationary state): 0.548607
PAR Max :2.51962 @407.851 nm.
PAR Min :-0.00877298 @808.617 nm.

Produces the following from a dataset captured for a Fluence far-red only fixture:

Blue: 3.43184%
Green: 3.84571%
Red: 92.7224%
.
UVB: 0.00348822 %
UVA: 0.111233 %
Violet-Blue: 0.160706 %
Cyan-Green: 0.123364 %
Green-Yellow-Red: 5.59957 %
Far Red / Near IR: 94.0016 %
.
Irradiant Energy : 1.48668
PAR Energy (400-700nm): 8.27002
PAR Energy (287-850nm): 140.559
DLI : 0.357265
YPF Energy (360-760nm): 25.4375
YPF/PFD: 3.07587
PPS (phytochrome photostationary state): 0.152282
PAR Max :1.90153 @735.555 nm.
PAR Min :-0.00112761 @375.859 nm.

CC BY-SA 4.0

2 Likes

Example

A second example, illustrates plotting a simple text based plot of the calculated PAR data across the spectra:

/* Plot spectra. Stride = the number of wavelength to skip, e.g. every 5th wavelength is plotted with the integrated power for stride = 5. */
/* min and max wavelength is the plot range. */
void plot_spectrum(spectrum *spectral_results, unsigned int min_wavelength, unsigned int max_wavelength, unsigned int wavelength_stride)
{
	int wavelength = 0;
	float total_par = 0.0;
	float integrate_range = 0.0;
	std::cout << "********************************************************************************************************************" << std::endl;
	std::cout << "*********************************** Irradiance Spectrum ************************************************************" << std::endl;
	std::cout << "********************************************************************************************************************" << std::endl;
    
	if ((spectral_results != NULL) && (wavelength_stride > 0))
	{
		std::pair<float, float> wavelength_range = spectral_results->par_energy.range();
    
		/* Sanity, clamp wavelength range to available spectral range. */
		if (wavelength_range.first > min_wavelength) min_wavelength = wavelength_range.first;
		if (wavelength_range.second < max_wavelength) max_wavelength = wavelength_range.second;
    
		std::cout << "Calculating : " << std::endl;
		double distmax = spectral_results->par_energy.max(min_wavelength, max_wavelength).second * wavelength_stride;
		double distmin = spectral_results->par_energy.min(min_wavelength, max_wavelength).second; 
		
		int distmax_ceil = ceil(distmax);
		int distmin_floor = floor(distmin);	
		double chart_x_quant = 120.0 / (distmax_ceil - distmin_floor);

    
		/* Plot the chart */
		for (std::pair<float, float> element : spectral_results->par_energy)
		{
			wavelength++;
			double distance = element.second;

			if (element.first >= min_wavelength && element.first <= max_wavelength)
			{
				integrate_range += distance;
				if (wavelength % wavelength_stride == 0)
				{
                    
					if ((element.first >= 400) && (element.first <= 700))
					{
						total_par += integrate_range;
					}

					std::cout << FG_DEFAULT <<  boost::format("%8.2f") % element.first << FG_YELLOW << " : ";
                    
					if (element.first <= 850)
					{
						if (element.first > 700)
						{
							std::cout << FG_MAGENTA;
						}
						else if (element.first > 614)
						{
							std::cout << FG_RED;
						}
						else if (element.first > 589)
						{
							std::cout << FG_ORANGE;
						}
						else if (element.first > 574)
						{
							std::cout << FG_YELLOW;
						}
						else if (element.first > 564)
						{
							std::cout << FG_LIGHT_YELLOW;
						}
						else if (element.first > 550)
						{
							std::cout << FG_LIGHT_GREEN;
						}
						else if (element.first > 517)
						{
							std::cout << FG_GREEN;
						}
						else if (element.first > 476)
						{
							std::cout << FG_CYAN;
						}
						else if (element.first > 400)
						{
							std::cout << FG_LIGHT_BLUE;
						}
						else if (element.first > 321)
						{
							std::cout << FG_BLUE;
						}		
						else if (element.first > 287)
						{
							std::cout << FG_BLUE;
						}		
					}
     
					int dots = ceil(((integrate_range) - distmin)*chart_x_quant);
					std::cout << std::string(dots, '*') << FG_YELLOW << " : " << FG_DEFAULT << integrate_range << FG_DEFAULT;
            
					std::cout << std::endl;
					integrate_range = 0.0;
				}
				else
				{

				}
			}
    
		}
		std::cout << "Total PAR : " <<  total_par << std::endl;	
		std::cout << std::endl;	
	}
	else
	{
		std::cout << "Plot NULL spectra." << std::endl;
	}
}

Produces the following from a dataset captured for a Heliospectra LX601C:

Produces the following from a dataset captured for a Fluence UV only fixture:

Produces the following from a dataset captured for a Fluence far-red only fixture:

CC BY-SA 4.0

3 Likes

Example

Here is another example based on a slightly modified version of the previous example. In this case, we zoom into the far red region to get a better look:

   std::string filename("601C_Full_1000_Spectrum.csv");
   std::string cal_filename("SW1.icf");
   std::string dark_filename("601C_Full_0_Spectrum.csv");
   std::string RQE_filename("RQE.csv");
   std::string sigmaR_filename("sigmaR.csv");
   std::string sigmaFR_filename("sigmaFR.csv");
   /* Execute */	
   spectrum spectral_results(filename, cal_filename, dark_filename);
   /* Plot the PAR data. */
    int wavelength = 0;
    int wavelength_stride = 2;
    int min_wavelength = 650;
    int max_wavelength = 800;
    float total_par = 0.0;
    float integrate_range = 0.0;
    std::cout << "********************************************************************************************************************" << std::endl;
    std::cout << "*********************************** Irradiance Spectrum ************************************************************" << std::endl;
    std::cout << "********************************************************************************************************************" << std::endl;
    
    std::cout << "Calculating : " << std::endl;
    double distmax = spectral_results.par_energy.max(min_wavelength, max_wavelength).second;
    double distmin = spectral_results.par_energy.min().second;

    int distmax_ceil = ceil(distmax);
    int distmin_floor = floor(distmin);	
    double chart_x_quant = 96.0 / (distmax_ceil - distmin_floor);

    
    /* Plot the chart */
    for (std::pair<float, float> element : spectral_results.par_energy)
    {
	    wavelength++;
        double distance = element.second;

        if (element.first >= min_wavelength && element.first <= max_wavelength)
        {
            integrate_range += distance;
	        if (wavelength % wavelength_stride == 0)
            {
                    
                if ((element.first >= 400) && (element.first <= 700))
                {
                    total_par += integrate_range;
                }

                std::cout << FG_DEFAULT <<  boost::format("%8.2f") % element.first << " : ";
                    
                if (element.first <= 850)
                {
                    if (element.first > 700)
                    {
                        std::cout << FG_MAGENTA;
                    }
                    else if (element.first > 614)
                    {
                        std::cout << FG_RED;
                    }
                    else if (element.first > 589)
                    {
                        std::cout << FG_ORANGE;
                    }
                    else if (element.first > 574)
                    {
                        std::cout << FG_YELLOW;
                    }
                    else if (element.first > 564)
                    {
                        std::cout << FG_LIGHT_YELLOW;
                    }
                    else if (element.first > 550)
                    {
                        std::cout << FG_LIGHT_GREEN;
                    }
                    else if (element.first > 517)
                    {
                        std::cout << FG_GREEN;
                    }
                    else if (element.first > 476)
                    {
                        std::cout << FG_CYAN;
                    }
                    else if (element.first > 400)
                    {
                        std::cout << FG_LIGHT_BLUE;
                    }
                    else if (element.first > 321)
                    {
                        std::cout << FG_BLUE;
                    }		
                    else if (element.first > 287)
                    {
                        std::cout << FG_BLUE;
                    }		
                }

                        
                std::cout << std::string(round(((integrate_range) - distmin)*chart_x_quant), '*') << FG_DEFAULT << " : " << FG_ORANGE << integrate_range << FG_DEFAULT;
            

                std::cout << std::endl;
                integrate_range = 0.0;
            }
            else
            {

            }
        }
    
    }
    std::cout << "Total PAR : " <<  total_par << std::endl;	
    std::cout << std::endl;	

Produces the following for a Fluence far-red only fixture:

CC BY-SA 4.0

3 Likes

An example that plots the conversion efficiency of light striking a plant leaf using the classes in this thread. This is called relative quantum efficiency (RQE).

/* YPF */
/* Plot parameters. Stride = the number of wavelength to skip, e.g. every 5th wavelength is plotted with the integrated power for stride = 5. */
/* min and max wavelength is the plot range. */
void plot_ypf_spectrum(spectrum *spectral_results, unsigned int min_wavelength, unsigned int max_wavelength, unsigned int wavelength_stride)
{
    
	int wavelength = 0;
	float total_par = 0.0;
	float integrate_range = 0.0;
	float ypf_range = 0.0;
	std::cout << "********************************************************************************************************************" << std::endl;
	std::cout << "*********************************** Irradiance Spectrum ************************************************************" << std::endl;
	std::cout << "********************************************************************************************************************" << std::endl;
    
	if ((spectral_results != NULL) && (wavelength_stride > 0))
	{
		std::pair<float, float> wavelength_range = spectral_results->par_energy.range();
    
		/* Sanity, clamp wavelength range to available spectral range. */
		if (wavelength_range.first > min_wavelength) min_wavelength = wavelength_range.first;
		if (wavelength_range.second < max_wavelength) max_wavelength = wavelength_range.second;
        
		std::cout << "Calculating : " << std::endl;
		double distmax = spectral_results->par_energy.max(min_wavelength, max_wavelength).second * wavelength_stride;
		double distmin = spectral_results->par_energy.min(min_wavelength, max_wavelength).second; 
		
		int distmax_ceil = ceil(distmax);
		int distmin_floor = floor(distmin);	
		double chart_x_quant = 120.0 / (distmax_ceil - distmin_floor);

    
		/* Plot the chart */
		std::map<float, float>::iterator ypf_interator = spectral_results->ypf_energy.ordered_spectra.begin();
		for (std::pair<float, float> element : spectral_results->par_energy)
		{
			wavelength++;
			double distance = element.second;
			double ypf_distance = ypf_interator->second;
			ypf_interator++;

			if (element.first >= min_wavelength && element.first <= max_wavelength)
			{
				integrate_range += distance;
				ypf_range += ypf_distance;
                
				if (wavelength % wavelength_stride == 0)
				{
                    
					if ((element.first >= 400) && (element.first <= 700))
					{
						total_par += integrate_range;
					}

					std::cout << FG_DEFAULT <<  boost::format("%8.2f") % element.first << FG_YELLOW << " : ";
                    
					if (element.first <= 850)
					{
						if (element.first > 700)
						{
							std::cout << FG_MAGENTA;
						}
						else if (element.first > 614)
						{
							std::cout << FG_RED;
						}
						else if (element.first > 589)
						{
							std::cout << FG_ORANGE;
						}
						else if (element.first > 574)
						{
							std::cout << FG_YELLOW;
						}
						else if (element.first > 564)
						{
							std::cout << FG_LIGHT_YELLOW;
						}
						else if (element.first > 550)
						{
							std::cout << FG_LIGHT_GREEN;
						}
						else if (element.first > 517)
						{
							std::cout << FG_GREEN;
						}
						else if (element.first > 476)
						{
							std::cout << FG_CYAN;
						}
						else if (element.first > 400)
						{
							std::cout << FG_LIGHT_BLUE;
						}
						else if (element.first > 321)
						{
							std::cout << FG_BLUE;
						}		
						else if (element.first > 287)
						{
							std::cout << FG_BLUE;
						}		
					}
     
					std::ostringstream os;
					os << std::string(round(((ypf_range) - distmin)*chart_x_quant) - 1, '*').c_str() << 'Y' << FG_DEFAULT;
					os << std::string(round(((integrate_range - ypf_range) - distmin)*chart_x_quant) - 1, ' ').c_str() << '*' << FG_DEFAULT;
					std::cout << os.str() << std::endl;
                    
					integrate_range = 0.0;
					ypf_range = 0.0;
				}
				else
				{

				}
			}
    
		}
		std::cout << "Total PAR : " <<  total_par << std::endl;	
		std::cout << std::endl;	
	}
	else
	{
		std::cout << "Plot NULL spectra." << std::endl;
	}
        
}

This produces a plot of PAR (white asterisks) as compared to the PAR adjusted for the relative quantum efficiency of plant conversion of light energy (the colored asterisks). The RQE values are used when calculating YPF. PAR in this case is not truncated to 400-700nm but instead is calculated for each wavelength bin across the captured spectrum.

CC BY-SA 4.0

5 Likes

Reference data files for use with this source (convert or copy to csv format):

RQE.pdf (75.3 KB)
sigmaR.pdf (77.7 KB)
sigmaFR.pdf (78.7 KB)

RQE = relative quantum efficiency
sigmaR = red phytochrome absorption state
sigmaFR = far red phytochrome absorption state

These are used for the calculation of YPF and PSS.

Calibration file for the spectrometer I have (remove the .pdf extension for the .icf file which is plain text):

SW1.icf.pdf (42.4 KB)

3 Likes