Detector Wavelength Ranges¶
The spectral response of a detector is a set of bands, each with a cut-on wavelength, a cut-off wavelength, and optionally a measured profile.
Overview¶
WavelengthRange is declared inside Detector_Type, so its class is Detector_Type_InlineWavelengthRange. Every concrete detector derives from Detector_Type, so the same class serves a CMOS, a CCD, and a point detector.
| Accessor | Sets |
|---|---|
setCutOn(), setCutOff() |
The bounds of the band, as PositiveFloat_Type values |
setCutOnUnit(), setCutOffUnit() |
The unit of each bound, as a UnitsLength_Type value |
setPeakWavelength() |
The wavelength of maximum response, as a plain float |
setWavelengthProfile() |
A reference to the measured response curve |
addToWavelengthRange_List() |
Adds one band; call it once per band |
Physical quantities follow one rule throughout the schema: the magnitude and the unit are separate attributes. The unit is itself a restricted type, so nm is accepted for a length and rejected for an angle, and some elements narrow the admissible units further — MirroringDevice_Type takes its angle of incidence in degrees only. An omitted unit falls back to the schema default.
Note
This example characterizes a Hamamatsu ORCA-Fusion BT over two bands. Replace the manufacturer, the model, and the bands with the values from your detector datasheet.
Describe the spectral response of a detector¶
# Step 1: Describe the detector
camera = inscoper_nbo.CMOS()
detector_id = inscoper_nbo.DetectorID_Type()
detector_id.set("Detector:1")
camera.setID(detector_id)
camera.setManufacturer("Hamamatsu")
camera.setModel("ORCA-Fusion BT")
camera.setCatalogNumber("C15440-20UP")
max_bit_depth = inscoper_nbo.DigitizerType_Type()
max_bit_depth.set("16bit")
camera.setMaxBitDepth(max_bit_depth)
noise_model = inscoper_nbo.DetectorNoiseModel_Type()
noise_model.set("Gaussian")
camera.setDetectorNoiseModel(noise_model)
camera.setReadOutNoise(0.7)
camera.setElectronicConversionFactor(0.24)
camera.setDarkCurrentRate(0.06)
camera.setQuantumEfficiency(0.8)
# Camera adds its own required attributes on top of those of Detector
camera.setPixelWidth(6.5)
camera.setPixelHeight(6.5)
camera.setPixelWellCapacity(15000)
camera.setMaximumFrameRate(89.1)
camera.setMaximumReadoutRate(100.0)
illumination = inscoper_nbo.CameraIllumination_Type()
illumination.set("Back")
camera.setIllumination(illumination)
# Step 2: Add one WavelengthRange per band the detector is characterized for
# WavelengthRange is declared inside Detector_Type, so the generated class is
# Detector_Type_InlineWavelengthRange. The same class is reused by every concrete
# detector, CMOS included.
for cut_on_value, cut_off_value, profile in [
(400.0, 550.0, "qe_400_550.csv"),
(550.0, 700.0, "qe_550_700.csv"),
]:
wavelength_range = inscoper_nbo.Detector_Type_InlineWavelengthRange()
cut_on = inscoper_nbo.PositiveFloat_Type()
cut_on.set(cut_on_value)
wavelength_range.setCutOn(cut_on)
cut_off = inscoper_nbo.PositiveFloat_Type()
cut_off.set(cut_off_value)
wavelength_range.setCutOff(cut_off)
# Wavelengths are lengths: the unit is an attribute of its own
unit = inscoper_nbo.UnitsLength_Type()
unit.set("nm")
wavelength_range.setCutOnUnit(unit)
wavelength_range.setCutOffUnit(unit)
# PeakWavelength and WavelengthProfile are required on a wavelength range
wavelength_range.setPeakWavelength((cut_on_value + cut_off_value) / 2)
wavelength_range.setWavelengthProfile(profile)
camera.addToWavelengthRange_List(wavelength_range)
# Step 3: Read the ranges back
print(f"Ranges: {len(camera.getWavelengthRange_List())}")
for wavelength_range in camera.getWavelengthRange_List():
print(
f" {wavelength_range.getCutOn().get()} - {wavelength_range.getCutOff().get()} "
f"{wavelength_range.getCutOnUnit().get()}"
)
# Step 4: Export
output_path = os.path.join(tempfile.gettempdir(), "detector.xml")
camera.toXmlFile(output_path)
print(f"Detector description written to {output_path}")
int main() {
// Step 1: Describe the detector
NBO::CMOS camera;
NBO::DetectorID_TypePtr detectorId = std::make_shared<NBO::DetectorID_Type>();
detectorId->set("Detector:1");
camera.setID(detectorId);
camera.setManufacturer("Hamamatsu");
camera.setModel("ORCA-Fusion BT");
camera.setCatalogNumber("C15440-20UP");
NBO::DigitizerType_TypePtr maxBitDepth = std::make_shared<NBO::DigitizerType_Type>();
maxBitDepth->set("16bit");
camera.setMaxBitDepth(maxBitDepth);
NBO::DetectorNoiseModel_TypePtr noiseModel = std::make_shared<NBO::DetectorNoiseModel_Type>();
noiseModel->set("Gaussian");
camera.setDetectorNoiseModel(noiseModel);
camera.setReadOutNoise(0.7f);
camera.setElectronicConversionFactor(0.24f);
camera.setDarkCurrentRate(0.06f);
camera.setQuantumEfficiency(0.8f);
// Camera adds its own required attributes on top of those of Detector
camera.setPixelWidth(6.5f);
camera.setPixelHeight(6.5f);
camera.setPixelWellCapacity(15000);
camera.setMaximumFrameRate(89.1f);
camera.setMaximumReadoutRate(100.0f);
NBO::CameraIllumination_TypePtr illumination =
std::make_shared<NBO::CameraIllumination_Type>();
illumination->set("Back");
camera.setIllumination(illumination);
// Step 2: Add one WavelengthRange per band the detector is characterized for
// WavelengthRange is declared inside Detector_Type, so in C++ it is the nested
// class Detector_Type::InlineWavelengthRange. The same class is reused by every
// concrete detector, CMOS included.
const std::vector<std::tuple<float, float, std::string>> bands = {
{400.0f, 550.0f, "qe_400_550.csv"},
{550.0f, 700.0f, "qe_550_700.csv"},
};
for (const auto &[cutOnValue, cutOffValue, profile] : bands) {
NBO::Detector_Type::InlineWavelengthRangePtr wavelengthRange =
std::make_shared<NBO::Detector_Type::InlineWavelengthRange>();
NBO::PositiveFloat_TypePtr cutOn = std::make_shared<NBO::PositiveFloat_Type>();
cutOn->set(cutOnValue);
wavelengthRange->setCutOn(cutOn);
NBO::PositiveFloat_TypePtr cutOff = std::make_shared<NBO::PositiveFloat_Type>();
cutOff->set(cutOffValue);
wavelengthRange->setCutOff(cutOff);
// Wavelengths are lengths: the unit is an attribute of its own
NBO::UnitsLength_TypePtr unit = std::make_shared<NBO::UnitsLength_Type>();
unit->set("nm");
wavelengthRange->setCutOnUnit(unit);
wavelengthRange->setCutOffUnit(unit);
// PeakWavelength and WavelengthProfile are required on a wavelength range
wavelengthRange->setPeakWavelength((cutOnValue + cutOffValue) / 2.0f);
wavelengthRange->setWavelengthProfile(profile);
camera.addToWavelengthRange_List(wavelengthRange);
}
// Step 3: Read the ranges back
std::cout << "Ranges: " << camera.getWavelengthRange_List().size() << std::endl;
for (const auto &wavelengthRange : camera.getWavelengthRange_List()) {
std::cout << " " << wavelengthRange->getCutOn()->get() << " - "
<< wavelengthRange->getCutOff()->get() << " "
<< wavelengthRange->getCutOnUnit()->get() << std::endl;
}
// Step 4: Export
std::filesystem::path outputPath = std::filesystem::temp_directory_path() / "detector.xml";
camera.toXmlFile(outputPath.string());
std::cout << "Detector description written to " << outputPath.string() << std::endl;
return 0;
}
record Band(float cutOn, float cutOff, String profile) {
}
public static void main(String[] args) {
// Step 1: Describe the detector
CMOS camera = new CMOS();
DetectorID_Type detectorId = new DetectorID_Type();
detectorId.set("Detector:1");
camera.setID(detectorId);
camera.setManufacturer("Hamamatsu");
camera.setModel("ORCA-Fusion BT");
camera.setCatalogNumber("C15440-20UP");
DigitizerType_Type maxBitDepth = new DigitizerType_Type();
maxBitDepth.set("16bit");
camera.setMaxBitDepth(maxBitDepth);
DetectorNoiseModel_Type noiseModel = new DetectorNoiseModel_Type();
noiseModel.set("Gaussian");
camera.setDetectorNoiseModel(noiseModel);
camera.setReadOutNoise(0.7f);
camera.setElectronicConversionFactor(0.24f);
camera.setDarkCurrentRate(0.06f);
camera.setQuantumEfficiency(0.8f);
// Camera adds its own required attributes on top of those of Detector
camera.setPixelWidth(6.5f);
camera.setPixelHeight(6.5f);
camera.setPixelWellCapacity(15000L);
camera.setMaximumFrameRate(89.1f);
camera.setMaximumReadoutRate(100.0f);
CameraIllumination_Type illumination = new CameraIllumination_Type();
illumination.set("Back");
camera.setIllumination(illumination);
// Step 2: Add one WavelengthRange per band the detector is characterized for
// WavelengthRange is declared inside Detector_Type, so the generated class is
// Detector_Type_InlineWavelengthRange. The same class is reused by every
// concrete detector, CMOS included.
Band[] bands = {
new Band(400.0f, 550.0f, "qe_400_550.csv"),
new Band(550.0f, 700.0f, "qe_550_700.csv"),
};
for (Band band : bands) {
Detector_Type_InlineWavelengthRange wavelengthRange =
new Detector_Type_InlineWavelengthRange();
PositiveFloat_Type cutOn = new PositiveFloat_Type();
cutOn.set(band.cutOn());
wavelengthRange.setCutOn(cutOn);
PositiveFloat_Type cutOff = new PositiveFloat_Type();
cutOff.set(band.cutOff());
wavelengthRange.setCutOff(cutOff);
// Wavelengths are lengths: the unit is an attribute of its own
UnitsLength_Type unit = new UnitsLength_Type();
unit.set("nm");
wavelengthRange.setCutOnUnit(unit);
wavelengthRange.setCutOffUnit(unit);
// PeakWavelength and WavelengthProfile are required on a wavelength range
wavelengthRange.setPeakWavelength((band.cutOn() + band.cutOff()) / 2.0f);
wavelengthRange.setWavelengthProfile(band.profile());
camera.addToWavelengthRange_List(wavelengthRange);
}
// Step 3: Read the ranges back
System.out.println("Ranges: " + camera.getWavelengthRange_List().size());
for (Detector_Type_InlineWavelengthRange wavelengthRange :
camera.getWavelengthRange_List()) {
System.out.println(" " + wavelengthRange.getCutOn().get() + " - "
+ wavelengthRange.getCutOff().get() + " "
+ wavelengthRange.getCutOnUnit().get());
}
// Step 4: Export
Path outputPath = Paths.get(System.getProperty("java.io.tmpdir"), "detector.xml");
camera.toXmlFile(outputPath.toString());
System.out.println("Detector description written to " + outputPath);
}