// SPDX-FileCopyrightText: 2024 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only #include #include #include "NetworkAddressConvert.h" #include "JFJochCompressor.h" // For ZSTD_USE_JFJOCH_RLE #include "DiffractionExperiment.h" #include "CUDAWrapper.h" #include "JFJochException.h" #include "RawToConvertedGeometry.h" #include "../include/spdlog/fmt/fmt.h" #include "GitInfo.h" using namespace std::literals::chrono_literals; #define check_max(param, val, max) if ((val) > (max)) throw JFJochException(JFJochExceptionCategory::InputParameterAboveMax, param) #define check_min(param, val, min) if ((val) < (min)) throw JFJochException(JFJochExceptionCategory::InputParameterBelowMin, param) #define check_finite(param, val) if (!std::isfinite(val)) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, param) DiffractionExperiment::DiffractionExperiment() : DiffractionExperiment(DetJF4M()) {} DiffractionExperiment::DiffractionExperiment(const DetectorSetup& det_setup) : detector(det_setup) { ndatastreams = 1; series_id = 0; mode = DetectorMode::Conversion; image_format_settings.Conv(); summation = 1; cpu_summation = false; } // setter functions DiffractionExperiment &DiffractionExperiment::Detector(const DetectorSetup &input) { detector = input; auto settings = detector.GetDefaultSettings(); if (settings) detector_settings = *settings; return *this; } DiffractionExperiment &DiffractionExperiment::Mode(DetectorMode input) { mode = input; if (input == DetectorMode::Conversion) Conversion(); if (input == DetectorMode::Raw) Raw(); return *this; } DiffractionExperiment &DiffractionExperiment::DataStreams(int64_t input) { check_max("Number of data streams", input, 16); check_min("Number of data streams", input, 1); ndatastreams = input; return *this; } DiffractionExperiment &DiffractionExperiment::ImagesPerTrigger(int64_t input) { dataset.ImagesPerTrigger(input); return *this; } DiffractionExperiment &DiffractionExperiment::NumTriggers(int64_t input) { dataset.NumTriggers(input); return *this; } DiffractionExperiment &DiffractionExperiment::FrameTime(std::chrono::microseconds in_frame_time, std::chrono::microseconds in_count_time) { DetectorSettings tmp = detector_settings; if (in_count_time.count() == 0) tmp.FrameTime(in_frame_time); else tmp.FrameTime(in_frame_time, in_count_time); ImportDetectorSettings(tmp); return *this; } DiffractionExperiment &DiffractionExperiment::PedestalG0Frames(int64_t input) { detector_settings.PedestalG0Frames(input); return *this; } DiffractionExperiment &DiffractionExperiment::PedestalG1Frames(int64_t input) { detector_settings.PedestalG1Frames(input); return *this; } DiffractionExperiment &DiffractionExperiment::PedestalG2Frames(int64_t input) { detector_settings.PedestalG2Frames(input); return *this; } DiffractionExperiment &DiffractionExperiment::IncidentEnergy_keV(float input) { dataset.PhotonEnergy_keV(input); return *this; } DiffractionExperiment &DiffractionExperiment::BeamX_pxl(float input) { dataset.BeamX_pxl(input); return *this; } DiffractionExperiment &DiffractionExperiment::BeamY_pxl(float input) { dataset.BeamY_pxl(input); return *this; } DiffractionExperiment &DiffractionExperiment::DetectorDistance_mm(float input) { dataset.DetectorDistance_mm(input); return *this; } DiffractionExperiment &DiffractionExperiment::FilePrefix(std::string input) { dataset.FilePrefix(std::move(input)); return *this; } DiffractionExperiment &DiffractionExperiment::UseInternalPacketGenerator(bool input) { detector_settings.InternalGeneratorEnable(input); return *this; } DiffractionExperiment &DiffractionExperiment::MaskModuleEdges(bool input) { image_format_settings.MaskModuleEdges(input); return *this; } DiffractionExperiment &DiffractionExperiment::Compression(CompressionAlgorithm input) { dataset.Compression(input); return *this; } DiffractionExperiment &DiffractionExperiment::MaskChipEdges(bool input) { image_format_settings.MaskChipEdges(input); return *this; } DiffractionExperiment &DiffractionExperiment::QRangeForAzimInt_recipA(float low, float high) { az_integration_settings.QRange_recipA(low, high); return *this; } DiffractionExperiment& DiffractionExperiment::BkgEstimateQRange_recipA(float low, float high) { az_integration_settings.BkgEstimateQRange_recipA(low, high); return *this; } DiffractionExperiment& DiffractionExperiment::QSpacingForAzimInt_recipA(float input) { az_integration_settings.QSpacing_recipA(input); return *this; } DiffractionExperiment &DiffractionExperiment::SetUnitCell(const std::optional &cell) { dataset.SetUnitCell(cell); return *this; } DiffractionExperiment &DiffractionExperiment::ZMQPreviewPeriod(const std::optional &input) { if (input.has_value()) { check_min("Preview image generation period", input.value().count(), 0); } zmq_preview_period = input; return *this; } DiffractionExperiment &DiffractionExperiment::SpaceGroupNumber(std::optional input) { dataset.SpaceGroupNumber(input); return *this; } DiffractionExperiment &DiffractionExperiment::StorageCells(int64_t input) { detector_settings.StorageCells(input); return *this; } DiffractionExperiment &DiffractionExperiment::StorageCellStart(int64_t input) { detector_settings.StorageCellStart(input); return *this; } DiffractionExperiment &DiffractionExperiment::SampleName(const std::string &input) { dataset.SampleName(input); return *this; } DiffractionExperiment &DiffractionExperiment::OverwriteExistingFiles(bool input) { file_writer.OverwriteExistingFiles(input); return *this; } // getter functions int64_t DiffractionExperiment::GetNumTriggers() const { if (IsPedestalRun()) return 1; else if (IsPulsedSource()) // For pulsed source summation happens over multiple triggers return dataset.GetNumTriggers() * GetSummation(); else return dataset.GetNumTriggers(); } DetectorMode DiffractionExperiment::GetDetectorMode() const { return mode; } std::chrono::microseconds DiffractionExperiment::GetFrameTime() const { if ((GetDetectorType() != DetectorType::JUNGFRAU) && dataset.GetImageTime().has_value()) return dataset.GetImageTime().value(); switch (GetDetectorMode()) { case DetectorMode::PedestalG1: case DetectorMode::PedestalG2: return detector_settings.GetFrameTimePedestalG1G2(); default: return detector_settings.GetFrameTime(); } } std::chrono::microseconds DiffractionExperiment::GetDetectorPeriod() const { // Without storage cells - this is just frame time if (GetStorageCellNumber() == 1) return GetFrameTime(); // With storage cells // Do 100 Hz repetition for pedestal G1/G2 // Do 2 kHz repetition for conversion/raw/pedestal G0 switch (GetDetectorMode()) { case DetectorMode::PedestalG1: case DetectorMode::PedestalG2: return detector_settings.GetFrameTimePedestalG1G2(); default: return std::chrono::microseconds(MIN_FRAME_TIME_JUNGFRAU_FULL_SPEED_IN_US) * GetStorageCellNumber(); } } std::chrono::microseconds DiffractionExperiment::GetImageTime() const { switch (GetDetectorMode()) { case DetectorMode::PedestalG1: case DetectorMode::PedestalG2: return detector_settings.GetFrameTimePedestalG1G2(); default: return GetFrameTime() * GetSummation(); } } int64_t DiffractionExperiment::GetImageNum() const { return IsPedestalRun() ? 0 : (GetFrameNum() / GetSummation()); } int64_t DiffractionExperiment::GetFrameNum() const { return GetFrameNumPerTrigger() * GetNumTriggers(); } int64_t DiffractionExperiment::GetFrameNumPerTrigger() const { switch (GetDetectorMode()) { case DetectorMode::PedestalG0: return GetPedestalG0Frames() * GetStorageCellNumber(); case DetectorMode::PedestalG1: return GetPedestalG1Frames() * GetStorageCellNumber(); case DetectorMode::PedestalG2: return GetPedestalG2Frames()* GetStorageCellNumber(); default: if (GetStorageCellNumber() > 1) return GetStorageCellNumber(); else if (IsPulsedSource()) return 1; else return dataset.GetImageNumPerTrigger() * GetSummation(); } } std::chrono::microseconds DiffractionExperiment::GetFrameCountTime() const { if ((GetDetectorType() != DetectorType::JUNGFRAU) && dataset.GetImageTime().has_value() && !detector_settings.GetCountTime().has_value()) return dataset.GetImageTime().value() - detector.GetReadOutTime(); return detector_settings.GetCountTime() .value_or(detector_settings.GetFrameTime() - detector.GetReadOutTime()); } bool DiffractionExperiment::GetFrameCountTimeAuto() const { return detector_settings.GetCountTime().has_value(); } std::chrono::microseconds DiffractionExperiment::GetImageCountTime() const { return GetFrameCountTime() * GetSummation(); } int64_t DiffractionExperiment::GetPedestalG0Frames() const { if (GetDetectorType() != DetectorType::JUNGFRAU) return 0; return detector_settings.GetPedestalG0Frames(); } int64_t DiffractionExperiment::GetPedestalG1Frames() const { if (GetDetectorType() != DetectorType::JUNGFRAU) return 0; return detector_settings.GetPedestalG1Frames(); } int64_t DiffractionExperiment::GetPedestalG2Frames() const { if (GetDetectorType() != DetectorType::JUNGFRAU) return 0; return detector_settings.GetPedestalG2Frames(); } float DiffractionExperiment::GetIncidentEnergy_keV() const { return dataset.GetPhotonEnergy_keV(); } float DiffractionExperiment::GetWavelength_A() const { if (instrument.IsElectronSource()) { const double hc = WVL_1A_IN_KEV; const double me_c2 = 511; // in keV return static_cast(hc / sqrt(dataset.GetPhotonEnergy_keV() * (dataset.GetPhotonEnergy_keV() + 2 * me_c2))); } return WVL_1A_IN_KEV / dataset.GetPhotonEnergy_keV(); } float DiffractionExperiment::GetBeamX_pxl() const { return dataset.GetBeamX_pxl(); } float DiffractionExperiment::GetBeamY_pxl() const { return dataset.GetBeamY_pxl(); } float DiffractionExperiment::GetDetectorDistance_mm() const { return dataset.GetDetectorDistance_mm(); } Coord DiffractionExperiment::GetScatteringVector() const { return dataset.GetScatteringVector(); } std::string DiffractionExperiment::GetFilePrefix() const { return dataset.GetFilePrefix(); } CompressionAlgorithm DiffractionExperiment::GetCompressionAlgorithm() const { return dataset.GetCompressionAlgorithm(); } int64_t DiffractionExperiment::GetByteDepthImage() const { if (detector.GetBitDepthImage()) return detector.GetBitDepthImage().value() / 8; if (IsCPUSummation()) return 4; auto bit_depth_image = image_format_settings.GetBitDepthImage(); if (!bit_depth_image.has_value()) { if (GetBitDepthReadout() == 32) return 4; if (GetBitDepthReadout() == 8) return 1; return (GetSummation() > 2) ? 4 : 2; } return bit_depth_image.value() / 8; } bool DiffractionExperiment::IsPixelSigned() const { auto pixel_signed = image_format_settings.IsPixelSigned(); if (!pixel_signed.has_value()) return IsJungfrauConvPhotonCnt() && (GetDetectorType() == DetectorType::JUNGFRAU); else return pixel_signed.value(); } int64_t DiffractionExperiment::GetDataStreamsNum() const { return std::min(ndatastreams, detector.GetModulesNum()); } int64_t DiffractionExperiment::GetModulesNum(uint16_t data_stream) const { if (data_stream >= GetDataStreamsNum()) throw JFJochException(JFJochExceptionCategory::ArrayOutOfBounds, "Non existing data stream"); return (detector.GetModulesNum() + (GetDataStreamsNum() - 1) - data_stream) / GetDataStreamsNum(); } int64_t DiffractionExperiment::GetModulesNum() const { return detector.GetModulesNum(); } int64_t DiffractionExperiment::GetFirstModuleOfDataStream(uint16_t data_stream) const { if (data_stream >= GetDataStreamsNum()) throw JFJochException(JFJochExceptionCategory::ArrayOutOfBounds, "Non exisiting data stream"); int64_t val = 0; for (int i = 0; i < data_stream; i++) val += GetModulesNum(i); return val; } int64_t DiffractionExperiment::GetMaxCompressedSize() const { return MaxCompressedSize(GetCompressionAlgorithm(), GetPixelsNum(), GetByteDepthImage()); } int64_t DiffractionExperiment::GetPixelsNum() const { return GetXPixelsNum() * GetYPixelsNum(); } int64_t DiffractionExperiment::GetXPixelsNum() const { return detector.GetGeometry().GetWidth(IsGeometryTransformed()); } int64_t DiffractionExperiment::GetYPixelsNum() const { return detector.GetGeometry().GetHeight(IsGeometryTransformed()); } int64_t DiffractionExperiment::GetPixelsNumConv() const { return GetXPixelsNumConv() * GetYPixelsNumConv(); } int64_t DiffractionExperiment::GetXPixelsNumConv() const { return detector.GetGeometry().GetWidth(true); } int64_t DiffractionExperiment::GetYPixelsNumConv() const { return detector.GetGeometry().GetHeight(true); } int64_t DiffractionExperiment::GetPixel0OfModuleConv(uint16_t module_number) const { return detector.GetGeometry().GetPixel0(module_number, true); } int64_t DiffractionExperiment::GetOverflow() const { switch (GetByteDepthImage()) { case 4: return IsPixelSigned() ? static_cast(INT32_MAX) : static_cast(UINT32_MAX); case 2: return IsPixelSigned() ? INT16_MAX : UINT16_MAX; case 1: return IsPixelSigned() ? INT8_MAX : UINT8_MAX; default: throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Pixel depth unsupported"); } } int64_t DiffractionExperiment::GetSaturationLimit() const { auto sat = detector.GetSaturationLimit(); auto overflow = GetOverflow(); if (!sat) return overflow; return std::min(sat.value(), overflow); } int64_t DiffractionExperiment::GetUnderflow() const { if (!IsPixelSigned()) return -1; switch (GetByteDepthImage()) { case 1: return INT8_MIN; case 2: return INT16_MIN; case 4: return INT32_MIN; default: throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Pixel depth unsupported"); } } std::optional DiffractionExperiment::GetZMQPreviewPeriod() const { if (IsPedestalRun()) return {}; else return zmq_preview_period; } int64_t DiffractionExperiment::GetDefaultPlotBinning() const { // If images are equal longer than 100 ms, don't bin images // If data collection is shorter than 5s, don't bin if ((GetImageTime() >= 100ms) || (GetImageNum() * GetImageTime() < 5s)) return 1; return 500ms / GetImageTime(); // 1 bin == 500 ms } bool DiffractionExperiment::IsUsingInternalPacketGen() const { return detector_settings.IsInternalGeneratorEnable(); } uint32_t DiffractionExperiment::GetSrcIPv4Address(uint32_t data_stream, uint32_t half_module) const { uint32_t host = half_module + detector.GetUDPInterfaceCount() * GetFirstModuleOfDataStream(data_stream); return detector.GetSrcIPv4Addr(host); } bool DiffractionExperiment::GetMaskModuleEdges() const { return image_format_settings.IsMaskModuleEdges(); } bool DiffractionExperiment::GetMaskChipEdges() const { return image_format_settings.IsMaskChipEdges(); } std::optional DiffractionExperiment::GetUnitCell() const { return dataset.GetUnitCell(); } std::string DiffractionExperiment::GetUnitCellString() const { auto uc = dataset.GetUnitCell(); if (uc.has_value()) { return fmt::format("{:.1f}, {:.1f}, {:.1f}, {:.1f}, {:.1f} {:.1f}", uc.value().a, uc.value().b, uc.value().c, uc.value().alpha, uc.value().beta, uc.value().gamma); } else return "-"; } std::optional DiffractionExperiment::GetSpaceGroupNumber() const { return dataset.GetSpaceGroupNumber(); } int64_t DiffractionExperiment::GetStorageCellNumber() const { auto storage_cells = detector_settings.GetStorageCells(); switch (GetDetectorMode()) { case DetectorMode::PedestalG1: if (IsFixedGainG1()) return storage_cells; case DetectorMode::PedestalG2: if (storage_cells > 1) return 2; else return 1; default: return storage_cells; } } int64_t DiffractionExperiment::GetStorageCellStart() const { return detector_settings.GetStorageCellStart(); } float DiffractionExperiment::GetLowQForAzimInt_recipA() const { return az_integration_settings.GetLowQ_recipA(); } float DiffractionExperiment::GetHighQForAzimInt_recipA() const { return az_integration_settings.GetHighQ_recipA(); } float DiffractionExperiment::GetQSpacingForAzimInt_recipA() const { return az_integration_settings.GetQSpacing_recipA(); } DiffractionExperiment &DiffractionExperiment::MaxSpotCount(int64_t input) { dataset.MaxSpotCount(input); return *this; } int64_t DiffractionExperiment::GetMaxSpotCount() const { if (!IsSpotFindingEnabled()) return 0; return dataset.GetMaxSpotCount(); } std::string DiffractionExperiment::GetSampleName() const { return dataset.GetSampleName(); } void DiffractionExperiment::CheckDataProcessingSettings(const SpotFindingSettings &settings) { check_finite("Signal to noise threshold", settings.signal_to_noise_threshold); check_min("Signal to noise threshold", settings.signal_to_noise_threshold, 1); check_min("Photon count threshold", settings.photon_count_threshold, 0); check_min("Minimum pixels per spot", settings.min_pix_per_spot, 1); check_min("Maximum pixels per spot", settings.max_pix_per_spot, settings.min_pix_per_spot + 1); check_finite("Spot finding high resolution limit", settings.high_resolution_limit); check_finite("Spot finding low resolution limit", settings.low_resolution_limit); if (settings.high_resolution_limit > 0) { check_min("Spot finding high resolution limit", settings.high_resolution_limit, 0.5); check_max("Spot finding high resolution limit", settings.high_resolution_limit, 50.0); if (settings.low_resolution_limit > 0) { check_min("Spot finding low resolution limit", settings.low_resolution_limit, settings.high_resolution_limit); } } else if (settings.low_resolution_limit > 0) { check_min("Spot finding low resolution limit", settings.low_resolution_limit, 1.0); check_max("Spot finding low resolution limit", settings.low_resolution_limit, 50.0); } check_min("Ice ring width in Q-space (A^-1)", settings.ice_ring_width_Q_recipA, 0.0); check_max("Ice ring width in Q-space (A^-1)", settings.ice_ring_width_Q_recipA, 1.0); check_finite("Ice ring width in Q-space (A^-1)", settings.ice_ring_width_Q_recipA); } SpotFindingSettings DiffractionExperiment::DefaultDataProcessingSettings() { return {}; } void DiffractionExperiment::FillMessage(StartMessage &message) const { message.images_per_file = GetImagesPerFile(); message.beam_center_x = GetBeamX_pxl(); message.beam_center_y = GetBeamY_pxl(); message.detector_distance = GetDetectorDistance_mm() * 1e-3f; message.incident_wavelength = GetWavelength_A(); message.incident_energy = GetIncidentEnergy_keV() * 1e3f; message.image_size_x = GetXPixelsNum(); message.image_size_y = GetYPixelsNum(); message.saturation_value = GetSaturationLimit() - 1; message.error_value = GetUnderflow(); message.frame_time = GetImageTime().count() * 1e-6f; message.count_time = GetImageCountTime().count() * 1e-6f; message.number_of_images = GetImageNum(); message.pixel_size_x = GetPixelSize_mm() * 1e-3f; message.pixel_size_y = GetPixelSize_mm() * 1e-3f; message.sensor_material = detector.GetSensorMaterial(); message.sensor_thickness = detector.GetSensorThickness_um() * 1e-6f; message.bit_depth_image = GetByteDepthImage() * 8; message.bit_depth_readout = GetBitDepthReadout(); message.indexing_algorithm = GetIndexingAlgorithm(); message.images_per_trigger = dataset.GetImageNumPerTrigger(); if (GetDetectorType() == DetectorType::JUNGFRAU) { message.storage_cell_number = GetStorageCellNumber(); message.storage_cell_delay_ns = GetStorageCellDelay().count(); } message.file_prefix = GetFilePrefix(); message.pixel_signed = IsPixelSigned(); message.sample_name = GetSampleName(); message.max_spot_count = GetMaxSpotCount(); message.pixel_mask_enabled = IsApplyPixelMask(); message.detector_description = GetDetectorDescription(); message.space_group_number = GetSpaceGroupNumber(); message.unit_cell = GetUnitCell(); message.total_flux = GetTotalFlux(); message.attenuator_transmission = GetAttenuatorTransmission(); message.detector_translation[0] = 0.0f; message.detector_translation[1] = 0.0f; message.detector_translation[2] = GetDetectorDistance_mm() * 1e-3f; message.source_name = GetSourceName(); message.source_type = GetSourceType(); message.instrument_name = GetInstrumentName(); message.summation = GetSummation(); message.user_data = GetHeaderAppendix(); message.countrate_correction_enabled = false; message.flatfield_enabled = false; message.goniometer = dataset.GetGoniometer(); message.grid_scan = dataset.GetGridScan(); message.run_number = GetRunNumber(); message.run_name = GetRunName(); message.gain_file_names = detector.GetGainFileNames(); message.rois = roi_mask.ExportMetadata(); message.data_reduction_factor_serialmx = GetLossyCompressionSerialMX(); message.experiment_group = dataset.GetExperimentGroup(); message.jfjoch_release = jfjoch_version(); message.detector_serial_number = detector.GetSerialNumber(); message.write_master_file = dataset.IsWriteNXmxHDF5Master(); message.overwrite = file_writer.IsOverwriteExistingFiles(); message.file_format = file_writer.GetHDF5MasterFormatVersion(); message.ring_current_mA = dataset.GetRingCurrent_mA(); message.sample_temperature_K = dataset.GetSampleTemperature_K(); message.fluorescence_spectrum = dataset.GetFluorescenceSpectrum(); message.poni_rot1 = dataset.GetPoniRot1_rad(); message.poni_rot2 = dataset.GetPoniRot2_rad(); message.poni_rot3 = dataset.GetPoniRot3_rad(); message.detect_ice_rings = dataset.IsDetectIceRings(); message.channels = {"default"}; switch (GetDetectorType()) { case DetectorType::JUNGFRAU: message.jungfrau_conversion_enabled = IsJungfrauConvPhotonCnt(); if (IsJungfrauConvPhotonCnt()) message.jungfrau_conversion_factor = GetPhotonEnergyForConversion_keV() * 1000; break; case DetectorType::EIGER: case DetectorType::DECTRIS: message.threshold_energy["default"] = GetEigerThreshold_keV() * 1000.0f; // threshold in CBOR is in eV break; } message.geometry_transformation_enabled = IsGeometryTransformed(); if (GetSummation() == 1) message.summation_mode = "none"; else { if (IsCPUSummation()) message.summation_mode = "cpu"; else message.summation_mode = "fpga"; } } float DiffractionExperiment::GetPixelSize_mm() const { return detector.GetPixelSize_mm(); } std::string DiffractionExperiment::GetSourceName() const { return instrument.GetSourceName(); } std::string DiffractionExperiment::GetSourceType() const { return instrument.GetSourceType(); } std::string DiffractionExperiment::GetInstrumentName() const { return instrument.GetInstrumentName(); } int64_t DiffractionExperiment::GetModuleFastDirectionStep(uint16_t module_number) const { return detector.GetGeometry().GetFastDirectionStep(module_number); } int64_t DiffractionExperiment::GetModuleSlowDirectionStep(uint16_t module_number) const { return detector.GetGeometry().GetSlowDirectionStep(module_number); } std::vector DiffractionExperiment::GetDetectorModuleHostname() const { return detector.GetDetectorModuleHostname(); } std::string DiffractionExperiment::GetDetectorDescription() const { return detector.GetDescription(); } DiffractionExperiment &DiffractionExperiment::ApplySolidAngleCorr(bool input) { az_integration_settings.SolidAngleCorrection(input); return *this; } bool DiffractionExperiment::GetApplySolidAngleCorr() const { return az_integration_settings.IsSolidAngleCorrection(); } bool DiffractionExperiment::GetSaveCalibration() const { auto val = dataset.IsSaveCalibration(); if (val.has_value()) return val.value(); // By default calibration is saved if more than 4 images // to limit cases were size of the file is determined by the calibration return (GetImageNum() > 4) && (GetDetectorType() == DetectorType::JUNGFRAU); } DiffractionExperiment &DiffractionExperiment::StorageCellDelay(std::chrono::nanoseconds input) { detector_settings.StorageCellDelay(input); return *this; } std::chrono::nanoseconds DiffractionExperiment::GetStorageCellDelay() const { return detector_settings.GetStorageCellDelay(); } DiffractionExperiment &DiffractionExperiment::Summation(int64_t input) { check_min("Summation factor", input, 1); summation = input; return *this; } int64_t DiffractionExperiment::GetSummation() const { if (GetAutoSummation()) return summation; return 1; } int64_t DiffractionExperiment::GetFPGASummation() const { if (IsCPUSummation()) return 1; return GetSummation(); } int64_t DiffractionExperiment::GetByteDepthFPGA() const { if (IsCPUSummation()) return 2; return GetByteDepthImage(); } int64_t DiffractionExperiment::GetUDPInterfaceCount() const { return detector.GetUDPInterfaceCount(); } std::vector DiffractionExperiment::GetDetectorModuleConfig(const std::vector &net_config) const{ std::vector ret; for (int d = 0; d < GetDataStreamsNum(); d++) { for (int m = 0; m < GetModulesNum(d); m++) { DetectorModuleConfig mod_cfg; mod_cfg.data_stream = d; mod_cfg.udp_dest_port_1 = 1024 + m; mod_cfg.udp_dest_port_2 = 1024 + m; if (detector.GetUDPInterfaceCount() == 2) { mod_cfg.ipv4_src_addr_1 = IPv4AddressToStr(GetSrcIPv4Address(d, 2 * m)); mod_cfg.ipv4_src_addr_2 = IPv4AddressToStr(GetSrcIPv4Address(d, 2 * m + 1)); } else { mod_cfg.ipv4_src_addr_1 = IPv4AddressToStr(GetSrcIPv4Address(d, m)); mod_cfg.ipv4_src_addr_2 = IPv4AddressToStr(GetSrcIPv4Address(d, m)); // not used, settings just in case } mod_cfg.ipv4_dest_addr_1 = net_config[d].ipv4_addr; mod_cfg.ipv4_dest_addr_2 = net_config[d].ipv4_addr; mod_cfg.mac_addr_dest_1 = net_config[d].mac_addr; mod_cfg.mac_addr_dest_2 = net_config[d].mac_addr; mod_cfg.module_id_in_data_stream = m; ret.emplace_back(std::move(mod_cfg)); } } return ret; } float DiffractionExperiment::GetLowQForBkgEstimate_recipA() const { return az_integration_settings.GetBkgEstimateLowQ_recipA(); } float DiffractionExperiment::GetHighQForBkgEstimate_recipA() const { return az_integration_settings.GetBkgEstimateHighQ_recipA(); } DiffractionExperiment &DiffractionExperiment::AttenuatorTransmission(const std::optional &input) { dataset.AttenuatorTransmission(input); return *this; } DiffractionExperiment &DiffractionExperiment::TotalFlux(const std::optional &input) { dataset.TotalFlux(input); return *this; } std::optional DiffractionExperiment::GetAttenuatorTransmission() const { return dataset.GetAttenuatorTransmission(); } std::optional DiffractionExperiment::GetTotalFlux() const { return dataset.GetTotalFlux(); } DiffractionExperiment &DiffractionExperiment::Goniometer(const std::optional &input) { dataset.Goniometer(input); return *this; } std::optional DiffractionExperiment::GetGoniometer() const { return dataset.GetGoniometer(); } std::optional DiffractionExperiment::GetGridScan() const { return dataset.GetGridScan(); } DiffractionExperiment &DiffractionExperiment::UsingGainHG0(bool input) { detector_settings.UseGainHG0(input); return *this; } DiffractionExperiment &DiffractionExperiment::FixedGainG1(bool input) { detector_settings.FixGainG1(input); return *this; } bool DiffractionExperiment::IsFixedGainG1() const { return detector_settings.IsFixGainG1(); } bool DiffractionExperiment::IsUsingGainHG0() const { return detector_settings.IsUseGainHG0(); } DiffractionExperiment &DiffractionExperiment::HeaderAppendix(const nlohmann::json &input) { dataset.HeaderAppendix(input); return *this; } DiffractionExperiment &DiffractionExperiment::ImageAppendix(const nlohmann::json &input) { dataset.ImageAppendix(input); return *this; } const nlohmann::json& DiffractionExperiment::GetHeaderAppendix() const { return dataset.GetHeaderAppendix(); } const nlohmann::json& DiffractionExperiment::GetImageAppendix() const { return dataset.GetImageAppendix(); } uint64_t DiffractionExperiment::GetRunNumber() const { if (dataset.GetRunNumber()) return dataset.GetRunNumber().value(); return series_id; } std::string DiffractionExperiment::GetRunName() const { auto run_name = dataset.GetRunName(); if (run_name) return run_name.value(); else return std::to_string(series_id) + ":" + dataset.GetFilePrefix(); } DiffractionExperiment &DiffractionExperiment::IncrementRunNumber() { series_id++; return *this; } Coord DiffractionExperiment::GetModuleFastDirection(uint16_t module_number) const { return detector.GetGeometry().GetFastDirection(module_number); } Coord DiffractionExperiment::GetModuleSlowDirection(uint16_t module_number) const { return detector.GetGeometry().GetSlowDirection(module_number); } DiffractionExperiment &DiffractionExperiment::JungfrauConvPhotonCnt(bool input) { image_format_settings.JungfrauConversion(input); return *this; } bool DiffractionExperiment::IsJungfrauConvPhotonCnt() const { if (!IsPedestalRun() && (GetDetectorType() == DetectorType::JUNGFRAU)) return image_format_settings.IsJungfrauConversion(); else return false; } DiffractionExperiment &DiffractionExperiment::DetectorDelay(std::chrono::nanoseconds input) { detector_settings.DetectorDelay(input); return *this; } std::chrono::nanoseconds DiffractionExperiment::GetDetectorDelay() const { return detector_settings.GetDetectorDelay(); } const DetectorSetup &DiffractionExperiment::GetDetectorSetup() const { return detector; } DetectorSetup &DiffractionExperiment::Detector() { return detector; } DiffractionExperiment &DiffractionExperiment::PulsedSource(bool input) { instrument.PulsedSource(input); return *this; } bool DiffractionExperiment::IsPulsedSource() const { return instrument.IsPulsedSource(); } bool DiffractionExperiment::IsSpotFindingEnabled() const { return dataset.IsSpotFindingEnabled() && !IsPedestalRun(); } float DiffractionExperiment::GetPhotonEnergyForConversion_keV() const { auto val = GetJungfrauConversionFactor_keV(); if (val.has_value()) return val.value(); else return GetIncidentEnergy_keV(); } DiffractionExperiment &DiffractionExperiment::InternalPacketGeneratorImages(int64_t input) { detector_settings.InternalGeneratorImages(input); return *this; } int64_t DiffractionExperiment::GetInternalPacketGeneratorImages() const { return detector_settings.GetInternalGeneratorImages(); } DiffractionExperiment &DiffractionExperiment::ImportDatasetSettings(const DatasetSettings &input) { auto tmp = dataset; dataset = input; auto image_time = input.GetImageTime(); if (image_time) { switch (GetDetectorType()) { case DetectorType::EIGER: case DetectorType::DECTRIS: check_min("Image time [us]", image_time.value().count(), detector.GetMinFrameTime().count()); summation = 1; break; case DetectorType::JUNGFRAU: if (image_time->count() % GetFrameTime().count() != 0) { dataset = tmp; throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Image time must be multiple of frame time"); } if (GetFrameTime().count() == 0) { dataset = tmp; throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Frame time cannot be zero"); } if (image_time < GetFrameTime()) { dataset = tmp; throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Image time cannot be less than base frame time"); } this->Summation(image_time.value() / GetFrameTime()); break; } } else summation = 1; if (dataset.GridScan()) dataset.GridScan()->ImageNum(GetImageNum()); if (GetFrameNum() >= MAX_FRAMES) { dataset = tmp; throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Frame number (summation * images_per_trigger * ntrigger) cannot exceed " + std::to_string(MAX_FRAMES)); } return *this; } DatasetSettings DiffractionExperiment::GetDatasetSettings() const { return dataset; } ROIMap &DiffractionExperiment::ROI() { return roi_mask; } const ROIMap &DiffractionExperiment::ROI() const { return roi_mask; } std::vector DiffractionExperiment::ExportROIMap() const { return roi_mask.GetROIMap(GetDiffractionGeometry(), GetXPixelsNumConv(), GetYPixelsNumConv()); } DiffractionExperiment &DiffractionExperiment::ImagesPerFile(int64_t input) { dataset.ImagesPerFile(input); return *this; } int64_t DiffractionExperiment::GetImagesPerFile() const { auto tmp = dataset.GetImagesPerFile(); if (tmp == 0) return GetImageNum(); else return tmp; } int64_t DiffractionExperiment::GetImageBufferLocationSize() const { return GetMaxCompressedSize() + 1024 * 1024; } float DiffractionExperiment::GetLossyCompressionSerialMX() const { return dataset.GetLossyCompressionSerialMX(); } DiffractionExperiment &DiffractionExperiment::LossyCompressionSerialMX(float input) { dataset.LossyCompressionSerialMX(input); return *this; } std::optional DiffractionExperiment::GetLossyCompressionPoisson() const { return dataset.GetLossyCompressionPoisson(); } DiffractionExperiment &DiffractionExperiment::LossyCompressionPoisson(const std::optional &input) { dataset.LossyCompressionPoisson(input); return *this; } std::string DiffractionExperiment::GetExperimentGroup() const { return dataset.GetExperimentGroup(); } std::optional DiffractionExperiment::GetPixelValueLowThreshold() const { return dataset.GetPixelValueLowThreshold(); } DiffractionExperiment &DiffractionExperiment::PixelValueLowThreshold(const std::optional &input) { dataset.PixelValueLowThreshold(input); return *this; } DiffractionExperiment &DiffractionExperiment::ImportInstrumentMetadata(const InstrumentMetadata &input) { instrument = input; return *this; } InstrumentMetadata DiffractionExperiment::GetInstrumentMetadata() const { return instrument; } DiffractionExperiment &DiffractionExperiment::ImportFileWriterSettings(const FileWriterSettings &input) { file_writer = input; return *this; } FileWriterSettings DiffractionExperiment::GetFileWriterSettings() const { return file_writer; } bool DiffractionExperiment::IsGeometryTransformed() const { // For DECTRIS detectors always operate in transformed geometry return (GetDetectorType() == DetectorType::DECTRIS) || image_format_settings.IsGeometryTransformed(); } DiffractionExperiment &DiffractionExperiment::GeometryTransformation(bool input) { image_format_settings.GeometryTransformed(input); return *this; } int64_t DiffractionExperiment::GetImageFillValue() const { switch (GetByteDepthImage()) { case 1: if (IsPixelSigned()) return INT8_MIN; return UINT8_MAX; case 2: if (IsPixelSigned()) return INT16_MIN; return UINT16_MAX; case 4: if (IsPixelSigned()) return INT32_MIN; return UINT32_MAX; default: throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Pixel depth unsupported"); } } int64_t DiffractionExperiment::GetBitDepthReadout() const { if (GetDetectorType() == DetectorType::EIGER) { if (detector_settings.GetEigerBitDepth().has_value()) return detector_settings.GetEigerBitDepth().value(); if (GetFrameTime().count() > 1000) return 32; if (GetFrameTime().count() < 450) return 8; return 16; } auto det_value = detector.GetBitDepthReadout(); if (det_value) return det_value.value(); throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Bit depth readout not configured"); } bool DiffractionExperiment::IsPedestalRun() const { switch (GetDetectorMode()) { case DetectorMode::PedestalG0: case DetectorMode::PedestalG1: case DetectorMode::PedestalG2: return true; default: return false; } } bool DiffractionExperiment::GetAutoSummation() const { if (IsPedestalRun() || (GetStorageCellNumber() > 1)) return false; // for pedestal or more than 1 storage cell summation doesn't make sense and should be always turned off else return image_format_settings.IsAutoSummation(); } DiffractionExperiment &DiffractionExperiment::AutoSummation(bool input) { image_format_settings.AutoSummation(input); return *this; } DiffractionExperiment &DiffractionExperiment::BitDepthImage(const std::optional &input) { image_format_settings.BitDepthImage(input); return *this; } DiffractionExperiment &DiffractionExperiment::PixelSigned(const std::optional &input) { image_format_settings.PixelSigned(input); return *this; } DiffractionExperiment &DiffractionExperiment::JungfrauConversionFactor_keV(const std::optional &input) { image_format_settings.JungfrauConvFactor_keV(input); return *this; } std::optional DiffractionExperiment::GetJungfrauConversionFactor_keV() const { return image_format_settings.GetJungfrauConvFactor_keV(); } void DiffractionExperiment::Conversion() { image_format_settings.Conv(); } void DiffractionExperiment::Raw() { image_format_settings.Raw(); } DiffractionExperiment &DiffractionExperiment::ImportDetectorSettings(const DetectorSettings &input) { check_min("Frame time [us]", input.GetFrameTime().count(), detector.GetMinFrameTime().count()); if (GetDetectorType() == DetectorType::JUNGFRAU) { if (!input.GetCountTime().has_value()) { // implicit count time check_max("Frame time [us]", input.GetFrameTime().count(), MAX_COUNT_TIME_JUNGFRAU_IN_US + detector.GetReadOutTime().count()); } else { // explicit count time check_max("Count time [us]", input.GetCountTime().value().count(), MAX_COUNT_TIME_JUNGFRAU_IN_US); } if ((input.GetTiming() == DetectorTiming::Burst) || (input.GetTiming() == DetectorTiming::Gated)) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Burst and gated timing modes not supported in JUNGFRAU"); } detector_settings = input; return *this; } DetectorSettings DiffractionExperiment::GetDetectorSettings() const { return detector_settings; } DiffractionExperiment &DiffractionExperiment::ImportImageFormatSettings(const ImageFormatSettings &input) { image_format_settings = input; return *this; } ImageFormatSettings DiffractionExperiment::GetImageFormatSettings() const { return image_format_settings; } DiffractionExperiment &DiffractionExperiment::ImportAzimuthalIntegrationSettings(const AzimuthalIntegrationSettings &input) { az_integration_settings = input; return *this; } AzimuthalIntegrationSettings DiffractionExperiment::GetAzimuthalIntegrationSettings() const { return az_integration_settings; } DiffractionExperiment &DiffractionExperiment::PolarizationFactor(const std::optional &input) { dataset.PolarizationFactor(input); return *this; } std::optional DiffractionExperiment::GetPolarizationFactor() const { return dataset.GetPolarizationFactor(); } DiffractionExperiment &DiffractionExperiment::SaveCalibration(const std::optional &input) { dataset.SaveCalibration(input); return *this; } float DiffractionExperiment::GetPedestalG0RMSLimit() const { return image_format_settings.GetPedestalG0RMSLimit(); } uint32_t DiffractionExperiment::GetPedestalMinImageCount() const { return detector_settings.GetPedestalMinImageCount(); } float DiffractionExperiment::GetEigerThreshold_keV() const { float thr = GetIncidentEnergy_keV() / 2.0f; auto val = detector_settings.GetEigerThreshold_keV(); if (val) thr = val.value(); if (thr < detector.GetMinThreshold_keV()) thr = detector.GetMinThreshold_keV(); return thr; } DetectorTiming DiffractionExperiment::GetDetectorTiming() const { return detector_settings.GetTiming(); } bool DiffractionExperiment::IsDetectorModuleSync() const { return detector.IsModuleSync(); } int64_t DiffractionExperiment::GetEigerBitDepth() const { auto tmp = detector_settings.GetEigerBitDepth(); if (tmp.has_value()) return tmp.value(); else return 16; } DiffractionExperiment &DiffractionExperiment::EigerBitDepth(const std::optional &input) { detector_settings.EigerBitDepth(input); return *this; } DetectorType DiffractionExperiment::GetDetectorType() const { return detector.GetDetectorType(); } bool DiffractionExperiment::IsMaskPixelsWithoutG0() const { if (GetDetectorType() == DetectorType::JUNGFRAU) return image_format_settings.IsMaskPixelsWithoutG0(); return false; } bool DiffractionExperiment::IsApplyPixelMask() const { return image_format_settings.IsApplyPixelMask() && !IsPedestalRun(); } DiffractionExperiment &DiffractionExperiment::CPUSummation(bool input) { cpu_summation = input; return *this; } DiffractionExperiment &DiffractionExperiment::ApplyPixelMask(bool input) { image_format_settings.ApplyPixelMask(input); return *this; } bool DiffractionExperiment::IsCPUSummation() const { if (summation == 1) return false; if (summation >= MAX_FPGA_SUMMATION) return true; return cpu_summation; } DiffractionExperiment & DiffractionExperiment::ElectronSource(bool input) { instrument.ElectronSource(input); return *this; } bool DiffractionExperiment::IsElectronSource() const { return instrument.IsElectronSource(); } DiffractionExperiment &DiffractionExperiment::SetFileWriterFormat(FileWriterFormat input) { file_writer.HDF5MasterFormatVersion(input); return *this; } FileWriterFormat DiffractionExperiment::GetFileWriterFormat() const { return file_writer.GetHDF5MasterFormatVersion(); } DiffractionGeometry DiffractionExperiment::GetDiffractionGeometry() const { DiffractionGeometry g; g.Wavelength_A(GetWavelength_A()) .PixelSize_mm(GetPixelSize_mm()) .BeamX_pxl(dataset.GetBeamX_pxl()) .BeamY_pxl(dataset.GetBeamY_pxl()) .DetectorDistance_mm(dataset.GetDetectorDistance_mm()) .PoniRot1_rad(dataset.GetPoniRot1_rad()) .PoniRot2_rad(dataset.GetPoniRot2_rad()) .PoniRot3_rad(dataset.GetPoniRot3_rad()); return g; } void DiffractionExperiment::CalcAzIntCorrRawCoord(float *output, size_t module_number) const { if (module_number >= GetModulesNum()) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Wrong module number"); auto geom = GetDiffractionGeometry(); for (int i = 0; i < RAW_MODULE_SIZE; i++) { auto [x,y] = RawToConvertedCoordinate(*this, module_number, i); if (GetApplySolidAngleCorr()) output[i] /= geom.CalcAzIntSolidAngleCorr(static_cast(x), static_cast(y)); auto p = GetPolarizationFactor(); if (p.has_value()) output[i] /= geom.CalcAzIntPolarizationCorr(static_cast(x), static_cast(y), p.value()); } } void DiffractionExperiment::CalcSpotFinderResolutionMap(float *data, size_t module_number) const { if (module_number >= GetModulesNum()) throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Wrong module number"); auto geom = GetDiffractionGeometry(); for (int i = 0; i < RAW_MODULE_SIZE; i++) { auto [x,y] = RawToConvertedCoordinate(*this, module_number, i); data[i] = geom.PxlToRes(static_cast(x), static_cast(y)); } } CompressedImageMode DiffractionExperiment::GetImageMode() const { switch (GetByteDepthImage()) { case 1: return (IsPixelSigned() ? CompressedImageMode::Int8 : CompressedImageMode::Uint8); case 2: return (IsPixelSigned() ? CompressedImageMode::Int16 : CompressedImageMode::Uint16); case 4: return (IsPixelSigned() ? CompressedImageMode::Int32 : CompressedImageMode::Uint32); default: throw (JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Bit depth not supported")); } } DiffractionExperiment &DiffractionExperiment::IndexingAlgorithm(IndexingAlgorithmEnum input) { indexing.Algorithm(input); return *this; } IndexingAlgorithmEnum DiffractionExperiment::GetIndexingAlgorithm() const { auto cell = GetUnitCell().has_value(); switch (indexing.GetAlgorithm()) { case IndexingAlgorithmEnum::FFBIDX: if (!cell) return IndexingAlgorithmEnum::None; return IndexingAlgorithmEnum::FFBIDX; case IndexingAlgorithmEnum::Auto: if (get_gpu_count() == 0) return IndexingAlgorithmEnum::FFTW; if (!cell) return IndexingAlgorithmEnum::FFT; return IndexingAlgorithmEnum::FFBIDX; case IndexingAlgorithmEnum::FFT: return IndexingAlgorithmEnum::FFT; case IndexingAlgorithmEnum::FFTW: return IndexingAlgorithmEnum::FFTW; default: return IndexingAlgorithmEnum::None; } } IndexingSettings DiffractionExperiment::GetIndexingSettings() const { return indexing; } DiffractionExperiment &DiffractionExperiment::ImportIndexingSettings(const IndexingSettings &input) { indexing = input; return *this; } float DiffractionExperiment::GetIndexingTolerance() const { return indexing.GetTolerance(); } DiffractionExperiment &DiffractionExperiment::IndexingTolerance(float input) { indexing.Tolerance(input); return *this; } DiffractionExperiment &DiffractionExperiment::GridScan(const std::optional &input) { dataset.GridScan(input); return *this; } DiffractionExperiment &DiffractionExperiment::SampleTemperature_K(const std::optional &input) { dataset.SampleTemperature_K(input); return *this; } DiffractionExperiment &DiffractionExperiment::RingCurrent_mA(const std::optional &input) { dataset.RingCurrent_mA(input); return *this; } std::optional DiffractionExperiment::GetSampleTemperature_K() const { return dataset.GetSampleTemperature_K(); } std::optional DiffractionExperiment::GetRingCurrent_mA() const { return dataset.GetRingCurrent_mA(); } DiffractionExperiment &DiffractionExperiment::ImportBraggIntegrationSettings(const BraggIntegrationSettings &input) { bragg_integration_settings = input; return *this; } BraggIntegrationSettings DiffractionExperiment::GetBraggIntegrationSettings() const { return bragg_integration_settings; } DiffractionExperiment &DiffractionExperiment::PoniRot1_rad(float input) { dataset.PoniRot1_rad(input); return *this; } DiffractionExperiment &DiffractionExperiment::PoniRot2_rad(float input) { dataset.PoniRot2_rad(input); return *this; } DiffractionExperiment &DiffractionExperiment::PoniRot3_rad(float input) { dataset.PoniRot3_rad(input); return *this; } float DiffractionExperiment::GetPoniRot1_rad() const { return dataset.GetPoniRot1_rad(); } float DiffractionExperiment::GetPoniRot2_rad() const { return dataset.GetPoniRot2_rad(); } float DiffractionExperiment::GetPoniRot3_rad() const { return dataset.GetPoniRot3_rad(); } GeomRefinementAlgorithmEnum DiffractionExperiment::GetGeomRefinementAlgorithm() const { return indexing.GetGeomRefinementAlgorithm(); } DiffractionExperiment &DiffractionExperiment::GeomRefinementAlgorithm(GeomRefinementAlgorithmEnum input) { indexing.GeomRefinementAlgorithm(input); return *this; } gemmi::CrystalSystem DiffractionExperiment::GetCrystalSystem() const { auto sg = GetGemmiSpaceGroup(); if (!sg) return gemmi::CrystalSystem::Monoclinic; return sg->crystal_system(); } std::string DiffractionExperiment::GetSpaceGroupName() const { auto sg = GetGemmiSpaceGroup(); if (!sg) return ""; return sg->short_name(); } std::optional DiffractionExperiment::GetGemmiSpaceGroup() const { auto sg = GetSpaceGroupNumber(); if (!sg) return std::nullopt; const gemmi::SpaceGroup *g = gemmi::find_spacegroup_by_number(sg.value()); if (g == nullptr) return std::nullopt; return *g; } char DiffractionExperiment::GetCentering() const { auto sg = GetGemmiSpaceGroup(); if (!sg) return 'P'; return sg->centring_type(); } DiffractionExperiment &DiffractionExperiment::FluorescenceSpectrum(const XrayFluorescenceSpectrum &input) { dataset.FluorescenceSpectrum(input); return *this; } DiffractionExperiment &DiffractionExperiment::DetectIceRings(bool input) { dataset.DetectIceRings(input); return *this; } bool DiffractionExperiment::IsDetectIceRings() const { return dataset.IsDetectIceRings(); } const XrayFluorescenceSpectrum &DiffractionExperiment::GetFluorescenceSpectrum() const { return dataset.GetFluorescenceSpectrum(); }