Skip to content

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);
    }